Compare commits

..

3 Commits

Author SHA1 Message Date
Ayke van Laethem a1e69bbc13 WIP: precise GC 2019-05-14 14:20:39 +02:00
Ayke van Laethem 1b4d71bd3d runtime: refactor garbage collectors 2019-05-14 14:16:24 +02:00
Ayke van Laethem 43b3bb6e83 all: rename garbage collectors
dumb -> leaking:
  make it more clear what this "GC" does: leak everything.
marksweep -> conservative:
  "marksweep" is too generic, use "conservative" to differentiate
  between future garbage collectors: precise marksweep / mark-compact /
  refcounting.
2019-05-14 14:16:16 +02:00
242 changed files with 4376 additions and 14796 deletions
+74 -52
View File
@@ -40,40 +40,76 @@ commands:
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
rm node-v10.15.1-linux-x64.tar.xz rm node-v10.15.1-linux-x64.tar.xz
dep:
steps:
- run:
name: "Install Go dependencies"
command: |
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
dep ensure --vendor-only
llvm-source-linux: llvm-source-linux:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-8-v5 - llvm-source-8-v2
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-8-v5 key: llvm-source-8-v2
paths: paths:
- llvm-project - llvm
smoketest:
steps:
- smoketest-no-avr
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
smoketest-no-avr:
steps:
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
test-linux: test-linux:
parameters:
llvm:
type: string
steps: steps:
- checkout - checkout
- submodules - submodules
- apt-dependencies: - apt-dependencies:
llvm: "-8" llvm: <<parameters.llvm>>
- install-node - install-node
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }} - go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux - llvm-source-linux
- dep
- run: go install . - run: go install .
- run: go test -v ./transform . - run: go test -v
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest RISCV=0 - smoketest
- save_cache: - save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo - ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check - run: make fmt-check
build-linux: build-linux:
steps: steps:
@@ -96,12 +132,12 @@ commands:
- install-node - install-node
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }} - go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux - llvm-source-linux
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-8-linux-v7 - llvm-build-8-linux-v4
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
@@ -119,7 +155,7 @@ commands:
make llvm-build make llvm-build
fi fi
- save_cache: - save_cache:
key: llvm-build-8-linux-v7 key: llvm-build-8-linux-v4
paths: paths:
llvm-build llvm-build
- run: - run:
@@ -128,6 +164,7 @@ commands:
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8 ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8 ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8 ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
- dep
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
@@ -139,11 +176,10 @@ commands:
- store_artifacts: - store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache: - save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }} key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths: paths:
- ~/.cache/go-build - ~/.cache/go-build
- ~/.cache/tinygo - ~/.cache/tinygo
- /go/pkg/mod
- run: - run:
name: "Extract release tarball" name: "Extract release tarball"
command: | command: |
@@ -151,12 +187,7 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version tinygo version
- run: - smoketest
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
- run: make smoketest
build-macos: build-macos:
steps: steps:
- checkout - checkout
@@ -164,27 +195,20 @@ commands:
- run: - run:
name: "Install dependencies" name: "Install dependencies"
command: | command: |
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache: - restore_cache:
keys: keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - llvm-source-8-macos-v2
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-8-macos-v5
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-8-macos-v5 key: llvm-source-8-macos-v2
paths: paths:
- llvm-project - llvm
- restore_cache: - restore_cache:
keys: keys:
- llvm-build-8-macos-v6 - llvm-build-8-macos-v3
- run: - run:
name: "Build LLVM" name: "Build LLVM"
command: | command: |
@@ -196,13 +220,16 @@ commands:
make llvm-build make llvm-build
fi fi
- save_cache: - save_cache:
key: llvm-build-8-macos-v6 key: llvm-build-8-macos-v3
paths: paths:
llvm-build llvm-build
- run: - run:
name: "Create LLVM symlinks" name: "Create LLVM symlinks"
command: | command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8 ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
- run:
name: "Install Go dependencies"
command: dep ensure --vendor-only
- run: - run:
name: "Test TinyGo" name: "Test TinyGo"
command: make test command: make test
@@ -220,39 +247,34 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version tinygo version
- run: - smoketest-no-avr
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
jobs: jobs:
test-llvm8-go111: test-llvm8-go111:
docker: docker:
- image: circleci/golang:1.11-stretch - image: circleci/golang:1.11
working_directory: /go/src/github.com/tinygo-org/tinygo
steps: steps:
- test-linux - test-linux:
llvm: "-8"
test-llvm8-go112: test-llvm8-go112:
docker: docker:
- image: circleci/golang:1.12-stretch - image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps: steps:
- test-linux - test-linux:
llvm: "-8"
build-linux: build-linux:
docker: docker:
- image: circleci/golang:1.12-stretch - image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps: steps:
- build-linux - build-linux
build-macos: build-macos:
macos: macos:
xcode: "10.1.0" xcode: "10.1.0"
working_directory: ~/go/src/github.com/tinygo-org/tinygo
steps: steps:
- build-macos - build-macos
+2 -4
View File
@@ -5,12 +5,10 @@ src/device/avr/*.ld
src/device/avr/*.s src/device/avr/*.s
src/device/nrf/*.go src/device/nrf/*.go
src/device/nrf/*.s src/device/nrf/*.s
src/device/sam/*.go
src/device/sam/*.s
src/device/sifive/*.go
src/device/sifive/*.s
src/device/stm32/*.go src/device/stm32/*.go
src/device/stm32/*.s src/device/stm32/*.s
src/device/sam/*.go
src/device/sam/*.s
vendor vendor
llvm llvm
llvm-build llvm-build
+1 -1
View File
@@ -9,7 +9,7 @@
url = https://github.com/avr-rust/avr-mcu.git url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"] [submodule "lib/cmsis-svd"]
path = lib/cmsis-svd path = lib/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd url = https://github.com/posborne/cmsis-svd
[submodule "lib/compiler-rt"] [submodule "lib/compiler-rt"]
path = lib/compiler-rt path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git url = https://github.com/llvm-mirror/compiler-rt.git
+5 -8
View File
@@ -16,6 +16,7 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.11+) * Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * CMake
@@ -26,20 +27,16 @@ on a different system like Mac.
## Download the source ## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone The first step is to download the TinyGo sources. Then, inside the directory,
the git repository). Then, inside the directory, download the LLVM source: perform these steps:
make llvm-source dep ensure -vendor-only # download Go dependencies
make llvm-source # download LLVM
You can also store LLVM outside of the TinyGo root directory by setting the You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not `LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide. covered by this guide.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD ## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment Before starting the build, you may want to set the following environment
-145
View File
@@ -1,148 +1,3 @@
0.8.0
---
* **command line**
- fix parsing of beta Go versions
- check the major/minor installed version of Go before compiling
- validate `-target` flag better to not panic on an invalid target
* **compiler**
- implement full slice expression: `s[:2:4]`
- fix a crash when storing a linked list in an interface
- fix comparing struct types by making type IDs more unique
- fix some bugs in IR generation
- add support for linked lists in reflect data
- implement `[]rune` to string conversion
- implement support for `go` on func values
* **standard library**
- `reflect`: add support for named types
- `reflect`: add support for `t.Bits()`
- `reflect`: add basic support for `t.AssignableTo()`
- `reflect`: implement `t.Align()`
- `reflect`: add support for struct types
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
- `reflect`: implement support for array types
- `reflect`: implement `t.Comparable()`
- `runtime`: implement stack-based scheduler
- `runtime`: fix bug in the sleep queue of the scheduler
- `runtime`: implement `memcpy` for Cortex-M
- `testing`: implement stub `testing.B` struct
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
* **targets**
- `386`: add support for linux/386 syscalls
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
used
- `atsamd21`: correct issue with invalid first reading coming from ADC
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
- `atsamd21`: make pin selection more flexible for peripherals
- `atsamd21`: fix minimum delay in `time.Sleep`
- `atsamd51`: fix minimum delay in `time.Sleep`
- `nrf`: improve SPI write-only speed, by making use of double buffering
- `stm32f103`: fix SPI frequency selection
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
- `stm32f103`: allow board specific UART usage
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
- `cortex-m`: add support for `arm.SystemReset()`
- `gameboy-advance`: add initial support for the GameBoy Advance
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
- `wasm`: add syscall/js.valueSetIndex support
- `wasm`: add syscall/js.valueInvoke support
0.7.1
---
* **targets**
- `atsamd21`: add support for the `-port` flag in the flash subcommand
0.7.0
---
* **command line**
- try more locations to find Clang built-in headers
- add support for `tinygo test`
- build current directory if no package is specified
- support custom .json target spec with `-target` flag
- use zversion.go to detect version of GOROOT version
- make initial heap size configurable for some targets (currently WebAssembly
only)
* **cgo**
- add support for bitfields using generated getters and setters
- add support for anonymous structs
* **compiler**
- show an error instead of panicking on duplicate function definitions
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support
It has been replaced with the runtime/volatile package.
- allow poiners in map keys
- support non-constant syscall numbers
- implement non-blocking selects
- add support for the `-tags` flag
- add support for `string` to `[]rune` conversion
- implement a portable conservative garbage collector (with support for wasm)
- add the `//go:noinline` pragma
* **standard library**
- `os`: add `os.Exit` and `syscall.Exit`
- `os`: add several stubs
- `runtime`: fix heap corruption in conservative GC
- `runtime`: add support for math intrinsics where supported, massively
speeding up some benchmarks
- `testing`: add basic support for testing
* **targets**
- add support for a generic target that calls `__tinygo_*` functions for
peripheral access
- `arduino-nano33`: add support for this board
- `hifive1`: add support for this RISC-V board
- `reelboard`: add e-paper pins
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
- `wasm`: put the stack at the start of linear memory, to detect stack
overflows
0.6.0
---
* **command line**
- some portability improvements
- make `$GOROOT` more robust and configurable
- check for Clang at the Homebrew install location as fallback
* **compiler driver**
- support multiple variations of LLVM commands, for non-Debian distributions
* **compiler**
- improve code quality in multiple ways
- make panic configurable, adding trap on panic
- refactor many internal parts of the compiler
- print all errors encountered during compilation
- implement calling function values of a named type
- implement returning values from blocking functions
- allow larger-than-int values to be sent across a channel
- implement complex arithmetic
- improve hashmap support
- add debuginfo for function arguments
- insert nil checks on stores (increasing code size)
- implement volatile operations as compiler builtins
- add `//go:inline` pragma
- add build tags for the Go stdlib version
* **cgo**
- implement `char`, `enum` and `void*` types
- support `#include` for builtin headers
- improve typedef/struct/enum support
- only include symbols that are necessary, for broader support
- mark external function args as `nocapture`
- implement support for some `#define` constants
- implement support for multiple CGo files in a single package
- **standard library**
- `machine`: remove microbit matrix (moved to drivers repository)
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
- `runtime`: print more interface types on panic, including `error`
* **targets**
- `arm`: print an error on HardFault (including stack overflows)
- `atsamd21`: fix a bug in the ADC peripheral
- `atsamd21`: add support for I2S
- `feather-m0`: add support for this board
- `nrf51`: fix a bug in I2C
- `stm32f103xx`: fix a bug in I2C
- `syscall`: implement `Exit` on unix
- `trinket-m0`: add support for this board
- `wasm`: make _main_ example smaller
- `wasm`: don't cache wasm file in the server, for ease of debugging
- `wasm`: work around bug #41508 that caused a deadlock while linking
- `wasm`: add support for `js.FuncOf`
0.5.0 0.5.0
--- ---
- **compiler driver** - **compiler driver**
-4
View File
@@ -16,16 +16,12 @@ Please open a Github issue with your problem, and we will be happy to assist.
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated. We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile. A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo ### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated. As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board). Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful. Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
-17
View File
@@ -1,17 +0,0 @@
# This is the official list of TinyGo authors for copyright purposes.
#
# This file is not actively maintained.
# To be included, send a change adding the individual or
# company who owns a contribution's copyright.
#
# Names should be added to this file as one of
# Organization's name
# Individual's name <submission email address>
# Individual's name <submission email address> <email2> <emailN>
#
# Please keep the list sorted.
Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
+32 -29
View File
@@ -1,31 +1,34 @@
# TinyGo base stage installs Go 1.12, LLVM 8 and the TinyGo compiler itself. # TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:1.12 AS tinygo-base FROM golang:latest AS tinygo-base
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y llvm-8-dev libclang-8-dev git apt-get install -y llvm-8-dev libclang-8-dev git
COPY . /tinygo RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
COPY . /go/src/github.com/tinygo-org/tinygo
# remove submodules directories and re-init them to fix any hard-coded paths # remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step. # after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
rm -rf ./lib/* && \ rm -rf ./lib/* && \
git submodule update --init --recursive --force git submodule update --init --recursive --force
RUN cd /tinygo/ && \ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
go install /tinygo/ dep ensure --vendor-only && \
go install /go/src/github.com/tinygo-org/tinygo/
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM. # tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \ echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
apt-get update && \ apt-get update && \
apt-get install -y libllvm8 lld-8 apt-get install -y libllvm8 lld-8
@@ -33,17 +36,17 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
FROM tinygo-base AS tinygo-avr FROM tinygo-base AS tinygo-avr
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \ apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \ make gen-device-avr && \
apt-get remove -y python3 && \ apt-get remove -y python3 make && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get clean apt-get clean
@@ -51,32 +54,32 @@ RUN cd /tinygo/ && \
FROM tinygo-base AS tinygo-arm FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y apt-utils python3 make clang-8 && \ apt-get install -y apt-utils python3 make clang-8 && \
make gen-device-nrf && make gen-device-stm32 && \ make gen-device-nrf && make gen-device-stm32 && \
apt-get remove -y python3 && \ apt-get remove -y python3 make && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get clean apt-get clean
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms. # tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /tinygo/Makefile /tinygo/ COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
RUN cd /tinygo/ && \ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
apt-get update && \ apt-get update && \
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \ apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \ make gen-device && \
apt-get remove -y python3 && \ apt-get remove -y python3 make && \
apt-get autoremove -y && \ apt-get autoremove -y && \
apt-get clean apt-get clean
Generated
+51
View File
@@ -0,0 +1,51 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
name = "github.com/blakesmith/ar"
packages = ["."]
pruneopts = "UT"
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
[[projects]]
branch = "master"
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
name = "github.com/marcinbor85/gohex"
packages = ["."]
pruneopts = "UT"
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
[[projects]]
branch = "master"
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
name = "golang.org/x/tools"
packages = [
"go/ast/astutil",
"go/ssa",
"go/types/typeutil",
]
pruneopts = "UT"
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
[[projects]]
branch = "llvm8"
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
name = "tinygo.org/x/go-llvm"
packages = ["."]
pruneopts = "UT"
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/blakesmith/ar",
"github.com/marcinbor85/gohex",
"golang.org/x/tools/go/ast/astutil",
"golang.org/x/tools/go/ssa",
"tinygo.org/x/go-llvm",
]
solver-name = "gps-cdcl"
solver-version = 1
+11
View File
@@ -0,0 +1,11 @@
[[constraint]]
branch = "llvm8"
name = "tinygo.org/x/go-llvm"
[[constraint]]
branch = "master"
name = "golang.org/x/tools"
[prune]
go-tests = true
unused-packages = true
+22 -75
View File
@@ -3,12 +3,12 @@
all: tinygo all: tinygo
tinygo: build/tinygo tinygo: build/tinygo
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# Default build and source directories, as created by `make llvm-build`. # Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm-project/clang CLANG_SRC ?= llvm/tools/clang
LLD_SRC ?= llvm-project/lld LLD_SRC ?= llvm/tools/lld
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
@@ -18,17 +18,15 @@ ifeq ($(UNAME_S),Linux)
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
endif endif
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++ CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP) LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# For static linking. # For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","") CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include CGO_CXXFLAGS=-std=c++11
CGO_CXXFLAGS=-std=c++11 CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
endif
clean: clean:
@@ -40,8 +38,7 @@ fmt:
fmt-check: fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr: gen-device-avr:
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/ ./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
@@ -56,86 +53,36 @@ gen-device-sam:
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel ./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
go fmt ./src/device/sam go fmt ./src/device/sam
gen-device-sifive:
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
go fmt ./src/device/sifive
gen-device-stm32: gen-device-stm32:
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro ./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
go fmt ./src/device/stm32 go fmt ./src/device/stm32
# Get LLVM sources. # Get LLVM sources.
llvm-project/README.md: llvm/README.txt:
git clone -b release/8.x https://github.com/llvm/llvm-project git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
llvm-source: llvm-project/README.md llvm/tools/clang/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
llvm/tools/lld/README.md:
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
# Configure LLVM. # Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd) llvm-build/build.ninja: llvm-source
$(LLVM_BUILDDIR)/build.ninja: llvm-source mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
# Build LLVM. # Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja llvm-build: llvm-build/build.ninja
cd $(LLVM_BUILDDIR); ninja cd llvm-build; ninja
# Build the Go compiler. # Build the Go compiler.
build/tinygo: build/tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi @if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
test: test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm ./transform . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
tinygo-test:
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
# test all examples
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
tinygo build -size short -o test.elf -target=pca10040 examples/adc
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=pca10040 examples/button
tinygo build -size short -o test.elf -target=pca10040 examples/button2
tinygo build -size short -o test.elf -target=pca10040 examples/echo
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
tinygo build -size short -o test.elf -target=pca10040 examples/serial
tinygo build -size short -o test.elf -target=pca10040 examples/test
# test all targets/boards
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
tinygo build -size short -o test.elf -target=microbit examples/echo
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display
tinygo build -size short -o test.elf -target=itsybitsy-m4 examples/blinky1
tinygo build -size short -o test.elf -target=nucleo-f103rb examples/blinky1
ifneq ($(AVR), 0)
tinygo build -size short -o test.elf -target=arduino examples/blinky1
tinygo build -size short -o test.elf -target=digispark examples/blinky1
endif
ifneq ($(RISCV), 0)
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
endif
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
release: build/tinygo gen-device release: build/tinygo gen-device
@mkdir -p build/release/tinygo/bin @mkdir -p build/release/tinygo/bin
+5 -12
View File
@@ -2,7 +2,7 @@
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools. TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language. It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
@@ -17,8 +17,8 @@ import (
) )
func main() { func main() {
led := machine.LED led := machine.GPIO{machine.LED}
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for { for {
led.Low() led.Low()
time.Sleep(time.Millisecond * 1000) time.Sleep(time.Millisecond * 1000)
@@ -43,20 +43,13 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux. You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 18 microcontroller boards are currently supported: The following microcontroller boards are currently supported:
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333) * [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727) * [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3) * [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [BBC micro:bit](https://microbit.org/) * [BBC:Microbit](https://microbit.org/)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill) * [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [Digispark](http://digistump.com/products/1) * [Digispark](http://digistump.com/products/1)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle) * [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK) * [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
-361
View File
@@ -35,8 +35,6 @@ type cgoPackage struct {
globals map[string]globalInfo globals map[string]globalInfo
typedefs map[string]*typedefInfo typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
} }
// constantInfo stores some information about a CGo constant found by libclang // constantInfo stores some information about a CGo constant found by libclang
@@ -69,24 +67,6 @@ type typedefInfo struct {
// elaboratedTypeInfo contains some information about an elaborated type // elaboratedTypeInfo contains some information about an elaborated type
// (struct, union) found in the C AST. // (struct, union) found in the C AST.
type elaboratedTypeInfo struct { type elaboratedTypeInfo struct {
typeExpr *ast.StructType
pos token.Pos
bitfields []bitfieldInfo
}
// bitfieldInfo contains information about a single bitfield in a struct. It
// keeps information about the start, end, and the special (renamed) base field
// of this bitfield.
type bitfieldInfo struct {
field *ast.Field
name string
pos token.Pos
startBit int64
endBit int64 // may be 0 meaning "until the end of the field"
}
// enumInfo contains information about an enum in the C.
type enumInfo struct {
typeExpr ast.Expr typeExpr ast.Expr
pos token.Pos pos token.Pos
} }
@@ -160,7 +140,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
globals: map[string]globalInfo{}, globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{}, typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{}, elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{},
} }
// Add a new location for the following file. // Add a new location for the following file.
@@ -264,9 +243,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
// Add elaborated types for C structs and unions. // Add elaborated types for C structs and unions.
p.addElaboratedTypes() p.addElaboratedTypes()
// Add enum types and enum constants for C enums.
p.addEnumTypes()
// Patch the AST to use the declared types and functions. // Patch the AST to use the declared types and functions.
for _, f := range files { for _, f := range files {
astutil.Apply(f, p.walker, nil) astutil.Apply(f, p.walker, nil)
@@ -594,343 +570,6 @@ func (p *cgoPackage) addElaboratedTypes() {
} }
obj.Decl = typeSpec obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec) gen.Specs = append(gen.Specs, typeSpec)
// If this struct has bitfields, create getters for them.
for _, bitfield := range typ.bitfields {
p.createBitfieldGetter(bitfield, typeName)
p.createBitfieldSetter(bitfield, typeName)
}
}
p.generated.Decls = append(p.generated.Decls, gen)
}
// createBitfieldGetter creates a bitfield getter function like the following:
//
// func (s *C.struct_foo) bitfield_b() byte {
// return (s.__bitfield_1 >> 5) & 0x1
// }
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
// The value to return from the getter.
// Not complete: this is just an expression to get the complete field.
var result ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
if bitfield.startBit != 0 {
// Shift to the right by .startBit so that fields that come before are
// shifted off.
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.SHR,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
}
}
if bitfield.endBit != 0 {
// Mask off the high bits so that fields that come after this field are
// masked off.
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
result = &ast.BinaryExpr{
X: result,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(and, 16),
},
}
}
// Create the getter function.
getter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
Closing: bitfield.pos,
},
Results: &ast.FieldList{
List: []*ast.Field{
&ast.Field{
Type: bitfield.field.Type,
},
},
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.ReturnStmt{
Return: bitfield.pos,
Results: []ast.Expr{
result,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, getter)
}
// createBitfieldSetter creates a bitfield setter function like the following:
//
// func (s *C.struct_foo) set_bitfield_b(value byte) {
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
// }
//
// Or the following:
//
// func (s *C.struct_foo) set_bitfield_c(value byte) {
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
// }
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
// The full field with all bitfields.
var field ast.Expr = &ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
}
// The value to insert into the field.
var valueToInsert ast.Expr = &ast.Ident{
NamePos: bitfield.pos,
Name: "value",
}
if bitfield.endBit != 0 {
// Make sure the value is in range with a mask.
valueToInsert = &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
},
}
// Create a mask for the AND NOT operation.
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
// Zero the bits in the field that will soon be inserted.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND_NOT,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
} else { // bitfield.endBit == 0
// We don't know exactly how many high bits should be zeroed. So we do
// something different: keep the low bits with a mask and OR the new
// value with it.
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
// Extract the lower bits.
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.AND,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: "0x" + strconv.FormatUint(mask, 16),
},
}
}
// Bitwise OR with the new value (after the new value has been shifted).
field = &ast.BinaryExpr{
X: field,
OpPos: bitfield.pos,
Op: token.OR,
Y: &ast.BinaryExpr{
X: valueToInsert,
OpPos: bitfield.pos,
Op: token.SHL,
Y: &ast.BasicLit{
ValuePos: bitfield.pos,
Kind: token.INT,
Value: strconv.FormatInt(bitfield.startBit, 10),
},
},
}
// Create the setter function.
setter := &ast.FuncDecl{
Recv: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
},
},
Type: &ast.StarExpr{
Star: bitfield.pos,
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
},
Closing: bitfield.pos,
},
Name: &ast.Ident{
NamePos: bitfield.pos,
Name: "set_bitfield_" + bitfield.name,
},
Type: &ast.FuncType{
Func: bitfield.pos,
Params: &ast.FieldList{
Opening: bitfield.pos,
List: []*ast.Field{
&ast.Field{
Names: []*ast.Ident{
&ast.Ident{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
},
},
Closing: bitfield.pos,
},
},
Body: &ast.BlockStmt{
Lbrace: bitfield.pos,
List: []ast.Stmt{
&ast.AssignStmt{
Lhs: []ast.Expr{
&ast.SelectorExpr{
X: &ast.Ident{
NamePos: bitfield.pos,
Name: "s",
Obj: nil,
},
Sel: &ast.Ident{
NamePos: bitfield.pos,
Name: bitfield.field.Names[0].Name,
},
},
},
TokPos: bitfield.pos,
Tok: token.ASSIGN,
Rhs: []ast.Expr{
field,
},
},
},
Rbrace: bitfield.pos,
},
}
p.generated.Decls = append(p.generated.Decls, setter)
}
// addEnumTypes adds C enums to the AST. For example, the following C code:
//
// enum option {
// optionA,
// optionB = 5,
// };
//
// is translated to the following Go code equivalent:
//
// type C.enum_option int32
//
// The constants are treated just like macros so are inserted into the AST by
// addConstDecls.
// See also: https://en.cppreference.com/w/c/language/enum
func (p *cgoPackage) addEnumTypes() {
if len(p.enums) == 0 {
return
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.TYPE,
}
names := make([]string, 0, len(p.enums))
for name := range p.enums {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
typ := p.enums[name]
typeName := "C.enum_" + name
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typ.typeExpr,
}
obj.Decl = typeSpec
gen.Specs = append(gen.Specs, typeSpec)
} }
p.generated.Decls = append(p.generated.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
} }
+69 -208
View File
@@ -49,13 +49,9 @@ GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/ */
import "C" import "C"
@@ -505,94 +501,90 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
switch underlying.kind { switch underlying.kind {
case C.CXType_Record: case C.CXType_Record:
return p.makeASTType(underlying, pos) return p.makeASTType(underlying, pos)
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default: default:
panic("unknown elaborated type") panic("unknown elaborated type")
} }
case C.CXType_Record: case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ) cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor)) name := getString(C.tinygo_clang_getCursorSpelling(cursor))
if name == "" { var cgoName string
// Anonymous record, probably inside a typedef. switch C.tinygo_clang_getCursorKind(cursor) {
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos) case C.CXCursor_StructDecl:
if bitfieldList != nil { cgoName = "struct_" + name
// This struct has bitfields, so we have to declare it as a case C.CXCursor_UnionDecl:
// named type (for bitfield getters/setters to work). cgoName = "union_" + name
p.anonStructNum++ default:
cgoName := "struct_" + strconv.Itoa(p.anonStructNum) panic("unknown record declaration")
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ }
typeExpr: typeExpr, if _, ok := p.elaboratedTypes[cgoName]; !ok {
pos: pos, p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
bitfields: bitfieldList, fieldList := &ast.FieldList{
} Opening: pos,
return &ast.Ident{ Closing: pos,
NamePos: pos,
Name: "C." + cgoName,
}
} }
return typeExpr ref := storedRefs.Put(struct {
} else { fieldList *ast.FieldList
var cgoName string pkg *cgoPackage
}{fieldList, p})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) { switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl: case C.CXCursor_StructDecl:
cgoName = "struct_" + name
case C.CXCursor_UnionDecl:
cgoName = "union_" + name
default:
panic("unknown record declaration")
}
if _, ok := p.elaboratedTypes[cgoName]; !ok {
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{ p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr, typeExpr: &ast.StructType{
pos: pos, Struct: pos,
bitfields: bitfieldList, Fields: fieldList,
},
pos: pos,
} }
} case C.CXCursor_UnionDecl:
return &ast.Ident{ if len(fieldList.List) > 1 {
NamePos: pos, // Insert a special field at the front (of zero width) as a
Name: "C." + cgoName, // marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
default:
panic("unreachable")
} }
} }
case C.CXType_Enum: return &ast.Ident{
cursor := C.tinygo_clang_getTypeDeclaration(typ) NamePos: pos,
name := getString(C.tinygo_clang_getCursorSpelling(cursor)) Name: "C." + cgoName,
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
if name == "" {
// anonymous enum
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
return p.makeASTType(underlying, pos)
} else {
// named enum
if _, ok := p.enums[name]; !ok {
ref := storedRefs.Put(p)
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
p.enums[name] = enumInfo{
typeExpr: p.makeASTType(underlying, pos),
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C.enum_" + name,
}
} }
} }
if typeName == "" { if typeName == "" {
// Report this as an error.
spelling := getString(C.clang_getTypeSpelling(typ))
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
})
// Fallback, probably incorrect but at least the error points to an odd // Fallback, probably incorrect but at least the error points to an odd
// type name. // type name.
typeName = "C." + spelling typeName = "C." + getString(C.clang_getTypeSpelling(typ))
} }
return &ast.Ident{ return &ast.Ident{
NamePos: pos, NamePos: pos,
@@ -600,143 +592,25 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
} }
} }
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type. Unions are implemented by setting the first field to a
// zero-lengt "C union" field, which cannot be written in Go directly.
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
var bitfieldList []bitfieldInfo
inBitfield := false
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
defer storedRefs.Remove(ref)
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
switch C.tinygo_clang_getCursorKind(cursor) {
case C.CXCursor_StructDecl:
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
case C.CXCursor_UnionDecl:
if bitfieldList != nil {
// This is valid C... but please don't do this.
p.errors = append(p.errors, scanner.Error{
Pos: p.fset.PositionFor(pos, true),
Msg: fmt.Sprintf("bitfield in a union is not supported"),
})
}
if len(fieldList.List) > 1 {
// Insert a special field at the front (of zero width) as a
// marker that this is struct is actually a union. This is done
// by giving the field a name that cannot be expressed directly
// in Go.
// Other parts of the compiler look at the first element in a
// struct (of size > 2) to know whether this is a union.
// Note that we don't have to insert it for single-element
// unions as they're basically equivalent to a struct.
unionMarker := &ast.Field{
Type: &ast.StructType{
Struct: pos,
},
}
unionMarker.Names = []*ast.Ident{
&ast.Ident{
NamePos: pos,
Name: "C union",
Obj: &ast.Object{
Kind: ast.Var,
Name: "C union",
Decl: unionMarker,
},
},
}
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
}
return &ast.StructType{
Struct: pos,
Fields: fieldList,
}, bitfieldList
default:
panic("unknown record declaration")
}
}
//export tinygo_clang_struct_visitor //export tinygo_clang_struct_visitor
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int { func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct { passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList fieldList *ast.FieldList
pkg *cgoPackage pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}) })
fieldList := passed.fieldList fieldList := passed.fieldList
p := passed.pkg p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl { if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor") panic("expected field inside cursor")
} }
name := getString(C.tinygo_clang_getCursorSpelling(c)) name := getString(C.tinygo_clang_getCursorSpelling(c))
if name == "" {
// Assume this is a bitfield of 0 bits.
// Warning: this is not necessarily true!
return C.CXChildVisit_Continue
}
typ := C.tinygo_clang_getCursorType(c) typ := C.tinygo_clang_getCursorType(c)
pos := p.getCursorPosition(c)
field := &ast.Field{ field := &ast.Field{
Type: p.makeASTType(typ, p.getCursorPosition(c)), Type: p.makeASTType(typ, p.getCursorPosition(c)),
} }
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
bitfieldOffset := offsetof % alignOf
if bitfieldOffset != 0 {
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
panic("expected a bitfield")
}
if !*inBitfield {
*bitfieldNum++
}
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
prevField := fieldList.List[len(fieldList.List)-1]
if !*inBitfield {
// The previous element also was a bitfield, but wasn't noticed
// then. Add it now.
*inBitfield = true
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: prevField.Names[0].Name,
startBit: 0,
pos: prevField.Names[0].NamePos,
})
prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
}
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset
*bitfieldList = append(*bitfieldList, bitfieldInfo{
field: prevField,
name: name,
startBit: bitfieldOffset,
pos: pos,
})
return C.CXChildVisit_Continue
}
*inBitfield = false
field.Names = []*ast.Ident{ field.Names = []*ast.Ident{
&ast.Ident{ &ast.Ident{
NamePos: pos, NamePos: p.getCursorPosition(c),
Name: name, Name: name,
Obj: &ast.Object{ Obj: &ast.Object{
Kind: ast.Var, Kind: ast.Var,
@@ -748,16 +622,3 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
fieldList.List = append(fieldList.List, field) fieldList.List = append(fieldList.List, field)
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
//export tinygo_clang_enum_visitor
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
name := getString(C.tinygo_clang_getCursorSpelling(c))
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
}
return C.CXChildVisit_Continue
}
-12
View File
@@ -56,15 +56,3 @@ CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) { CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
return clang_Cursor_getTranslationUnit(c); return clang_Cursor_getTranslationUnit(c);
} }
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
return clang_getEnumConstantDeclValue(c);
}
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
-12
View File
@@ -4,7 +4,6 @@ import (
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
"runtime"
"strings" "strings"
) )
@@ -16,16 +15,6 @@ var commands = map[string][]string{
"wasm-ld": {"wasm-ld-8", "wasm-ld"}, "wasm-ld": {"wasm-ld-8", "wasm-ld"},
} }
func init() {
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
}
}
func execCommand(cmdNames []string, args ...string) error { func execCommand(cmdNames []string, args ...string) error {
for _, cmdName := range cmdNames { for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...) cmd := exec.Command(cmdName, args...)
@@ -37,7 +26,6 @@ func execCommand(cmdNames []string, args ...string) error {
// this command was not found, try the next // this command was not found, try the next
continue continue
} }
return err
} }
return nil return nil
} }
+6 -23
View File
@@ -42,7 +42,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// Fail: this is a nil pointer, exit with a panic. // Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock) c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookupPanic", nil, "") c.createRuntimeCall("lookuppanic", nil, "")
c.builder.CreateUnreachable() c.builder.CreateUnreachable()
// Ok: this is a valid pointer. // Ok: this is a valid pointer.
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
// normal meaning) and for creating a new slice, where 'capacity' means the // normal meaning) and for creating a new slice, where 'capacity' means the
// biggest possible slice capacity, 'low' means len and 'high' means cap. The // biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases. // logic is the same in both cases.
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) { func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
if frame.fn.IsNoBounds() { if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds // The //go:nobounds pragma was added to the function to avoid bounds
// checking. // checking.
@@ -71,9 +71,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() { if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type() capacityType = high.Type()
} }
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = max.Type()
}
if capacityType != capacity.Type() { if capacityType != capacity.Type() {
capacity = c.builder.CreateZExt(capacity, capacityType, "") capacity = c.builder.CreateZExt(capacity, capacityType, "")
} }
@@ -93,13 +90,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
high = c.builder.CreateSExt(high, capacityType, "") high = c.builder.CreateSExt(high, capacityType, "")
} }
} }
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds") faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next") nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
@@ -107,15 +97,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
// Now do the bounds check: low > high || high > capacity // Now do the bounds check: low > high || high > capacity
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax") outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap") outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock) c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic. // Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock) c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "") c.createRuntimeCall("slicepanic", nil, "")
c.builder.CreateUnreachable() c.builder.CreateUnreachable()
// Ok: this is a valid pointer. // Ok: this is a valid pointer.
@@ -126,11 +114,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
// has no effect in well-behaved programs, but makes sure no uncaught nil // has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code. // pointer dereferences exist in valid Go code.
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) { func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
// Check whether we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() {
return
}
// Check whether this is a nil pointer. // Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil") faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next") nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
@@ -158,7 +141,7 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
// Fail: this is a nil pointer, exit with a panic. // Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock) c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilPanic", nil, "") c.createRuntimeCall("nilpanic", nil, "")
c.builder.CreateUnreachable() c.builder.CreateUnreachable()
// Ok: this is a valid pointer. // Ok: this is a valid pointer.
+2 -2
View File
@@ -38,7 +38,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
} }
// Expand an argument type to a list that can be used in a function call // Expand an argument type to a list that can be used in a function call
// parameter list. // paramter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type { func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
@@ -163,7 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam { if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := llvm.ConstNull(t) value := c.getZeroValue(t)
for i, subtyp := range t.StructElementTypes() { for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields) structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining fields = remaining
+28 -181
View File
@@ -4,7 +4,6 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering. // or pseudo-operations that are lowered during goroutine lowering.
import ( import (
"fmt"
"go/types" "go/types"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
@@ -13,63 +12,60 @@ import (
// emitMakeChan returns a new channel value for the given channel type. // emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) { func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.getLLVMType(expr.Type()) chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType.ElementType()) size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false) sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc") ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, chanType, "chan") ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
// Set the elementSize field
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
if elementSize > 0xffff {
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
}
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
c.builder.CreateStore(elementSizeValue, elementSizePtr)
return ptr, nil return ptr, nil
} }
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual // emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering. // channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) { func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
valueType := c.getLLVMType(instr.X.Type())
ch := c.getValue(frame, instr.Chan) ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X) chanValue := c.getValue(frame, instr.X)
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// store value-to-send // store value-to-send
valueType := c.getLLVMType(instr.X.Type()) c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
c.builder.CreateStore(chanValue, valueAlloca) c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
// Do the send. // Do the send.
coroutine := c.createRuntimeCall("getCoroutine", nil, "") c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
// End the lifetime of the alloca. // Make sure CoroSplit includes the alloca in the coroutine frame.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This is a bit dirty, but it works (at least in LLVM 8).
// https://bugs.llvm.org/show_bug.cgi?id=41742 valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
} }
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the // emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering. // actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value { func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem()) valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, unop.X) ch := c.getValue(frame, unop.X)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// Allocate memory to receive into. // Allocate memory to receive into.
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value") c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
c.builder.SetInsertPointBefore(coroutine)
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
// Do the receive. // Do the receive.
coroutine := c.createRuntimeCall("getCoroutine", nil, "") c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received") received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk { if unop.CommaOk {
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide") commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk") commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false)) tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, received, 0, "") tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
@@ -82,157 +78,8 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
// emitChanClose closes the given channel. // emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) { func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
ch := c.getValue(frame, param) ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "") c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
}
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
// Simple selects (blocking and with just one case) are already transformed
// into regular chan operations during SSA construction so we don't have to
// optimize such small selects.
// Go through all the cases. Create the selectStates slice and and
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := c.getValue(frame, state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := c.getValue(frame, state.Send)
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
selectStates = append(selectStates, selectState)
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
c.builder.CreateStore(state, gep)
}
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Convert the 'blocking' flag on this select into a LLVM value.
blockingInt := uint64(0)
if expr.Blocking {
blockingInt = 1
}
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
// Do the select in the runtime.
results := c.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
blockingValue,
}, "")
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
frame.selectRecvBuf[expr] = recvbuf
return results
}
// getChanSelectResult returns the special values from a *ssa.Extract expression
// when extracting a value from a select statement (*ssa.Select). Because
// *ssa.Select cannot load all values in advance, it does this later in the
// *ssa.Extract expression.
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
} }
-128
View File
@@ -1,128 +0,0 @@
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
import (
"errors"
"fmt"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
if t.IsNil() {
panic(t)
}
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return
}
checked[t] = struct{}{}
// check for any context mismatches
switch {
case t.Context() == c.ctx:
// this is correct
case t.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(fmt.Errorf("type %q uses global context", t.String()))
default:
// we used some other context by accident
panic(fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx))
}
// if this is a composite type, check the components of the type
switch t.TypeKind() {
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
// there should only be one of any of these
if s, ok := specials[t.TypeKind()]; !ok {
specials[t.TypeKind()] = t
} else if s != t {
panic(fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s))
}
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
// floating point numbers are primitives - nothing to recurse
case llvm.IntegerTypeKind:
// integers are primitives - nothing to recurse
case llvm.FunctionTypeKind:
// check arguments and return(s)
for _, v := range t.ParamTypes() {
c.checkType(v, checked, specials)
}
c.checkType(t.ReturnType(), checked, specials)
case llvm.StructTypeKind:
// check all elements
for _, v := range t.StructElementTypes() {
c.checkType(v, checked, specials)
}
case llvm.ArrayTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
case llvm.PointerTypeKind:
// check underlying type
c.checkType(t.ElementType(), checked, specials)
case llvm.VectorTypeKind:
// check element type
c.checkType(t.ElementType(), checked, specials)
}
}
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check type
c.checkType(v.Type(), types, specials)
}
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check value properties
c.checkValue(inst, types, specials)
// check operands
for i := 0; i < inst.OperandsCount(); i++ {
c.checkValue(inst.Operand(i), types, specials)
}
}
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check basic block value and type
c.checkValue(bb.AsValue(), types, specials)
// check instructions
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
c.checkInstruction(inst, types, specials)
}
}
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) {
// check function value and type
c.checkValue(fn, types, specials)
// check basic blocks
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
c.checkBasicBlock(bb, types, specials)
}
}
func (c *Compiler) checkModule() {
// check for any context mismatches
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
panic(errors.New("module uses global context"))
default:
// we used some other context by accident
panic(fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
c.checkFunction(fn, types, specials)
}
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
c.checkValue(g, types, specials)
}
}
+215 -268
View File
@@ -3,7 +3,6 @@ package compiler
import ( import (
"errors" "errors"
"fmt" "fmt"
"go/ast"
"go/build" "go/build"
"go/constant" "go/constant"
"go/token" "go/token"
@@ -27,50 +26,22 @@ func init() {
llvm.InitializeAllAsmPrinters() llvm.InitializeAllAsmPrinters()
} }
// The TinyGo import path.
const tinygoPath = "github.com/tinygo-org/tinygo"
// functionsUsedInTransform is a list of function symbols that may be used
// during TinyGo optimization passes so they have to be marked as external
// linkage until all TinyGo passes have finished.
var functionsUsedInTransforms = []string{
"runtime.alloc",
"runtime.free",
"runtime.sleepTask",
"runtime.sleepCurrentTask",
"runtime.setTaskStatePtr",
"runtime.getTaskStatePtr",
"runtime.activateTask",
"runtime.scheduler",
"runtime.startGoroutine",
}
// Configure the compiler. // Configure the compiler.
type Config struct { type Config struct {
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default) Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default) CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
Features []string // LLVM CPU features
GOOS string // GOOS string //
GOARCH string // GOARCH string //
GC string // garbage collection strategy GC string // garbage collection strategy
Scheduler string // scheduler implementation ("coroutines" or "tasks") PanicStrategy string // panic strategy ("abort" or "trap")
PanicStrategy string // panic strategy ("print" or "trap")
CFlags []string // cflags to pass to cgo CFlags []string // cflags to pass to cgo
LDFlags []string // ldflags to pass to cgo LDFlags []string // ldflags to pass to cgo
ClangHeaders string // Clang built-in header include path
DumpSSA bool // dump Go SSA, for compiler debugging DumpSSA bool // dump Go SSA, for compiler debugging
VerifyIR bool // run extra checks on the IR
Debug bool // add debug symbols for gdb Debug bool // add debug symbols for gdb
GOROOT string // GOROOT GOROOT string // GOROOT
TINYGOROOT string // GOROOT for TinyGo TINYGOROOT string // GOROOT for TinyGo
GOPATH string // GOPATH, like `go env GOPATH` GOPATH string // GOPATH, like `go env GOPATH`
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH}) BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
TestConfig TestConfig
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
} }
type Compiler struct { type Compiler struct {
@@ -91,7 +62,6 @@ type Compiler struct {
interfaceInvokeWrappers []interfaceInvokeWrapper interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program ir *ir.Program
diagnostics []error diagnostics []error
astComments map[string]*ast.CommentGroup
} }
type Frame struct { type Frame struct {
@@ -108,7 +78,6 @@ type Frame struct {
deferFuncs map[*ir.Function]int deferFuncs map[*ir.Function]int
deferInvokeFuncs map[string]int deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int deferClosureFuncs map[*ir.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value
} }
type Phi struct { type Phi struct {
@@ -132,11 +101,7 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
features := "" c.machine = target.CreateTargetMachine(config.Triple, config.CPU, "", llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
if len(config.Features) > 0 {
features = strings.Join(config.Features, `,`)
}
c.machine = target.CreateTargetMachine(config.Triple, config.CPU, features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
c.targetData = c.machine.CreateTargetData() c.targetData = c.machine.CreateTargetData()
c.ctx = llvm.NewContext() c.ctx = llvm.NewContext()
@@ -184,21 +149,15 @@ func (c *Compiler) TargetData() llvm.TargetData {
// selectGC picks an appropriate GC strategy if none was provided. // selectGC picks an appropriate GC strategy if none was provided.
func (c *Compiler) selectGC() string { func (c *Compiler) selectGC() string {
if c.GC != "" { gc := c.GC
return c.GC if gc == "" {
gc = "leaking"
} }
return "conservative" return gc
} }
// selectScheduler picks an appropriate scheduler for the target if none was func (c *Compiler) gcIsPrecise() bool {
// given. return c.GC == "precise"
func (c *Compiler) selectScheduler() string {
if c.Scheduler != "" {
// A scheduler was specified in the target description.
return c.Scheduler
}
// Fall back to coroutines, which are supported everywhere.
return "coroutines"
} }
// Compile the given package path or .go file path. Return an error when this // Compile the given package path or .go file path. Return an error when this
@@ -217,7 +176,6 @@ func (c *Compiler) Compile(mainPath string) []error {
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
lprogram := &loader.Program{ lprogram := &loader.Program{
Build: &build.Context{ Build: &build.Context{
GOARCH: c.GOARCH, GOARCH: c.GOARCH,
@@ -227,7 +185,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true, CgoEnabled: true,
UseAllFiles: false, UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers Compiler: "gc", // must be one of the recognized compilers
BuildTags: buildTags, BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
}, },
OverlayBuild: &build.Context{ OverlayBuild: &build.Context{
GOARCH: c.GOARCH, GOARCH: c.GOARCH,
@@ -237,31 +195,24 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true, CgoEnabled: true,
UseAllFiles: false, UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers Compiler: "gc", // must be one of the recognized compilers
BuildTags: buildTags, BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
}, },
OverlayPath: func(path string) string { ShouldOverlay: func(path string) bool {
// Return the (overlay) import path when it should be overlaid, and
// "" if it should not.
if strings.HasPrefix(path, tinygoPath+"/src/") {
// Avoid issues with packages that are imported twice, one from
// GOPATH and one from TINYGOPATH.
path = path[len(tinygoPath+"/src/"):]
}
switch path { switch path {
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing": case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
return path return true
default: default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") { if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return path return true
} else if path == "syscall" { } else if path == "syscall" {
for _, tag := range c.BuildTags { for _, tag := range c.BuildTags {
if tag == "baremetal" || tag == "darwin" { if tag == "avr" || tag == "cortexm" || tag == "darwin" {
return path return true
} }
} }
} }
} }
return "" return false
}, },
TypeChecker: types.Config{ TypeChecker: types.Config{
Sizes: &StdSizes{ Sizes: &StdSizes{
@@ -270,12 +221,10 @@ func (c *Compiler) Compile(mainPath string) []error {
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)), MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
}, },
}, },
Dir: wd, Dir: wd,
TINYGOROOT: c.TINYGOROOT, TINYGOROOT: c.TINYGOROOT,
CFlags: c.CFlags, CFlags: c.CFlags,
ClangHeaders: c.ClangHeaders,
} }
if strings.HasSuffix(mainPath, ".go") { if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath) _, err = lprogram.ImportFile(mainPath)
if err != nil { if err != nil {
@@ -287,13 +236,12 @@ func (c *Compiler) Compile(mainPath string) []error {
return []error{err} return []error{err}
} }
} }
_, err = lprogram.Import("runtime", "") _, err = lprogram.Import("runtime", "")
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
err = lprogram.Parse(c.TestConfig.CompileTestBinary) err = lprogram.Parse()
if err != nil { if err != nil {
return []error{err} return []error{err}
} }
@@ -316,7 +264,39 @@ func (c *Compiler) Compile(mainPath string) []error {
var frames []*Frame var frames []*Frame
c.loadASTComments(lprogram) // Declare all named struct types.
for _, t := range c.ir.NamedTypes {
if named, ok := t.Type.Type().(*types.Named); ok {
if _, ok := named.Underlying().(*types.Struct); ok {
t.LLVMType = c.ctx.StructCreateNamed(named.Obj().Pkg().Path() + "." + named.Obj().Name())
}
}
}
// Define all named struct types.
for _, t := range c.ir.NamedTypes {
if named, ok := t.Type.Type().(*types.Named); ok {
if st, ok := named.Underlying().(*types.Struct); ok {
llvmType := c.getLLVMType(st)
t.LLVMType.StructSetBody(llvmType.StructElementTypes(), false)
}
}
}
// Declare all globals.
for _, g := range c.ir.Globals {
typ := g.Type().(*types.Pointer).Elem()
llvmType := c.getLLVMType(typ)
global := c.mod.NamedGlobal(g.LinkName())
if global.IsNil() {
global = llvm.AddGlobal(c.mod, llvmType, g.LinkName())
}
g.LLVMGlobal = global
if !g.IsExtern() {
global.SetLinkage(llvm.InternalLinkage)
global.SetInitializer(c.getZeroValue(llvmType))
}
}
// Declare all functions. // Declare all functions.
for _, f := range c.ir.Functions { for _, f := range c.ir.Functions {
@@ -364,15 +344,13 @@ func (c *Compiler) Compile(mainPath string) []error {
// would be optimized away. // would be optimized away.
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main") realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
// Make sure these functions are kept in tact during TinyGo transformation passes. c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
for _, name := range functionsUsedInTransforms { c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
fn := c.mod.NamedFunction(name) c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
if fn.IsNil() { c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
continue c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
} c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
fn.SetLinkage(llvm.ExternalLinkage)
}
// Load some attributes // Load some attributes
getAttr := func(attrName string) llvm.Attribute { getAttr := func(attrName string) llvm.Attribute {
@@ -392,15 +370,6 @@ func (c *Compiler) Compile(mainPath string) []error {
// See emitNilCheck in asserts.go. // See emitNilCheck in asserts.go.
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture) c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc.go). Indicate to the optimizer that the only thing
// we'll do is read the pointer.
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if !trackPointer.IsNil() {
trackPointer.AddAttributeAtIndex(1, nocapture)
trackPointer.AddAttributeAtIndex(1, readonly)
}
// Memory copy operations do not capture pointers, even though some weird // Memory copy operations do not capture pointers, even though some weird
// pointer arithmetic is happening in the Go implementation. // pointer arithmetic is happening in the Go implementation.
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} { for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
@@ -433,22 +402,6 @@ func (c *Compiler) Compile(mainPath string) []error {
return c.diagnostics return c.diagnostics
} }
// getRuntimeType obtains a named type from the runtime package and returns it
// as a Go type.
func (c *Compiler) getRuntimeType(name string) types.Type {
return c.ir.Program.ImportedPackage("runtime").Type(name).Type()
}
// getLLVMRuntimeType obtains a named type from the runtime package and returns
// it as a LLVM type, creating it if necessary. It is a shorthand for
// getLLVMType(getRuntimeType(name)).
func (c *Compiler) getLLVMRuntimeType(name string) llvm.Type {
return c.getLLVMType(c.getRuntimeType(name))
}
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
// named struct types (or Go types implemented as named LLVM structs such as
// strings) it also creates it first if necessary.
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type { func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
switch typ := goType.(type) { switch typ := goType.(type) {
case *types.Array: case *types.Array:
@@ -477,7 +430,7 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
case types.Complex128: case types.Complex128:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false) return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString: case types.String, types.UntypedString:
return c.getLLVMRuntimeType("_string") return c.mod.GetTypeByName("runtime._string")
case types.Uintptr: case types.Uintptr:
return c.uintptrType return c.uintptrType
case types.UnsafePointer: case types.UnsafePointer:
@@ -486,23 +439,16 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
panic("unknown basic type: " + typ.String()) panic("unknown basic type: " + typ.String())
} }
case *types.Chan: case *types.Chan:
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0) return llvm.PointerType(c.mod.GetTypeByName("runtime.channel"), 0)
case *types.Interface: case *types.Interface:
return c.getLLVMRuntimeType("_interface") return c.mod.GetTypeByName("runtime._interface")
case *types.Map: case *types.Map:
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0) return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0)
case *types.Named: case *types.Named:
if st, ok := typ.Underlying().(*types.Struct); ok { if _, ok := typ.Underlying().(*types.Struct); ok {
// Structs are a special case. While other named types are ignored llvmType := c.mod.GetTypeByName(typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
// in LLVM IR, named structs are implemented as named structs in
// LLVM. This is because it is otherwise impossible to create
// self-referencing types such as linked lists.
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
llvmType := c.mod.GetTypeByName(llvmName)
if llvmType.IsNil() { if llvmType.IsNil() {
llvmType = c.ctx.StructCreateNamed(llvmName) panic("underlying type not found: " + typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
} }
return llvmType return llvmType
} }
@@ -564,6 +510,42 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
} }
} }
// Return a zero LLVM value for any LLVM type. Setting this value as an
// initializer has the same effect as setting 'zeroinitializer' on a value.
// Sadly, I haven't found a way to do it directly with the Go API but this works
// just fine.
func (c *Compiler) getZeroValue(typ llvm.Type) llvm.Value {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
subTyp := typ.ElementType()
subVal := c.getZeroValue(subTyp)
vals := make([]llvm.Value, typ.ArrayLength())
for i := range vals {
vals[i] = subVal
}
return llvm.ConstArray(subTyp, vals)
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
return llvm.ConstFloat(typ, 0.0)
case llvm.IntegerTypeKind:
return llvm.ConstInt(typ, 0, false)
case llvm.PointerTypeKind:
return llvm.ConstPointerNull(typ)
case llvm.StructTypeKind:
types := typ.StructElementTypes()
vals := make([]llvm.Value, len(types))
for i, subTyp := range types {
vals[i] = c.getZeroValue(subTyp)
}
if typ.StructName() != "" {
return llvm.ConstNamedStruct(typ, vals)
} else {
return c.ctx.ConstStruct(vals, false)
}
default:
panic("unknown LLVM zero inititializer: " + typ.String())
}
}
// Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer. // Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer.
func isPointer(typ types.Type) bool { func isPointer(typ types.Type) bool {
if _, ok := typ.(*types.Pointer); ok { if _, ok := typ.(*types.Pointer); ok {
@@ -786,11 +768,6 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame {
// External/exported functions may not retain pointer values. // External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers // https://golang.org/cmd/cgo/#hdr-Passing_pointers
if f.IsExported() { if f.IsExported() {
// Set the wasm-import-module attribute if the function's module is set.
if f.Module() != "" {
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
}
nocaptureKind := llvm.AttributeKindID("nocapture") nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes { for i, typ := range paramTypes {
@@ -847,10 +824,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
if c.DumpSSA { if c.DumpSSA {
fmt.Printf("\nfunc %s:\n", frame.fn.Function) fmt.Printf("\nfunc %s:\n", frame.fn.Function)
} }
if !frame.fn.LLVMFn.IsDeclaration() {
c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name())
return
}
if !frame.fn.IsExported() { if !frame.fn.IsExported() {
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage) frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
frame.fn.LLVMFn.SetUnnamedAddr(true) frame.fn.LLVMFn.SetUnnamedAddr(true)
@@ -865,10 +838,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
// Add LLVM inline hint to functions with //go:inline pragma. // Add LLVM inline hint to functions with //go:inline pragma.
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0) inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
frame.fn.LLVMFn.AddFunctionAttr(inline) frame.fn.LLVMFn.AddFunctionAttr(inline)
case ir.InlineNone:
// Add LLVM attribute to always avoid inlining this function.
noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
frame.fn.LLVMFn.AddFunctionAttr(noinline)
} }
// Add debug info, if needed. // Add debug info, if needed.
@@ -1021,56 +990,42 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type())) frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
} else { } else {
frame.locals[instr] = value frame.locals[instr] = value
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
c.trackExpr(frame, instr, value)
}
} }
case *ssa.DebugRef: case *ssa.DebugRef:
// ignore // ignore
case *ssa.Defer: case *ssa.Defer:
c.emitDefer(frame, instr) c.emitDefer(frame, instr)
case *ssa.Go: case *ssa.Go:
if instr.Call.IsInvoke() {
c.addError(instr.Pos(), "todo: go on method receiver")
return
}
callee := instr.Call.StaticCallee()
if callee == nil {
c.addError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
return
}
calleeFn := c.ir.GetFunction(callee)
// Mark this function as a 'go' invocation and break invalid
// interprocedural optimizations. For example, heap-to-stack
// transformations are not sound as goroutines can outlive their parent.
calleeType := calleeFn.LLVMFn.Type()
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "")
// Get all function parameters to pass to the goroutine. // Get all function parameters to pass to the goroutine.
var params []llvm.Value var params []llvm.Value
for _, param := range instr.Call.Args { for _, param := range instr.Call.Args {
params = append(params, c.getValue(frame, param)) params = append(params, c.getValue(frame, param))
} }
if !calleeFn.IsExported() {
// Start a new goroutine. params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
if callee := instr.Call.StaticCallee(); callee != nil { params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
// Static callee is known. This makes it easier to start a new
// goroutine.
calleeFn := c.ir.GetFunction(callee)
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
// For coroutine scheduling, this is only required when calling
// an external function.
// For tasks, because all params are stored in a single object,
// no unnecessary parameters should be stored anyway.
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
}
c.emitStartGoroutine(calleeFn.LLVMFn, params)
} else if !instr.Call.IsInvoke() {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The parent handle (for coroutines) or the function pointer
// itself (for tasks).
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
params = append(params, context) // context parameter
switch c.selectScheduler() {
case "coroutines":
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
case "tasks":
params = append(params, funcPtr)
default:
panic("unknown scheduler type")
}
c.emitStartGoroutine(funcPtr, params)
} else {
c.addError(instr.Pos(), "todo: go on interface call")
} }
c.createCall(calleeValue, params, "")
case *ssa.If: case *ssa.If:
cond := c.getValue(frame, instr.Cond) cond := c.getValue(frame, instr.Cond)
block := instr.Block() block := instr.Block()
@@ -1097,7 +1052,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
c.builder.CreateRet(c.getValue(frame, instr.Results[0])) c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
} else { } else {
// Multiple return values. Put them all in a struct. // Multiple return values. Put them all in a struct.
retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType()) retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
for i, result := range instr.Results { for i, result := range instr.Results {
val := c.getValue(frame, result) val := c.getValue(frame, result)
retVal = c.builder.CreateInsertValue(retVal, val, i, "") retVal = c.builder.CreateInsertValue(retVal, val, i, "")
@@ -1116,7 +1071,12 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// nothing to store // nothing to store
return return
} }
c.builder.CreateStore(llvmVal, llvmAddr) store := c.builder.CreateStore(llvmVal, llvmAddr)
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem()
if c.ir.IsVolatile(valType) {
// Volatile store, for memory-mapped registers.
store.SetVolatile(true)
}
default: default:
c.addError(instr.Pos(), "unknown instruction: "+instr.String()) c.addError(instr.Pos(), "unknown instruction: "+instr.String())
} }
@@ -1319,11 +1279,11 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
if fn := instr.StaticCallee(); fn != nil { if fn := instr.StaticCallee(); fn != nil {
name := fn.RelString(nil) name := fn.RelString(nil)
switch { switch {
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister": case name == "device/arm.ReadRegister":
return c.emitReadRegister(name, instr.Args) return c.emitReadRegister(instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm": case name == "device/arm.Asm" || name == "device/avr.Asm":
return c.emitAsm(instr.Args) return c.emitAsm(instr.Args)
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull": case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
return c.emitAsmFull(frame, instr) return c.emitAsmFull(frame, instr)
case strings.HasPrefix(name, "device/arm.SVCall"): case strings.HasPrefix(name, "device/arm.SVCall"):
return c.emitSVCall(frame, instr.Args) return c.emitSVCall(frame, instr.Args)
@@ -1363,7 +1323,10 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
value := c.getValue(frame, instr.Value) value := c.getValue(frame, instr.Value)
// This is a func value, which cannot be called directly. We have to // This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value. // extract the function pointer and context first from the func value.
funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
if err != nil {
return llvm.Value{}, err
}
c.emitNilCheck(frame, funcPtr, "fpcall") c.emitNilCheck(frame, funcPtr, "fpcall")
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
} }
@@ -1383,9 +1346,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value {
} }
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature) return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
case *ssa.Global: case *ssa.Global:
value := c.getGlobal(expr) value := c.ir.GetGlobal(expr).LLVMGlobal
if value.IsNil() { if value.IsNil() {
c.addError(expr.Pos(), "global not found: "+expr.RelString(nil)) c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
return llvm.Undef(c.getLLVMType(expr.Type())) return llvm.Undef(c.getLLVMType(expr.Type()))
} }
return value return value
@@ -1410,6 +1373,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
switch expr := expr.(type) { switch expr := expr.(type) {
case *ssa.Alloc: case *ssa.Alloc:
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem()) typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
var buf llvm.Value
if expr.Heap { if expr.Heap {
size := c.targetData.TypeAllocSize(typ) size := c.targetData.TypeAllocSize(typ)
// Calculate ^uintptr(0) // Calculate ^uintptr(0)
@@ -1418,17 +1382,17 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// Size would be truncated if truncated to uintptr. // Size would be truncated if truncated to uintptr.
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size)) return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
} }
// TODO: escape analysis
sizeValue := llvm.ConstInt(c.uintptrType, size, false) sizeValue := llvm.ConstInt(c.uintptrType, size, false)
buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment) buf = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "") buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
return buf, nil
} else { } else {
buf := c.createEntryBlockAlloca(typ, expr.Comment) buf = c.builder.CreateAlloca(typ, expr.Comment)
if c.targetData.TypeAllocSize(typ) != 0 { if c.targetData.TypeAllocSize(typ) != 0 {
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
} }
return buf, nil
} }
return buf, nil
case *ssa.BinOp: case *ssa.BinOp:
x := c.getValue(frame, expr.X) x := c.getValue(frame, expr.X)
y := c.getValue(frame, expr.Y) y := c.getValue(frame, expr.Y)
@@ -1481,11 +1445,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
x := c.getValue(frame, expr.X) x := c.getValue(frame, expr.X)
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos()) return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
case *ssa.Extract: case *ssa.Extract:
if _, ok := expr.Tuple.(*ssa.Select); ok {
return c.getChanSelectResult(frame, expr), nil
}
value := c.getValue(frame, expr.Tuple) value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, ""), nil result := c.builder.CreateExtractValue(value, expr.Index, "")
return result, nil
case *ssa.Field: case *ssa.Field:
value := c.getValue(frame, expr.X) value := c.getValue(frame, expr.X)
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" { if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
@@ -1493,12 +1455,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// This could be done directly, but as this is a very infrequent // This could be done directly, but as this is a very infrequent
// operation it's much easier to bitcast it through an alloca. // operation it's much easier to bitcast it through an alloca.
resultType := c.getLLVMType(expr.Type()) resultType := c.getLLVMType(expr.Type())
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(value.Type(), "union.alloca") alloca := c.builder.CreateAlloca(value.Type(), "")
c.builder.CreateStore(value, alloca) c.builder.CreateStore(value, alloca)
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast") bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
result := c.builder.CreateLoad(bitcast, "union.result") return c.builder.CreateLoad(bitcast, ""), nil
c.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil
} }
result := c.builder.CreateExtractValue(value, expr.Field, "") result := c.builder.CreateExtractValue(value, expr.Field, "")
return result, nil return result, nil
@@ -1538,13 +1498,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
// Can't load directly from array (as index is non-constant), so have to // Can't load directly from array (as index is non-constant), so have to
// do it using an alloca+gep+load. // do it using an alloca+gep+load.
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(array.Type(), "index.alloca") alloca := c.builder.CreateAlloca(array.Type(), "index.alloca")
c.builder.CreateStore(array, alloca) c.builder.CreateStore(array, alloca)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep") ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep")
result := c.builder.CreateLoad(ptr, "index.load") return c.builder.CreateLoad(ptr, "index.load"), nil
c.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil
case *ssa.IndexAddr: case *ssa.IndexAddr:
val := c.getValue(frame, expr.X) val := c.getValue(frame, expr.X)
index := c.getValue(frame, expr.Index) index := c.getValue(frame, expr.Index)
@@ -1661,11 +1619,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
} }
// Bounds checking. // Bounds checking.
lenType := expr.Len.Type().(*types.Basic) c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
capType := expr.Cap.Type().(*types.Basic)
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
// Allocate the backing array. // Allocate the backing array.
// TODO: escape analysis
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos()) sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
if err != nil { if err != nil {
return llvm.Value{}, err return llvm.Value{}, err
@@ -1705,16 +1662,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key()) llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key())
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem()) llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key") mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key")
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value") mapKeyPtr := c.builder.CreateBitCast(mapKeyAlloca, c.i8ptrType, "range.keyptr")
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "range.value")
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "range.valueptr")
ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next") ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
tuple = c.builder.CreateInsertValue(tuple, ok, 0, "") tuple = c.builder.CreateInsertValue(tuple, ok, 0, "")
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "") tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "") tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
return tuple, nil return tuple, nil
} }
case *ssa.Phi: case *ssa.Phi:
@@ -1725,22 +1682,43 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
var iteratorType llvm.Type var iteratorType llvm.Type
switch typ := expr.X.Type().Underlying().(type) { switch typ := expr.X.Type().Underlying().(type) {
case *types.Basic: // string case *types.Basic: // string
iteratorType = c.getLLVMRuntimeType("stringIterator") iteratorType = c.mod.GetTypeByName("runtime.stringIterator")
case *types.Map: case *types.Map:
iteratorType = c.getLLVMRuntimeType("hashmapIterator") iteratorType = c.mod.GetTypeByName("runtime.hashmapIterator")
default: default:
panic("unknown type in range: " + typ.String()) panic("unknown type in range: " + typ.String())
} }
it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it") it := c.builder.CreateAlloca(iteratorType, "range.it")
c.builder.CreateStore(llvm.ConstNull(iteratorType), it) c.builder.CreateStore(c.getZeroValue(iteratorType), it)
return it, nil return it, nil
case *ssa.Select: case *ssa.Select:
return c.emitSelect(frame, expr), nil if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
c.createRuntimeCall("deadlockStub", nil, "")
return llvm.Undef(llvmType), nil
} else {
// No-op:
// select {
// default:
// }
retval := llvm.Undef(llvmType)
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval, nil // {-1, false}
}
}
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
case *ssa.Slice: case *ssa.Slice:
if expr.Max != nil {
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
}
value := c.getValue(frame, expr.X) value := c.getValue(frame, expr.X)
var lowType, highType, maxType *types.Basic var lowType, highType *types.Basic
var low, high, max llvm.Value var low, high llvm.Value
if expr.Low != nil { if expr.Low != nil {
lowType = expr.Low.Type().Underlying().(*types.Basic) lowType = expr.Low.Type().Underlying().(*types.Basic)
@@ -1771,20 +1749,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
highType = types.Typ[types.Uintptr] highType = types.Typ[types.Uintptr]
} }
if expr.Max != nil {
maxType = expr.Max.Type().Underlying().(*types.Basic)
max = c.getValue(frame, expr.Max)
if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, c.uintptrType, "")
} else {
max = c.builder.CreateSExt(max, c.uintptrType, "")
}
}
} else {
maxType = types.Typ[types.Uintptr]
}
switch typ := expr.X.Type().Underlying().(type) { switch typ := expr.X.Type().Underlying().(type) {
case *types.Pointer: // pointer to array case *types.Pointer: // pointer to array
// slice an array // slice an array
@@ -1793,31 +1757,25 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() { if high.IsNil() {
high = llvmLen high = llvmLen
} }
if max.IsNil() {
max = llvmLen
}
indices := []llvm.Value{ indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false),
low, low,
} }
c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType) c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds // Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe. // check so it's safe.
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
}
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "") high = c.builder.CreateTrunc(high, c.uintptrType, "")
} }
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
max = c.builder.CreateTrunc(max, c.uintptrType, "") low = c.builder.CreateTrunc(low, c.uintptrType, "")
} }
sliceLen := c.builder.CreateSub(high, low, "slice.len") sliceLen := c.builder.CreateSub(high, low, "slice.len")
slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr") slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr")
sliceCap := c.builder.CreateSub(max, low, "slice.cap") sliceCap := c.builder.CreateSub(llvmLen, low, "slice.cap")
slice := c.ctx.ConstStruct([]llvm.Value{ slice := c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(slicePtr.Type()), llvm.Undef(slicePtr.Type()),
@@ -1837,11 +1795,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() { if high.IsNil() {
high = oldLen high = oldLen
} }
if max.IsNil() {
max = oldCap
}
c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType) c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds // Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe. // check so it's safe.
@@ -1851,13 +1806,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
high = c.builder.CreateTrunc(high, c.uintptrType, "") high = c.builder.CreateTrunc(high, c.uintptrType, "")
} }
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
max = c.builder.CreateTrunc(max, c.uintptrType, "")
}
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
newLen := c.builder.CreateSub(high, low, "") newLen := c.builder.CreateSub(high, low, "")
newCap := c.builder.CreateSub(max, low, "") newCap := c.builder.CreateSub(oldCap, low, "")
slice := c.ctx.ConstStruct([]llvm.Value{ slice := c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(newPtr.Type()), llvm.Undef(newPtr.Type()),
llvm.Undef(c.uintptrType), llvm.Undef(c.uintptrType),
@@ -1873,18 +1825,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String()) return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String())
} }
// slice a string // slice a string
if expr.Max != nil {
// This might as well be a panic, as the frontend should have
// handled this already.
return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec")
}
oldPtr := c.builder.CreateExtractValue(value, 0, "") oldPtr := c.builder.CreateExtractValue(value, 0, "")
oldLen := c.builder.CreateExtractValue(value, 1, "") oldLen := c.builder.CreateExtractValue(value, 1, "")
if high.IsNil() { if high.IsNil() {
high = oldLen high = oldLen
} }
c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType) c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
// Truncate ints bigger than uintptr. This is after the bounds // Truncate ints bigger than uintptr. This is after the bounds
// check so it's safe. // check so it's safe.
@@ -1897,7 +1844,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "") newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
newLen := c.builder.CreateSub(high, low, "") newLen := c.builder.CreateSub(high, low, "")
str := llvm.Undef(c.getLLVMRuntimeType("_string")) str := llvm.Undef(c.mod.GetTypeByName("runtime._string"))
str = c.builder.CreateInsertValue(str, newPtr, 0, "") str = c.builder.CreateInsertValue(str, newPtr, 0, "")
str = c.builder.CreateInsertValue(str, newLen, 1, "") str = c.builder.CreateInsertValue(str, newLen, 1, "")
return str, nil return str, nil
@@ -2276,7 +2223,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "") strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen}) strObj := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._string"), []llvm.Value{strPtr, strLen})
return strObj return strObj
} else if typ.Kind() == types.UnsafePointer { } else if typ.Kind() == types.UnsafePointer {
if !expr.IsNil() { if !expr.IsNil() {
@@ -2314,12 +2261,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
if expr.Value != nil { if expr.Value != nil {
panic("expected nil chan constant") panic("expected nil chan constant")
} }
return llvm.ConstNull(c.getLLVMType(expr.Type())) return c.getZeroValue(c.getLLVMType(expr.Type()))
case *types.Signature: case *types.Signature:
if expr.Value != nil { if expr.Value != nil {
panic("expected nil signature constant") panic("expected nil signature constant")
} }
return llvm.ConstNull(c.getLLVMType(expr.Type())) return c.getZeroValue(c.getLLVMType(expr.Type()))
case *types.Interface: case *types.Interface:
if expr.Value != nil { if expr.Value != nil {
panic("expected nil interface constant") panic("expected nil interface constant")
@@ -2329,7 +2276,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
llvm.ConstInt(c.uintptrType, 0, false), llvm.ConstInt(c.uintptrType, 0, false),
llvm.ConstPointerNull(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType),
} }
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields) return llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
case *types.Pointer: case *types.Pointer:
if expr.Value != nil { if expr.Value != nil {
panic("expected nil pointer constant") panic("expected nil pointer constant")
@@ -2354,7 +2301,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
panic("non-nil map constant") panic("non-nil map constant")
} }
llvmType := c.getLLVMType(typ) llvmType := c.getLLVMType(typ)
return llvm.ConstNull(llvmType) return c.getZeroValue(llvmType)
default: default:
panic("unknown constant: " + expr.String()) panic("unknown constant: " + expr.String())
} }
@@ -2426,8 +2373,6 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
switch typeFrom.Elem().(*types.Basic).Kind() { switch typeFrom.Elem().(*types.Basic).Kind() {
case types.Byte: case types.Byte:
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil
default: default:
return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String()) return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String())
} }
@@ -2514,10 +2459,8 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
switch elemType.Kind() { switch elemType.Kind() {
case types.Byte: case types.Byte:
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil
default: default:
panic("unexpected type in string to slice conversion") return llvm.Value{}, c.makeError(pos, "todo: convert from string: "+elemType.String())
} }
default: default:
@@ -2543,18 +2486,18 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String()) return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String())
} }
case token.MUL: // *x, dereference pointer case token.MUL: // *x, dereference pointer
unop.X.Type().Underlying().(*types.Pointer).Elem() valType := unop.X.Type().Underlying().(*types.Pointer).Elem()
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 { if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
// zero-length data // zero-length data
return llvm.ConstNull(x.Type().ElementType()), nil return c.getZeroValue(x.Type().ElementType()), nil
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") { } else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
// CGo function pointer. The cgo part has rewritten CGo function // CGo function pointer. The cgo part has rewritten CGo function
// pointers as stub global variables of the form: // pointers as stub global variables of the form:
// var C.add unsafe.Pointer // var C.add unsafe.Pointer
// Instead of a load from the global, create a bitcast of the // Instead of a load from the global, create a bitcast of the
// function pointer itself. // function pointer itself.
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName global := c.ir.GetGlobal(unop.X.(*ssa.Global))
name := globalName[:len(globalName)-len("$funcaddr")] name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
fn := c.mod.NamedFunction(name) fn := c.mod.NamedFunction(name)
if fn.IsNil() { if fn.IsNil() {
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name) return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
@@ -2563,6 +2506,10 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
} else { } else {
c.emitNilCheck(frame, x, "deref") c.emitNilCheck(frame, x, "deref")
load := c.builder.CreateLoad(x, "") load := c.builder.CreateLoad(x, "")
if c.ir.IsVolatile(valType) {
// Volatile load, for memory-mapped registers.
load.SetVolatile(true)
}
return load, nil return load, nil
} }
case token.XOR: // ^x, toggle all bits in integer case token.XOR: // ^x, toggle all bits in integer
@@ -2713,7 +2660,7 @@ func (c *Compiler) ExternalInt64AsPtr() error {
// correct calling convention. // correct calling convention.
fn.SetLinkage(llvm.InternalLinkage) fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true) fn.SetUnnamedAddr(true)
entryBlock := c.ctx.AddBasicBlock(externalFn, "entry") entryBlock := llvm.AddBasicBlock(externalFn, "entry")
c.builder.SetInsertPointAtEnd(entryBlock) c.builder.SetInsertPointAtEnd(entryBlock)
var callParams []llvm.Value var callParams []llvm.Value
if fnType.ReturnType() == int64Type { if fnType.ReturnType() == int64Type {
+10 -13
View File
@@ -29,7 +29,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
frame.deferClosureFuncs = make(map[*ir.Function]int) frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer. // Create defer list pointer.
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0) deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr") frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr) c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
} }
@@ -122,7 +122,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// Make a struct out of the collected values to put in the defer frame. // Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false) deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType) deferFrame := c.getZeroValue(deferFrameType)
for i, value := range values { for i, value := range values {
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "") deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
} }
@@ -130,9 +130,6 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
// Put this struct in an alloca. // Put this struct in an alloca.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca") alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca) c.builder.CreateStore(deferFrame, alloca)
if c.needsStackObjects() {
c.trackPointer(alloca)
}
// Push it on top of the linked list by replacing deferPtr. // Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast") allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
@@ -157,10 +154,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// } // }
// Create loop. // Create loop.
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead") loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop") loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default") unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end") end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead) c.builder.CreateBr(loophead)
// Create loop head: // Create loop head:
@@ -192,7 +189,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Create switch case, for example: // Create switch case, for example:
// case 0: // case 0:
// // run first deferred call // // run first deferred call
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback") block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block) sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block) c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) { switch callback := callback.(type) {
@@ -203,7 +200,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
} }
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType} valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
for _, arg := range callback.Args { for _, arg := range callback.Args {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type())) valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
} }
@@ -234,7 +231,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
for _, param := range callback.Params { for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type())) valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
} }
@@ -263,7 +260,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function)) fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)} valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
params := fn.Signature.Params() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type())) valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
+82 -98
View File
@@ -51,7 +51,7 @@ func (c *Compiler) LowerFuncValues() {
} }
// Find all func values used in the program with their signatures. // Find all func values used in the program with their signatures.
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0) funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{} signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr { if global.Type() != funcValueWithSignaturePtr {
@@ -152,19 +152,19 @@ func (c *Compiler) LowerFuncValues() {
// There are multiple functions used in a func value that // There are multiple functions used in a func value that
// implement this signature. // implement this signature.
// What we'll do is transform the following: // What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(func.ptr) // rawPtr := runtime.getFuncPtr(fn)
// if rawPtr == nil { // if func.rawPtr == nil {
// runtime.nilPanic() // runtime.nilpanic()
// } // }
// result := rawPtr(...args, func.context) // result := func.rawPtr(...args, func.context)
// into this: // into this:
// if false { // if false {
// runtime.nilPanic() // runtime.nilpanic()
// } // }
// var result // Phi // var result // Phi
// switch fn.id { // switch fn.id {
// case 0: // case 0:
// runtime.nilPanic() // runtime.nilpanic()
// case 1: // case 1:
// result = call first implementation... // result = call first implementation...
// case 2: // case 2:
@@ -175,111 +175,95 @@ func (c *Compiler) LowerFuncValues() {
// Remove some casts, checks, and the old call which we're going // Remove some casts, checks, and the old call which we're going
// to replace. // to replace.
for _, callIntPtr := range getUses(getFuncPtrCall) { var funcCall llvm.Value
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" { for _, inttoptr := range getUses(getFuncPtrCall) {
for _, inttoptr := range getUses(callIntPtr) { if inttoptr.IsAIntToPtrInst().IsNil() {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected a inttoptr")
}
for _, use := range getUses(inttoptr) {
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
use.EraseFromParentAsInstruction()
}
inttoptr.EraseFromParentAsInstruction()
}
callIntPtr.EraseFromParentAsInstruction()
continue
}
if callIntPtr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr") panic("expected inttoptr")
} }
for _, ptrUse := range getUses(callIntPtr) { for _, ptrUse := range getUses(inttoptr) {
if !ptrUse.IsABitCastInst().IsNil() { if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) { for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() { if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
panic("expected a call instruction")
}
switch bitcastUse.CalledValue().Name() {
case "runtime.isnil":
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
default:
panic("expected a call to runtime.isnil") panic("expected a call to runtime.isnil")
} }
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
bitcastUse.EraseFromParentAsInstruction()
} }
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr { ptrUse.EraseFromParentAsInstruction()
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value { } else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
return c.builder.CreateCall(funcPtr, params, "") if !funcCall.IsNil() {
}, functions) panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else { } else {
panic("unexpected getFuncPtrCall") panic("unexpected getFuncPtrCall")
} }
ptrUse.EraseFromParentAsInstruction()
} }
callIntPtr.EraseFromParentAsInstruction() }
if funcCall.IsNil() {
panic("expected exactly one call use of a runtime.getFuncPtr")
}
// The block that cannot be reached with correct funcValues (to
// help the optimizer).
c.builder.SetInsertPointBefore(funcCall)
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(funcCall)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few
// basic blocks in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilpanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to
// make.
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
for i := range callParams {
callParams[i] = funcCall.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(funcCall.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
funcCall.ReplaceAllUsesWith(phi)
}
// Finally, remove the old instructions.
funcCall.EraseFromParentAsInstruction()
for _, inttoptr := range getUses(getFuncPtrCall) {
inttoptr.EraseFromParentAsInstruction()
} }
getFuncPtrCall.EraseFromParentAsInstruction() getFuncPtrCall.EraseFromParentAsInstruction()
} }
} }
} }
} }
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
// to the newly created direct calls. The funcID is the number to switch on,
// call is the call instruction to replace, and createCall is the callback that
// actually creates the new call. By changing createCall to something other than
// c.builder.CreateCall, instead of calling a function it can start a new
// goroutine for example.
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
// The block that cannot be reached with correct funcValues (to help the
// optimizer).
c.builder.SetInsertPointBefore(call)
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
c.builder.SetInsertPointAtEnd(defaultBlock)
c.builder.CreateUnreachable()
// Create the switch.
c.builder.SetInsertPointBefore(call)
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
// Split right after the switch. We will need to insert a few basic blocks
// in this gap.
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
// The 0 case, which is actually a nil check.
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
c.builder.SetInsertPointAtEnd(nilBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.builder.CreateUnreachable()
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
// Gather the list of parameters for every call we're going to make.
callParams := make([]llvm.Value, call.OperandsCount()-1)
for i := range callParams {
callParams[i] = call.Operand(i)
}
// If the call produces a value, we need to get it using a PHI
// node.
phiBlocks := make([]llvm.BasicBlock, len(functions))
phiValues := make([]llvm.Value, len(functions))
for i, fn := range functions {
// Insert a switch case.
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
c.builder.SetInsertPointAtEnd(bb)
result := createCall(fn.funcPtr, callParams)
c.builder.CreateBr(nextBlock)
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
phiBlocks[i] = bb
phiValues[i] = result
}
// Create the PHI node so that the call result flows into the
// next block (after the split). This is only necessary when the
// call produced a value.
if call.Type().TypeKind() != llvm.VoidTypeKind {
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
phi := c.builder.CreatePHI(call.Type(), "")
phi.AddIncoming(phiValues, phiBlocks)
call.ReplaceAllUsesWith(phi)
}
}
+23 -12
View File
@@ -32,15 +32,10 @@ const (
// funcImplementation picks an appropriate func value implementation for the // funcImplementation picks an appropriate func value implementation for the
// target. // target.
func (c *Compiler) funcImplementation() funcValueImplementation { func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking if c.GOARCH == "wasm" {
// inside a function that is used as a func value.
switch c.selectScheduler() {
case "coroutines":
return funcValueSwitch return funcValueSwitch
case "tasks": } else {
return funcValueDoubleword return funcValueDoubleword
default:
panic("unknown scheduler type")
} }
} }
@@ -53,11 +48,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
// Closure is: {context, function pointer} // Closure is: {context, function pointer}
funcValueScalar = funcPtr funcValueScalar = funcPtr
case funcValueSwitch: case funcValueSwitch:
sigGlobal := c.getTypeCode(sig) sigGlobal := c.getFuncSignature(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature" funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName) funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() { if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature") funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{ funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType), llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal, sigGlobal,
@@ -78,6 +73,22 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
return funcValue return funcValue
} }
// getFuncSignature returns a global for identification of a particular function
// signature. It is used in runtime.funcValueWithSignature and in calls to
// getFuncPtr.
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
typeCodeName := getTypeCodeName(sig)
sigGlobalName := "reflect/types.type:" + typeCodeName
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
sigGlobal.SetGlobalConstant(true)
sigGlobal.SetLinkage(llvm.InternalLinkage)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -92,14 +103,14 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
// decodeFuncValue extracts the context and the function pointer from this func // decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation. // value. This may be an expensive operation.
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) { func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
context = c.builder.CreateExtractValue(funcValue, 0, "") context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() { switch c.funcImplementation() {
case funcValueDoubleword: case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "") funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch: case funcValueSwitch:
llvmSig := c.getRawFuncType(sig) llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig) sigGlobal := c.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "") funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "") funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default: default:
@@ -115,7 +126,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
rawPtr := c.getRawFuncType(typ) rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false) return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch: case funcValueSwitch:
return c.getLLVMRuntimeType("funcValue") return c.mod.GetTypeByName("runtime.funcValue")
default: default:
panic("unimplemented func value variant") panic("unimplemented func value variant")
} }
+104
View File
@@ -0,0 +1,104 @@
package compiler
import (
"math/big"
"tinygo.org/x/go-llvm"
)
func (c *Compiler) addGlobalsBitmap() {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
//
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(bitmapNew)
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
-471
View File
@@ -1,471 +0,0 @@
package compiler
// This file provides IR transformations necessary for precise and portable
// garbage collectors.
import (
"go/token"
"math/big"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// needsStackObjects returns true if the compiler should insert stack objects
// that can be traced by the garbage collector.
func (c *Compiler) needsStackObjects() bool {
if c.selectGC() != "conservative" {
return false
}
for _, tag := range c.BuildTags {
if tag == "baremetal" {
return false
}
}
return true
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
c.trackPointer(value)
case llvm.StructTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.StructElementTypesCount()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
return
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
// If the type is an aggregate type, it will check whether there is a pointer
// inside.
func typeHasPointers(t llvm.Type) bool {
switch t.TypeKind() {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if typeHasPointers(t.ElementType()) {
return true
}
return false
default:
return false
}
}
// makeGCStackSlots converts all calls to runtime.trackPointer to explicit
// stores to stack slots that are scannable by the GC.
func (c *Compiler) makeGCStackSlots() bool {
// Check whether there are allocations at all.
alloc := c.mod.NamedFunction("runtime.alloc")
if alloc.IsNil() {
// Nothing to. Make sure all remaining bits and pieces for stack
// chains are neutralized.
for _, call := range getUses(c.mod.NamedFunction("runtime.trackPointer")) {
call.EraseFromParentAsInstruction()
}
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if !stackChainStart.IsNil() {
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStart.Type().ElementType()))
stackChainStart.SetGlobalConstant(true)
}
}
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
if trackPointer.IsNil() || trackPointer.FirstUse().IsNil() {
return false // nothing to do
}
// Look at *all* functions to see whether they are free of function pointer
// calls.
// This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
// faster when written in C++ (to avoid the CGo overhead).
funcsWithFPCall := map[llvm.Value]struct{}{}
n := 0
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
n++
if _, ok := funcsWithFPCall[fn]; ok {
continue // already found
}
done := false
for bb := fn.FirstBasicBlock(); !bb.IsNil() && !done; bb = llvm.NextBasicBlock(bb) {
for call := bb.FirstInstruction(); !call.IsNil() && !done; call = llvm.NextInstruction(call) {
if call.IsACallInst().IsNil() {
continue // only looking at calls
}
called := call.CalledValue()
if !called.IsAFunction().IsNil() {
continue // only looking for function pointers
}
funcsWithFPCall[fn] = struct{}{}
markParentFunctions(funcsWithFPCall, fn)
done = true
}
}
}
// Determine which functions need stack objects. Many leaf functions don't
// need it: it only causes overhead for them.
// Actually, in one test it was only able to eliminate stack object from 12%
// of functions that had a call to runtime.trackPointer (8 out of 68
// functions), so this optimization is not as big as it may seem.
allocatingFunctions := map[llvm.Value]struct{}{} // set of allocating functions
// Work from runtime.alloc and trace all parents to check which functions do
// a heap allocation (and thus which functions do not).
markParentFunctions(allocatingFunctions, alloc)
// Also trace all functions that call a function pointer.
for fn := range funcsWithFPCall {
// Assume that functions that call a function pointer do a heap
// allocation as a conservative guess because the called function might
// do a heap allocation.
allocatingFunctions[fn] = struct{}{}
markParentFunctions(allocatingFunctions, fn)
}
// Collect some variables used below in the loop.
stackChainStart := c.mod.NamedGlobal("runtime.stackChainStart")
if stackChainStart.IsNil() {
panic("stack chain start not found!")
}
stackChainStartType := stackChainStart.Type().ElementType()
stackChainStart.SetInitializer(llvm.ConstNull(stackChainStartType))
// Iterate until runtime.trackPointer has no uses left.
for use := trackPointer.FirstUse(); !use.IsNil(); use = trackPointer.FirstUse() {
// Pick the first use of runtime.trackPointer.
call := use.User()
if call.IsACallInst().IsNil() {
panic("expected runtime.trackPointer use to be a call")
}
// Pick the parent function.
fn := call.InstructionParent().Parent()
if _, ok := allocatingFunctions[fn]; !ok {
// This function nor any of the functions it calls (recursively)
// allocate anything from the heap, so it will not trigger a garbage
// collection cycle. Thus, it does not need to track local pointer
// values.
// This is a useful optimization but not as big as you might guess,
// as described above (it avoids stack objects for ~12% of
// functions).
call.EraseFromParentAsInstruction()
continue
}
// Find all calls to runtime.trackPointer in this function.
var calls []llvm.Value
var returns []llvm.Value
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
switch inst.InstructionOpcode() {
case llvm.Call:
if inst.CalledValue() == trackPointer {
calls = append(calls, inst)
}
case llvm.Ret:
returns = append(returns, inst)
}
}
}
// Determine what to do with each call.
var allocas, pointers []llvm.Value
for _, call := range calls {
ptr := call.Operand(0)
call.EraseFromParentAsInstruction()
if ptr.IsAInstruction().IsNil() {
continue
}
// Some trivial optimizations.
if ptr.IsAInstruction().IsNil() {
continue
}
switch ptr.InstructionOpcode() {
case llvm.PHI, llvm.GetElementPtr:
// These values do not create new values: the values already
// existed locally in this function so must have been tracked
// already.
continue
case llvm.ExtractValue, llvm.BitCast:
// These instructions do not create new values, but their
// original value may not be tracked. So keep tracking them for
// now.
// With more analysis, it should be possible to optimize a
// significant chunk of these away.
case llvm.Call, llvm.Load, llvm.IntToPtr:
// These create new values so must be stored locally. But
// perhaps some of these can be fused when they actually refer
// to the same value.
default:
// Ambiguous. These instructions are uncommon, but perhaps could
// be optimized if needed.
}
if !ptr.IsAAllocaInst().IsNil() {
if typeHasPointers(ptr.Type().ElementType()) {
allocas = append(allocas, ptr)
}
} else {
pointers = append(pointers, ptr)
}
}
if len(allocas) == 0 && len(pointers) == 0 {
// This function does not need to keep track of stack pointers.
continue
}
// Determine the type of the required stack slot.
fields := []llvm.Type{
stackChainStartType, // Pointer to parent frame.
c.uintptrType, // Number of elements in this frame.
}
for _, alloca := range allocas {
fields = append(fields, alloca.Type().ElementType())
}
for _, ptr := range pointers {
fields = append(fields, ptr.Type())
}
stackObjectType := c.ctx.StructType(fields, false)
// Create the stack object at the function entry.
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
stackObject := c.builder.CreateAlloca(stackObjectType, "gc.stackobject")
initialStackObject := llvm.ConstNull(stackObjectType)
numSlots := (c.targetData.TypeAllocSize(stackObjectType) - c.targetData.TypeAllocSize(c.i8ptrType)*2) / uint64(c.targetData.ABITypeAlignment(c.uintptrType))
numSlotsValue := llvm.ConstInt(c.uintptrType, numSlots, false)
initialStackObject = llvm.ConstInsertValue(initialStackObject, numSlotsValue, []uint32{1})
c.builder.CreateStore(initialStackObject, stackObject)
// Update stack start.
parent := c.builder.CreateLoad(stackChainStart, "")
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "")
c.builder.CreateStore(parent, gep)
stackObjectCast := c.builder.CreateBitCast(stackObject, stackChainStartType, "")
c.builder.CreateStore(stackObjectCast, stackChainStart)
// Replace all independent allocas with GEPs in the stack object.
for i, alloca := range allocas {
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+i), false),
}, "")
alloca.ReplaceAllUsesWith(gep)
alloca.EraseFromParentAsInstruction()
}
// Do a store to the stack object after each new pointer that is created.
for i, ptr := range pointers {
c.builder.SetInsertPointBefore(llvm.NextInstruction(ptr))
gep := c.builder.CreateGEP(stackObject, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(2+len(allocas)+i), false),
}, "")
c.builder.CreateStore(ptr, gep)
}
// Make sure this stack object is popped from the linked list of stack
// objects at return.
for _, ret := range returns {
c.builder.SetInsertPointBefore(ret)
c.builder.CreateStore(parent, stackChainStart)
}
}
return true
}
func (c *Compiler) addGlobalsBitmap() bool {
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
return false // nothing to do: no GC in use
}
var trackedGlobals []llvm.Value
var trackedGlobalTypes []llvm.Type
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.IsDeclaration() {
continue
}
typ := global.Type().ElementType()
ptrs := c.getPointerBitmap(typ, global.Name())
if ptrs.BitLen() == 0 {
continue
}
trackedGlobals = append(trackedGlobals, global)
trackedGlobalTypes = append(trackedGlobalTypes, typ)
}
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
globalsBundle.SetLinkage(llvm.InternalLinkage)
globalsBundle.SetUnnamedAddr(true)
initializer := llvm.Undef(globalsBundleType)
for i, global := range trackedGlobals {
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
})
global.ReplaceAllUsesWith(gep)
global.EraseFromParentAsGlobal()
}
globalsBundle.SetInitializer(initializer)
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
bitmapValues := make([]llvm.Value, len(bitmapBytes))
for i, b := range bitmapBytes {
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
}
bitmapArray := llvm.ConstArray(c.ctx.Int8Type(), bitmapValues)
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
bitmapOld.ReplaceAllUsesWith(llvm.ConstBitCast(bitmapNew, bitmapOld.Type()))
bitmapNew.SetInitializer(bitmapArray)
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
return true // the IR was changed
}
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0)
case llvm.PointerTypeKind:
return big.NewInt(1)
case llvm.StructTypeKind:
ptrs := big.NewInt(0)
for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, name)
if subptrs.BitLen() == 0 {
continue
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
panic("precise GC: global contains unaligned pointer: " + name)
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
case llvm.ArrayTypeKind:
subtyp := typ.ElementType()
subptrs := c.getPointerBitmap(subtyp, name)
ptrs := big.NewInt(0)
if subptrs.BitLen() == 0 {
return ptrs
}
elementSize := c.targetData.TypeAllocSize(subtyp)
for i := 0; i < typ.ArrayLength(); i++ {
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
return ptrs
default:
panic("unknown type kind of global: " + name)
}
}
// markParentFunctions traverses all parent function calls (recursively) and
// adds them to the set of marked functions. It only considers function calls:
// any other uses of such a function is ignored.
func markParentFunctions(marked map[llvm.Value]struct{}, fn llvm.Value) {
worklist := []llvm.Value{fn}
for len(worklist) != 0 {
fn := worklist[len(worklist)-1]
worklist = worklist[:len(worklist)-1]
for _, use := range getUses(fn) {
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
// Not the parent function.
continue
}
parent := use.InstructionParent().Parent()
if _, ok := marked[parent]; !ok {
marked[parent] = struct{}{}
worklist = append(worklist, parent)
}
}
}
}
+39 -127
View File
@@ -1,16 +1,5 @@
package compiler package compiler
// This file implements lowering for the goroutine scheduler. There are two
// scheduler implementations, one based on tasks (like RTOSes and the main Go
// runtime) and one based on a coroutine compiler transformation. The task based
// implementation requires very little work from the compiler but is not very
// portable (in particular, it is very hard if not impossible to support on
// WebAssembly). The coroutine based one requires a lot of work by the compiler
// to implement, but can run virtually anywhere with a single scheduler
// implementation.
//
// The below description is for the coroutine based scheduler.
//
// This file lowers goroutine pseudo-functions into coroutines scheduled by a // This file lowers goroutine pseudo-functions into coroutines scheduled by a
// scheduler at runtime. It uses coroutine support in LLVM for this // scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html // transformation: https://llvm.org/docs/Coroutines.html
@@ -73,7 +62,7 @@ package compiler
// llvm.suspend(hdl) // suspend point // llvm.suspend(hdl) // suspend point
// println("some other operation") // println("some other operation")
// var i *int // allocate space on the stack for the return value // var i *int // allocate space on the stack for the return value
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise // runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
// bar(hdl) // await, pass a continuation (hdl) to bar // bar(hdl) // await, pass a continuation (hdl) to bar
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate // llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i) // println("done", *i)
@@ -117,69 +106,10 @@ type asyncFunc struct {
unreachableBlock llvm.BasicBlock unreachableBlock llvm.BasicBlock
} }
// LowerGoroutines performs some IR transformations necessary to support // LowerGoroutines is a pass called during optimization that transforms the IR
// goroutines. It does something different based on whether it uses the // into one where all blocking functions are turned into goroutines and blocking
// coroutine or the tasks implementation of goroutines, and whether goroutines // calls into await calls.
// are necessary at all.
func (c *Compiler) LowerGoroutines() error { func (c *Compiler) LowerGoroutines() error {
switch c.selectScheduler() {
case "coroutines":
return c.lowerCoroutines()
case "tasks":
return c.lowerTasks()
default:
panic("unknown scheduler type")
}
}
// lowerTasks starts the main goroutine and then runs the scheduler.
// This is enough compiler-level transformation for the task-based scheduler.
func (c *Compiler) lowerTasks() error {
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
panic("expected exactly 1 call of runtime.callMain, check the entry point")
}
mainCall := uses[0]
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
// Program needs a scheduler. Start main.main as a goroutine and start
// the scheduler.
realMainWrapper := c.createGoroutineStartWrapper(realMain)
c.builder.SetInsertPointBefore(mainCall)
zero := llvm.ConstInt(c.uintptrType, 0, false)
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
c.createRuntimeCall("scheduler", nil, "")
sleep := c.mod.NamedFunction("time.Sleep")
if !sleep.IsNil() {
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
}
} else {
// Program doesn't need a scheduler. Call main.main directly.
c.builder.SetInsertPointBefore(mainCall)
params := []llvm.Value{
llvm.Undef(c.i8ptrType), // unused context parameter
llvm.Undef(c.i8ptrType), // unused coroutine handle
}
c.createCall(realMain, params, "")
// runtime.Goexit isn't needed so let it be optimized away by
// globalopt.
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
}
mainCall.EraseFromParentAsInstruction()
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage)
return nil
}
// lowerCoroutines transforms the IR into one where all blocking functions are
// turned into goroutines and blocking calls into await calls. It also makes
// sure that the first coroutine is started and the coroutine scheduler will be
// run.
func (c *Compiler) lowerCoroutines() error {
needsScheduler, err := c.markAsyncFunctions() needsScheduler, err := c.markAsyncFunctions()
if err != nil { if err != nil {
return err return err
@@ -214,6 +144,12 @@ func (c *Compiler) lowerCoroutines() error {
// main.main was set to external linkage during IR construction. Set it to // main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations. // internal linkage to enable interprocedural optimizations.
realMain.SetLinkage(llvm.InternalLinkage) realMain.SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
return nil return nil
} }
@@ -237,9 +173,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() { if !sleep.IsNil() {
worklist = append(worklist, sleep) worklist = append(worklist, sleep)
} }
deadlock := c.mod.NamedFunction("runtime.deadlock") deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlock.IsNil() { if !deadlockStub.IsNil() {
worklist = append(worklist, deadlock) worklist = append(worklist, deadlockStub)
} }
chanSend := c.mod.NamedFunction("runtime.chanSend") chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() { if !chanSend.IsNil() {
@@ -275,25 +211,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Add all callees to the worklist. // Add all callees to the worklist.
for _, use := range getUses(f) { for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt { if use.IsConstant() && use.Opcode() == llvm.BitCast {
for _, call := range getUses(use) { bitcastUses := getUses(use)
for _, call := range bitcastUses {
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" { if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine") return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
} }
} }
// This is a go statement. Do not mark the parent as async, as // This is a go statement. Do not mark the parent as async, as
// starting a goroutine is not a blocking operation. // starting a goroutine is not a blocking operation.
continue continue
} }
if use.IsConstant() && use.Opcode() == llvm.BitCast {
// Not sure why this const bitcast is here but as long as it
// has no uses it can be ignored, I guess?
// I think it was created for the runtime.isnil check but
// somehow wasn't removed when all these checks are removed.
if len(getUses(use)) == 0 {
continue
}
}
if use.IsACallInst().IsNil() { if use.IsACallInst().IsNil() {
// Not a call instruction. Maybe a store to a global? In any // Not a call instruction. Maybe a store to a global? In any
// case, this requires support for async calls across function // case, this requires support for async calls across function
@@ -322,12 +250,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// goroutine is not async (does not do any blocking operation), no // goroutine is not async (does not do any blocking operation), no
// scheduler is necessary as it can be called directly. // scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) { for _, use := range getUses(makeGoroutine) {
// Input param must be const ptrtoint of function. // Input param must be const bitcast of function.
ptrtoint := use.Operand(0) bitcast := use.Operand(0)
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt { if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
panic("expected const ptrtoint operand of runtime.makeGoroutine") panic("expected const bitcast operand of runtime.makeGoroutine")
} }
goroutine := ptrtoint.Operand(0) goroutine := bitcast.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok { if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true needsScheduler = true
break break
@@ -364,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines. // Transform all async functions into coroutines.
for _, f := range asyncList { for _, f := range asyncList {
if f == sleep || f == deadlock || f == chanSend || f == chanRecv { if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
continue continue
} }
@@ -381,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) { for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() { if !inst.IsACallInst().IsNil() {
callee := inst.CalledValue() callee := inst.CalledValue()
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv { if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
continue continue
} }
asyncCalls = append(asyncCalls, inst) asyncCalls = append(asyncCalls, inst)
@@ -393,7 +321,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Coroutine setup. // Coroutine setup.
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction()) c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state") taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8") stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{ id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false),
@@ -408,9 +336,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr") size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
} }
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data") data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
if c.needsStackObjects() {
c.trackPointer(data)
}
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle") frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
// Modify async calls so this function suspends right after the child // Modify async calls so this function suspends right after the child
@@ -429,7 +354,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca") retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
c.builder.SetInsertPointBefore(inst) c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "") data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "") c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
} }
// Suspend. // Suspend.
@@ -464,20 +389,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointBefore(inst) c.builder.SetInsertPointBefore(inst)
var parentHandle llvm.Value parentHandle := f.LastParam()
if f.Linkage() == llvm.ExternalLinkage {
// Exported function.
// Note that getTaskStatePtr will panic if it is called with
// a nil pointer, so blocking exported functions that try to
// return anything will not work.
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
} else {
parentHandle = f.LastParam()
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
// sanity check
panic("trying to make exported function async: " + f.Name())
}
}
// Store return values. // Store return values.
switch inst.OperandsCount() { switch inst.OperandsCount() {
@@ -487,7 +399,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Return this value by writing to the pointer stored in the // Return this value by writing to the pointer stored in the
// parent handle. The parent coroutine has made an alloca that // parent handle. The parent coroutine has made an alloca that
// we can write to to store our return value. // we can write to to store our return value.
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData") returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca") alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
c.builder.CreateStore(inst.Operand(0), alloca) c.builder.CreateStore(inst.Operand(0), alloca)
default: default:
@@ -505,7 +417,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// behavior somehow (with the unreachable instruction). // behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()), llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false), llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret") }, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2) sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock) sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
@@ -566,9 +478,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
sleepCall.EraseFromParentAsInstruction() sleepCall.EraseFromParentAsInstruction()
} }
// Transform calls to runtime.deadlock into coroutine suspends (without // Transform calls to runtime.deadlockStub into coroutine suspends (without
// resume). // resume).
for _, deadlockCall := range getUses(deadlock) { for _, deadlockCall := range getUses(deadlockStub) {
// deadlockCall must be a call instruction. // deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()] frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
@@ -576,7 +488,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointBefore(deadlockCall) c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{ continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()), llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false), llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
}, "") }, "")
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead") c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
c.builder.SetInsertPointBefore(deadlockCall) c.builder.SetInsertPointBefore(deadlockCall)
@@ -643,14 +555,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine") makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) { for _, goroutine := range getUses(makeGoroutine) {
ptrtointIn := goroutine.Operand(0) bitcastIn := goroutine.Operand(0)
origFunc := ptrtointIn.Operand(0) origFunc := bitcastIn.Operand(0)
uses := getUses(goroutine) uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() { if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine") return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
} }
inttoptrOut := uses[0] bitcastOut := uses[0]
uses = getUses(inttoptrOut) uses = getUses(bitcastOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() { if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast") return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
} }
@@ -665,7 +577,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
c.builder.SetInsertPointBefore(realCall) c.builder.SetInsertPointBefore(realCall)
c.builder.CreateCall(origFunc, params, "") c.builder.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction() realCall.EraseFromParentAsInstruction()
inttoptrOut.EraseFromParentAsInstruction() bitcastOut.EraseFromParentAsInstruction()
goroutine.EraseFromParentAsInstruction() goroutine.EraseFromParentAsInstruction()
} }
-135
View File
@@ -1,135 +0,0 @@
package compiler
// This file implements the 'go' keyword to start a new goroutine. See
// goroutine-lowering.go for more details.
import "tinygo.org/x/go-llvm"
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.selectScheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, params, "")
default:
panic("unreachable")
}
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
//
// It creates a wrapper like this:
//
// func add$gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// })(ptr)
// add(args.x, args.y)
// }
//
// This is useful because the task-based goroutine start implementation only
// allows a single (pointer) argument to the newly started goroutine. Also, it
// ignores the return value because newly started goroutines do not have a
// return value.
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
var wrapper llvm.Value
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
wrapper.SetLinkage(llvm.PrivateLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-2])
params = append(params, llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType))
// Create the call.
c.builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
//
// var funcPtr func(x, y int) int
//
// A wrapper like the following is created:
//
// func .gowrapper(ptr *unsafe.Pointer) {
// args := (*struct{
// x, y int
// fn func(x, y int) int
// })(ptr)
// args.fn(x, y)
// }
//
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true)
entry := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(entry)
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
c.builder.CreateCall(fnPtr, params, "")
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+2 -11
View File
@@ -18,19 +18,10 @@ import (
// func ReadRegister(name string) uintptr // func ReadRegister(name string) uintptr
// //
// The register name must be a constant, for example "sp". // The register name must be a constant, for example "sp".
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) { func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false) fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value) regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil return c.builder.CreateCall(target, nil, ""), nil
} }
+8 -8
View File
@@ -163,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself. // run runs the pass itself.
func (p *lowerInterfacesPass) run() { func (p *lowerInterfacesPass) run() {
// Collect all type codes. // Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0) typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0) typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
var typesInInterfaces []llvm.Value var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() { switch global.Type() {
@@ -603,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
// TODO: debug info // TODO: debug info
// Create all used basic blocks. // Create all used basic blocks.
entry := p.ctx.AddBasicBlock(fn, "entry") entry := llvm.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then") thenBlock := llvm.AddBasicBlock(fn, "then")
elseBlock := p.ctx.AddBasicBlock(fn, "else") elseBlock := llvm.AddBasicBlock(fn, "else")
// Add all possible types as cases. // Add all possible types as cases.
p.builder.SetInsertPointAtEnd(entry) p.builder.SetInsertPointAtEnd(entry)
@@ -661,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// TODO: debug info // TODO: debug info
// Create entry block. // Create entry block.
entry := p.ctx.AddBasicBlock(fn, "entry") entry := llvm.AddBasicBlock(fn, "entry")
// Create default block and make it unreachable (which it is, because all // Create default block and make it unreachable (which it is, because all
// possible types are checked). // possible types are checked).
defaultBlock := p.ctx.AddBasicBlock(fn, "default") defaultBlock := llvm.AddBasicBlock(fn, "default")
p.builder.SetInsertPointAtEnd(defaultBlock) p.builder.SetInsertPointAtEnd(defaultBlock)
p.builder.CreateUnreachable() p.builder.CreateUnreachable()
@@ -684,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// Define all possible functions that can be called. // Define all possible functions that can be called.
for _, typ := range itf.types { for _, typ := range itf.types {
bb := p.ctx.AddBasicBlock(fn, typ.name) bb := llvm.AddBasicBlock(fn, typ.name)
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb) sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
// The function we will redirect to when the interface has this type. // The function we will redirect to when the interface has this type.
+22 -102
View File
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
itfMethodSetGlobal := c.getTypeMethodSet(typ) itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name()) itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() { if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.getLLVMRuntimeType("typeInInterface") typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name()) itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal})) itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true) itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage) itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
} }
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "") itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface")) itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "") itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf return itf
@@ -45,100 +45,27 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
// It returns a pointer to an external global which should be replaced with the // It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass. // real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ) globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if global.IsNil() { if global.IsNil() {
// Create a new typecode global. global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var references llvm.Value
var length int64
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
references = c.getTypeCode(typ.Elem())
case *types.Pointer:
references = c.getTypeCode(typ.Elem())
case *types.Slice:
references = c.getTypeCode(typ.Elem())
case *types.Array:
references = c.getTypeCode(typ.Elem())
length = typ.Len()
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
}
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global
} }
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
}
// getTypeCodeName returns a name for this type that can be used in the // getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect // interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum. // package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string { func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) { switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
case *types.Array: case *types.Array:
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem()) return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic: case *types.Basic:
var kind string var kind string
switch t.Kind() { switch t.Kind() {
@@ -181,21 +108,21 @@ func getTypeCodeName(t types.Type) string {
default: default:
panic("unknown basic type: " + t.Name()) panic("unknown basic type: " + t.Name())
} }
return "basic:" + kind return "basic:" + name + kind
case *types.Chan: case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem()) return "chan:" + name + getTypeCodeName(t.Elem())
case *types.Interface: case *types.Interface:
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type()) methods[i] = getTypeCodeName(t.Method(i).Type())
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}" return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
keyType := getTypeCodeName(t.Key()) keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem()) elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}" return "map:" + name + "{" + keyType + "," + elemType + "}"
case *types.Pointer: case *types.Pointer:
return "pointer:" + getTypeCodeName(t.Elem()) return "pointer:" + name + getTypeCodeName(t.Elem())
case *types.Signature: case *types.Signature:
params := make([]string, t.Params().Len()) params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ { for i := 0; i < t.Params().Len(); i++ {
@@ -205,9 +132,9 @@ func getTypeCodeName(t types.Type) string {
for i := 0; i < t.Results().Len(); i++ { for i := 0; i < t.Results().Len(); i++ {
results[i] = getTypeCodeName(t.Results().At(i).Type()) results[i] = getTypeCodeName(t.Results().At(i).Type())
} }
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}" return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice: case *types.Slice:
return "slice:" + getTypeCodeName(t.Elem()) return "slice:" + name + getTypeCodeName(t.Elem())
case *types.Struct: case *types.Struct:
elems := make([]string, t.NumFields()) elems := make([]string, t.NumFields())
if t.NumFields() > 2 && t.Field(0).Name() == "C union" { if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
@@ -215,16 +142,9 @@ func getTypeCodeName(t types.Type) string {
panic("cgo unions are not allowed in interfaces") panic("cgo unions are not allowed in interfaces")
} }
for i := 0; i < t.NumFields(); i++ { for i := 0; i < t.NumFields(); i++ {
embedded := "" elems[i] = getTypeCodeName(t.Field(i).Type())
if t.Field(i).Embedded() {
embedded = "#"
}
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`"
}
} }
return "struct:" + "{" + strings.Join(elems, ",") + "}" return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
default: default:
panic("unknown type: " + t.String()) panic("unknown type: " + t.String())
} }
@@ -243,11 +163,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
ms := c.ir.Program.MethodSets.MethodSet(typ) ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 { if ms.Len() == 0 {
// no methods, so can leave that one out // no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
} }
methods := make([]llvm.Value, ms.Len()) methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo") interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ { for i := 0; i < ms.Len(); i++ {
method := ms.At(i) method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
@@ -380,7 +300,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
// Continue after the if statement. // Continue after the if statement.
c.builder.SetInsertPointAtEnd(nextBlock) c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value") phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock}) phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk { if expr.CommaOk {
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
+9 -74
View File
@@ -1,8 +1,6 @@
package compiler package compiler
import ( import (
"reflect"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -26,41 +24,23 @@ func getUses(value llvm.Value) []llvm.Value {
// createEntryBlockAlloca creates a new alloca in the entry block, even though // createEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is // the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block. // after the last instruction in the current block. Also, it adds lifetime
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value { // information to the IR signalling that the alloca won't be used before this
currentBlock := c.builder.GetInsertBlock() // point.
entryBlock := currentBlock.Parent().EntryBasicBlock()
if entryBlock.FirstInstruction().IsNil() {
c.builder.SetInsertPointAtEnd(entryBlock)
} else {
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
}
alloca := c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
return alloca
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start infromation in the IR signalling that the alloca won't be used
// before this point.
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // end the lifetime after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) { func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
alloca = c.createEntryBlockAlloca(t, name) currentBlock := c.builder.GetInsertBlock()
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
alloca = c.builder.CreateAlloca(t, name)
c.builder.SetInsertPointAtEnd(currentBlock)
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast") bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false) size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "") c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
return return
} }
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it // getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet. // first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value { func (c *Compiler) getLifetimeStartFunc() llvm.Value {
@@ -154,48 +134,3 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
return newBlock return newBlock
} }
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
return buf
}
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}
+13 -15
View File
@@ -15,7 +15,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
// Allocate the memory for the resulting type. Do not zero this memory: it // Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not // will be zeroed by the hashmap get implementation if the key is not
// present in the map. // present in the map.
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value") mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
// Do the lookup. How it is done depends on the key type. // Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value var commaOkValue llvm.Value
@@ -27,21 +27,21 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack // Store the key in an alloca, in the entry block to avoid dynamic stack
// growth. // growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca) c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap. // Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr} params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "") commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
} else { } else {
// Not trivially comparable using memcmp. // Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String()) return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
} }
// Load the resulting value from the hashmap. The value is set to the zero // Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap. // value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "") mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
if commaOk { if commaOk {
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false)) tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
@@ -54,7 +54,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
} }
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value") valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca) c.builder.CreateStore(value, valueAlloca)
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
@@ -63,15 +63,15 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
c.createRuntimeCall("hashmapStringSet", params, "") c.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca) c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr} params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "") c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
} else { } else {
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String()) c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
} }
c.emitLifetimeEnd(valuePtr, valueSize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
} }
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
@@ -82,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapStringDelete", params, "") c.createRuntimeCall("hashmapStringDelete", params, "")
return nil return nil
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca) c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr} params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "") c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize) c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
return nil return nil
} else { } else {
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String()) return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
} }
} }
@@ -121,8 +121,6 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) { switch keyType := keyType.(type) {
case *types.Basic: case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0 return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct: case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ { for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying() fieldType := keyType.Field(i).Type().Underlying()
+143 -21
View File
@@ -3,7 +3,6 @@ package compiler
import ( import (
"errors" "errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -23,11 +22,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.replacePanicsWithTrap() // -panic=trap c.replacePanicsWithTrap() // -panic=trap
} }
// run a check of all of our code
if c.VerifyIR {
c.checkModule()
}
// Run function passes for each function. // Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod) funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose() defer funcPasses.Dispose()
@@ -43,15 +37,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses := llvm.NewPassManager() goPasses := llvm.NewPassManager()
defer goPasses.Dispose() defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass() goPasses.AddGlobalOptimizerPass()
goPasses.AddGlobalDCEPass()
goPasses.AddConstantPropagationPass() goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass() goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass() goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod) goPasses.Run(c.mod)
// Run Go-specific optimization passes. // Run Go-specific optimization passes.
transform.OptimizeMaps(c.mod) c.OptimizeMaps()
c.OptimizeStringToBytes() c.OptimizeStringToBytes()
transform.OptimizeAllocs(c.mod) c.OptimizeAllocs()
c.LowerInterfaces() c.LowerInterfaces()
c.LowerFuncValues() c.LowerFuncValues()
@@ -61,7 +56,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod) goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations. // Run TinyGo-specific interprocedural optimizations.
transform.OptimizeAllocs(c.mod) c.OptimizeAllocs()
c.OptimizeStringToBytes() c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons. // Lower runtime.isnil calls to regular nil comparisons.
@@ -107,15 +102,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
} }
} }
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32() // Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered. // doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
@@ -129,9 +115,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
builder.Populate(modPasses) builder.Populate(modPasses)
modPasses.Run(c.mod) modPasses.Run(c.mod)
hasGCPass := c.addGlobalsBitmap() if c.gcIsPrecise() {
hasGCPass = c.makeGCStackSlots() || hasGCPass c.addGlobalsBitmap()
if hasGCPass {
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return errors.New("GC pass caused a verification failure") return errors.New("GC pass caused a verification failure")
} }
@@ -159,6 +144,48 @@ func (c *Compiler) replacePanicsWithTrap() {
} }
} }
// Eliminate created but not used maps.
//
// In the future, this should statically allocate created but never modified
// maps. This has not yet been implemented, however.
func (c *Compiler) OptimizeMaps() {
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
if hashmapMake.IsNil() {
// nothing to optimize
return
}
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
for _, makeInst := range getUses(hashmapMake) {
updateInsts := []llvm.Value{}
unknownUses := false // are there any uses other than setting a value?
for _, use := range getUses(makeInst) {
if use := use.IsACallInst(); !use.IsNil() {
switch use.CalledValue() {
case hashmapBinarySet, hashmapStringSet:
updateInsts = append(updateInsts, use)
default:
unknownUses = true
}
} else {
unknownUses = true
}
}
if !unknownUses {
// This map can be entirely removed, as it is only created but never
// used.
for _, inst := range updateInsts {
inst.EraseFromParentAsInstruction()
}
makeInst.EraseFromParentAsInstruction()
}
}
}
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever // Transform runtime.stringToBytes(...) calls into const []byte slices whenever
// possible. This optimizes the following pattern: // possible. This optimizes the following pattern:
// w.Write([]byte("foo")) // w.Write([]byte("foo"))
@@ -209,6 +236,101 @@ func (c *Compiler) OptimizeStringToBytes() {
} }
} }
// Basic escape analysis: translate runtime.alloc calls into alloca
// instructions.
func (c *Compiler) OptimizeAllocs() {
allocator := c.mod.NamedFunction("runtime.alloc")
if allocator.IsNil() {
// nothing to optimize
return
}
heapallocs := getUses(allocator)
for _, heapalloc := range heapallocs {
nilValue := llvm.Value{}
if heapalloc.Operand(0).IsAConstant() == nilValue {
// Do not allocate variable length arrays on the stack.
continue
}
size := heapalloc.Operand(0).ZExtValue()
if size > 256 {
// The maximum value for a stack allocation.
// TODO: tune this, this is just a random value.
continue
}
// In general the pattern is:
// %0 = call i8* @runtime.alloc(i32 %size)
// %1 = bitcast i8* %0 to type*
// (use %1 only)
// But the bitcast might sometimes be dropped when allocating an *i8.
// The 'bitcast' variable below is thus usually a bitcast of the
// heapalloc but not always.
bitcast := heapalloc // instruction that creates the value
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
// getting only bitcast use
bitcast = uses[0]
}
if !c.doesEscape(bitcast) {
// Insert alloca in the entry block. Do it here so that mem2reg can
// promote it to a SSA value.
fn := bitcast.InstructionParent().Parent()
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
zero := c.getZeroValue(alloca.Type().ElementType())
c.builder.CreateStore(zero, alloca)
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
bitcast.ReplaceAllUsesWith(stackalloc)
if heapalloc != bitcast {
bitcast.EraseFromParentAsInstruction()
}
heapalloc.EraseFromParentAsInstruction()
}
}
}
// Very basic escape analysis.
func (c *Compiler) doesEscape(value llvm.Value) bool {
uses := getUses(value)
for _, use := range uses {
nilValue := llvm.Value{}
if use.IsAGetElementPtrInst() != nilValue {
if c.doesEscape(use) {
return true
}
} else if use.IsABitCastInst() != nilValue {
// A bitcast escapes if the casted-to value escapes.
if c.doesEscape(use) {
return true
}
} else if use.IsALoadInst() != nilValue {
// Load does not escape.
} else if use.IsAStoreInst() != nilValue {
// Store only escapes when the value is stored to, not when the
// value is stored into another value.
if use.Operand(0) == value {
return true
}
} else if use.IsACallInst() != nilValue {
if !c.hasFlag(use, value, "nocapture") {
return true
}
} else if use.IsAICmpInst() != nilValue {
// Comparing pointers don't let the pointer escape.
// This is often a compiler-inserted nil check.
} else {
// Unknown instruction, might escape.
return true
}
}
// does not escape
return false
}
// Check whether the given value (which is of pointer type) is never stored to. // Check whether the given value (which is of pointer type) is never stored to.
func (c *Compiler) isReadOnly(value llvm.Value) bool { func (c *Compiler) isReadOnly(value llvm.Value) bool {
uses := getUses(value) uses := getUses(value)
+73 -406
View File
@@ -1,43 +1,10 @@
package compiler package compiler
// This file has some compiler support for run-time reflection using the reflect
// package. In particular, it encodes type information in type codes in such a
// way that the reflect package can decode the type from this information.
// Where needed, it also adds some side tables for looking up more information
// about a type, when that information cannot be stored directly in the type
// code.
//
// Go has 26 different type kinds.
//
// Type kinds are subdivided in basic types (see the list of basicTypes below)
// that are mostly numeric literals and non-basic (or "complex") types that are
// more difficult to encode. These non-basic types come in two forms:
// * Prefix types (pointer, slice, interface, channel): these just add
// something to an existing type. For example, a pointer like *int just adds
// the fact that it's a pointer to an existing type (int).
// These are encoded efficiently by adding a prefix to a type code.
// * Types with multiple fields (struct, array, func, map). All of these have
// multiple fields contained within. Most obviously structs can contain many
// types as fields. Also arrays contain not just the element type but also
// the length parameter which can be any arbitrary number and thus may not
// fit in a type code.
// These types are encoded using side tables.
//
// This distinction is also important for how named types are encoded. At the
// moment, named basic type just get a unique number assigned while named
// non-basic types have their underlying type stored in a sidetable.
import ( import (
"encoding/binary"
"go/ast"
"math/big" "math/big"
"strings" "strings"
"tinygo.org/x/go-llvm"
) )
// A list of basic types and their numbers. This list should be kept in sync
// with the list of Kind constants of type.go in the reflect package.
var basicTypes = map[string]int64{ var basicTypes = map[string]int64{
"bool": 1, "bool": 1,
"int": 2, "int": 2,
@@ -59,72 +26,6 @@ var basicTypes = map[string]int64{
"unsafeptr": 18, "unsafeptr": 18,
} }
// A list of non-basic types. Adding 19 to this number will give the Kind as
// used in src/reflect/types.go, and it must be kept in sync with that list.
var nonBasicTypes = map[string]int64{
"chan": 0,
"interface": 1,
"pointer": 2,
"slice": 3,
"array": 4,
"func": 5,
"map": 6,
"struct": 7,
}
// typeCodeAssignmentState keeps some global state around for type code
// assignments, used to assign one unique type code to each Go type.
type typeCodeAssignmentState struct {
// An integer that's incremented each time it's used to give unique IDs to
// type codes that are not yet fully supported otherwise by the reflect
// package (or are simply unused in the compiled program).
fallbackIndex int
// This is the length of an uintptr. Only used occasionally to know whether
// a given number can be encoded as a varint.
uintptrLen int
// Map of named types to their type code. It is important that named types
// get unique IDs for each type.
namedBasicTypes map[string]int
namedNonBasicTypes map[string]int
// Map of array types to their type code.
arrayTypes map[string]int
arrayTypesSidetable []byte
needsArrayTypesSidetable bool
// Map of struct types to their type code.
structTypes map[string]int
structTypesSidetable []byte
needsStructNamesSidetable bool
// Map of struct names and tags to their name string.
structNames map[string]int
structNamesSidetable []byte
needsStructTypesSidetable bool
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
// used at runtime to get details about a named non-basic type.
// Entries are varints (see makeVarint below and readVarint in
// reflect/sidetables.go for the encoding): one varint per entry. The
// integers in namedNonBasicTypes are indices into this array. Because these
// are varints, most type codes are really small (just one byte).
//
// Note that this byte buffer is not created when it is not needed
// (reflect.namedNonBasicTypesSidetable has no uses), see
// needsNamedTypesSidetable.
namedNonBasicTypesSidetable []uint64
// This indicates whether namedNonBasicTypesSidetable needs to be created at
// all. If it is false, namedNonBasicTypesSidetable will contain simple
// monotonically increasing numbers.
needsNamedNonBasicTypesSidetable bool
}
// assignTypeCodes is used to assign a type code to each type in the program
// that is ever stored in an interface. It tries to use the smallest possible
// numbers to make the code that works with interfaces as small as possible.
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
fn := c.mod.NamedFunction("reflect.ValueOf") fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() { if fn.IsNil() {
@@ -137,21 +38,13 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
} }
// Assign typecodes the way the reflect package expects. // Assign typecodes the way the reflect package expects.
state := typeCodeAssignmentState{ fallbackIndex := 1
fallbackIndex: 1, namedTypes := make(map[string]int)
uintptrLen: c.uintptrType.IntTypeWidth(),
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
arrayTypes: make(map[string]int),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
}
for _, t := range typeSlice { for _, t := range typeSlice {
num := state.getTypeCodeNum(t.typecode) if t.name[:5] != "type:" {
panic("expected type name to start with 'type:'")
}
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() { if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
// TODO: support this in some way, using a side table for example. // TODO: support this in some way, using a side table for example.
// That's less efficient but better than not working at all. // That's less efficient but better than not working at all.
@@ -161,41 +54,25 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
} }
t.num = num.Uint64() t.num = num.Uint64()
} }
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsArrayTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructTypesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructNamesSidetable {
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
} }
// getTypeCodeNum returns the typecode for a given type as expected by the // getTypeCodeNum returns the typecode for a given type as expected by the
// reflect package. Also see getTypeCodeName, which serializes types to a string // reflect package. Also see getTypeCodeName, which serializes types to a string
// based on a types.Type value for this function. // based on a types.Type value for this function.
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int { func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
// Note: see src/reflect/type.go for bit allocations. // Note: see src/reflect/type.go for bit allocations.
class, value := getClassAndValueFromTypeCode(typecode) // A type can be named or unnamed. Example of both:
// basic:~foo:uint64
// basic:uint64
// Extract the class (basic, slice, pointer, etc.), the name, and the
// contents of this type ID string. Allocate bits based on that, as
// src/runtime/types.go expects.
class := id[:strings.IndexByte(id, ':')]
value := id[len(class)+1:]
name := "" name := ""
if class == "named" { if value[0] == '~' {
name = value name = value[1:strings.IndexByte(value, ':')]
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}) value = value[len(name)+2:]
class, value = getClassAndValueFromTypeCode(typecode)
} }
if class == "basic" { if class == "basic" {
// Basic types follow the following bit pattern: // Basic types follow the following bit pattern:
@@ -204,286 +81,76 @@ func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.I
// upper bits are used to indicate the named type. // upper bits are used to indicate the named type.
num, ok := basicTypes[value] num, ok := basicTypes[value]
if !ok { if !ok {
panic("invalid basic type: " + value) panic("invalid basic type: " + id)
} }
if name != "" { if name != "" {
// This type is named, set the upper bits to the name ID. // This type is named, set the upper bits to the name ID.
num |= int64(state.getBasicNamedTypeNum(name)) << 5 num |= int64(getNamedTypeNum(namedTypes, name)) << 5
} }
return big.NewInt(num << 1) return big.NewInt(num << 1)
} else { } else {
// Non-baisc types use the following bit pattern: // Complex types use the following bit pattern:
// ...nxxx1 // ...nxxx1
// where xxx indicates the non-basic type. The upper bits contain // where xxx indicates the complex type (any non-basic type). The upper
// whatever the type contains. Types that wrap a single other type // bits contain whatever the type contains. Types that wrap a single
// (channel, interface, pointer, slice) just contain the bits of the // other type (channel, interface, pointer, slice) just contain the bits
// wrapped type. Other types (like struct) need more fields and thus // of the wrapped type. Other types (like struct) have a different
// cannot be encoded as a simple prefix. // method of encoding the contents of the type.
var classNumber int64
if n, ok := nonBasicTypes[class]; ok {
classNumber = n
} else {
panic("unknown type kind: " + class)
}
var num *big.Int var num *big.Int
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode var classNumber int64
switch class {
case "chan":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 0
case "interface":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 1
case "pointer":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 2
case "slice":
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
classNumber = 3
case "array":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(*fallbackIndex))
*fallbackIndex++
classNumber = 7
default:
panic("unknown type kind: " + id)
}
if name == "" { if name == "" {
num = state.getNonBasicTypeCode(class, typecode) num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
} else { } else {
// We must return a named type here. But first check whether it // TODO: store num in a sidetable
// has already been defined. num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
if index, ok := state.namedNonBasicTypes[name]; ok { num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
num := big.NewInt(int64(index))
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
return num
}
lowBits |= 1 << 4 // set the 'n' bit (see above)
if !state.needsNamedNonBasicTypesSidetable {
// Use simple small integers in this case, to make these numbers
// smaller.
index := len(state.namedNonBasicTypes) + 1
state.namedNonBasicTypes[name] = index
num = big.NewInt(int64(index))
} else {
// We need to store full type information.
// First allocate a number in the named non-basic type
// sidetable.
index := len(state.namedNonBasicTypesSidetable)
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
state.namedNonBasicTypes[name] = index
// Get the typecode of the underlying type (which could be the
// element type in the case of pointers, for example).
num = state.getNonBasicTypeCode(class, typecode)
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
panic("cannot store value in sidetable")
}
// Now update the side table with the number we just
// determined. We need this multi-step approach to avoid stack
// overflow due to adding types recursively in the case of
// linked lists (a pointer which points to a struct that
// contains that same pointer).
state.namedNonBasicTypesSidetable[index] = num.Uint64()
num = big.NewInt(int64(index))
}
} }
// Concatenate the 'num' and 'lowBits' bitstrings.
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
return num return num
} }
} }
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of // getNamedTypeNum returns an appropriate (unique) number for the given named
// the type code used there in the type code. // type. If the name already has a number that number is returned, else a new
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int { // number is returned. The number is always non-zero.
switch class { func getNamedTypeNum(namedTypes map[string]int, name string) int {
case "chan", "pointer", "slice": if num, ok := namedTypes[name]; ok {
// Prefix-style type kinds. The upper bits contain the element type. return num
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}) } else {
return state.getTypeCodeNum(sub) num = len(namedTypes) + 1
case "array": namedTypes[name] = num
// An array is basically a pair of (typecode, length) stored in a
// sidetable.
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
case "struct":
// More complicated type kind. The upper bits contain the index to the
// struct type in the struct types sidetable.
return big.NewInt(int64(state.getStructTypeNum(typecode)))
default:
// Type has not yet been implemented, so fall back by using a unique
// number.
num := big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
return num return num
} }
} }
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
// runtime.typecodeID), looks at the name, and extracts the typecode class and
// value from it. For example, for a typecode with the following name:
// reflect/types.type:pointer:named:reflect.ValueError
// It extracts:
// class = "pointer"
// value = "named:reflect.ValueError"
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
typecodeName := typecode.Name()
const prefix = "reflect/types.type:"
if !strings.HasPrefix(typecodeName, prefix) {
panic("unexpected typecode name: " + typecodeName)
}
id := typecodeName[len(prefix):]
class = id[:strings.IndexByte(id, ':')]
value = id[len(class)+1:]
return
}
// getBasicNamedTypeNum returns an appropriate (unique) number for the given
// named type. If the name already has a number that number is returned, else a
// new number is returned. The number is always non-zero.
func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
if num, ok := state.namedBasicTypes[name]; ok {
return num
}
num := len(state.namedBasicTypes) + 1
state.namedBasicTypes[name] = num
return num
}
// getArrayTypeNum returns the array type number, which is an index into the
// reflect.arrayTypesSidetable or a unique number for this type if this table is
// not used.
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.arrayTypes[name]; ok {
// This array type already has an entry in the sidetable. Don't store
// it twice.
return num
}
if !state.needsArrayTypesSidetable {
// We don't need array sidetables, so we can just assign monotonically
// increasing numbers to each array type.
num := len(state.arrayTypes)
state.arrayTypes[name] = num
return num
}
elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
// TODO: make this a regular error
panic("array element type has a type code that is too big")
}
// The array side table is a sequence of {element type, array length}.
arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue()
buf := makeVarint(elemTypeNum.Uint64())
buf = append(buf, makeVarint(arrayLength)...)
index := len(state.arrayTypesSidetable)
state.arrayTypes[name] = index
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
return index
}
// getStructTypeNum returns the struct type number, which is an index into
// reflect.structTypesSidetable or an unique number for every struct if this
// sidetable is not needed in the to-be-compiled program.
func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int {
name := typecode.Name()
if num, ok := state.structTypes[name]; ok {
// This struct already has an assigned type code.
return num
}
if !state.needsStructTypesSidetable {
// We don't need struct sidetables, so we can just assign monotonically
// increasing numbers to each struct type.
num := len(state.structTypes)
state.structTypes[name] = num
return num
}
// Get the fields this struct type contains.
// The struct number will be the start index of
structTypeGlobal := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}).Operand(0).Initializer()
numFields := structTypeGlobal.Type().ArrayLength()
// The first data that is stored in the struct sidetable is the number of
// fields this struct contains. This is usually just a single byte because
// most structs don't contain that many fields, but make it a varint just
// to be sure.
buf := makeVarint(uint64(numFields))
// Iterate over every field in the struct.
// Every field is stored sequentially in the struct sidetable. Fields can
// be retrieved from this list of fields at runtime by iterating over all
// of them until the right field has been found.
// Perhaps adding some index would speed things up, but it would also make
// the sidetable bigger.
for i := 0; i < numFields; i++ {
// Collect some information about this field.
field := llvm.ConstExtractValue(structTypeGlobal, []uint32{uint32(i)})
nameGlobal := llvm.ConstExtractValue(field, []uint32{1})
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
panic("compiler: no name for this struct field")
}
fieldNameBytes := getGlobalBytes(nameGlobal.Operand(0))
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
// See whether this struct field has an associated tag, and if so,
// store that tag in the tags sidetable.
tagGlobal := llvm.ConstExtractValue(field, []uint32{2})
hasTag := false
tagNumber := 0
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
hasTag = true
tagBytes := getGlobalBytes(tagGlobal.Operand(0))
tagNumber = state.getStructNameNumber(tagBytes)
}
// The 'embedded' or 'anonymous' flag for this field.
embedded := llvm.ConstExtractValue(field, []uint32{3}).ZExtValue() != 0
// The first byte in the struct types sidetable is a flags byte with
// two bits in it.
flagsByte := byte(0)
if embedded {
flagsByte |= 1
}
if hasTag {
flagsByte |= 2
}
if ast.IsExported(string(fieldNameBytes)) {
flagsByte |= 4
}
buf = append(buf, flagsByte)
// Get the type number and add it to the buffer.
// All fields have a type, so include it directly here.
typeNum := state.getTypeCodeNum(llvm.ConstExtractValue(field, []uint32{0}))
if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() {
// TODO: make this a regular error
panic("struct field has a type code that is too big")
}
buf = append(buf, makeVarint(typeNum.Uint64())...)
// Add the name.
buf = append(buf, makeVarint(uint64(fieldNameNumber))...)
// Add the tag, if there is one.
if hasTag {
buf = append(buf, makeVarint(uint64(tagNumber))...)
}
}
num := len(state.structTypesSidetable)
state.structTypes[name] = num
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
return num
}
// getStructNameNumber stores this string (name or tag) onto the struct names
// sidetable. The format is a varint of the length of the struct, followed by
// the raw bytes of the name. Multiple identical strings are stored under the
// same name for space efficiency.
func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int {
name := string(nameBytes)
if n, ok := state.structNames[name]; ok {
// This name was used before, re-use it now (for space efficiency).
return n
}
// This name is not yet in the names sidetable. Add it now.
n := len(state.structNamesSidetable)
state.structNames[name] = n
state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...)
state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...)
return n
}
// makeVarint is a small helper function that returns the bytes of the number in
// varint encoding.
func makeVarint(n uint64) []byte {
buf := make([]byte, binary.MaxVarintLen64)
return buf[:binary.PutUvarint(buf, n)]
}
-107
View File
@@ -1,107 +0,0 @@
package compiler
// This file manages symbols, that is, functions and globals. It reads their
// pragmas, determines the link name, etc.
import (
"go/ast"
"go/token"
"go/types"
"strings"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// globalInfo contains some information about a specific global. By default,
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
type globalInfo struct {
linkName string // go:extern
extern bool // go:extern
}
// loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals.
func (c *Compiler) loadASTComments(lprogram *loader.Program) {
c.astComments = map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
c.astComments[id] = decl.Doc
}
}
}
}
}
}
}
}
}
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
// the LLVM IR if it has not been added already.
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
info := c.getGlobalInfo(g)
llvmGlobal := c.mod.NamedGlobal(info.linkName)
if llvmGlobal.IsNil() {
llvmType := c.getLLVMType(g.Type().(*types.Pointer).Elem())
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
if !info.extern {
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
llvmGlobal.SetLinkage(llvm.InternalLinkage)
}
}
return llvmGlobal
}
// getGlobalInfo returns some information about a specific global.
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
info := globalInfo{}
if strings.HasPrefix(g.Name(), "C.") {
// Created by CGo: such a name cannot be created by regular C code.
info.linkName = g.Name()[2:]
info.extern = true
} else {
// Pick the default linkName.
info.linkName = g.RelString(nil)
// Check for //go: pragmas, which may change the link name (among
// others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc)
}
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
info.extern = true
if len(parts) == 2 {
info.linkName = parts[1]
}
}
}
}
+8 -33
View File
@@ -4,6 +4,7 @@ package compiler
// compiler builtins. // compiler builtins.
import ( import (
"go/constant"
"strconv" "strconv"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
@@ -13,7 +14,7 @@ import (
// emitSyscall emits an inline system call instruction, depending on the target // emitSyscall emits an inline system call instruction, depending on the target
// OS/arch. // OS/arch.
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) { func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
num := c.getValue(frame, call.Args[0]) num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
var syscallResult llvm.Value var syscallResult llvm.Value
switch { switch {
case c.GOARCH == "amd64": case c.GOARCH == "amd64":
@@ -28,12 +29,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
// > All system classes enter the kernel via the syscall instruction. // > All system classes enter the kernel via the syscall instruction.
// //
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h // Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "") num += 0x2000000
} }
// Sources: // Sources:
// https://stackoverflow.com/a/2538212 // https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall // https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
args := []llvm.Value{num} args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
argTypes := []llvm.Type{c.uintptrType} argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like: // Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}" // "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
@@ -58,32 +59,6 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
fnType := llvm.FunctionType(c.uintptrType, argTypes, false) fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "") syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "386" && c.GOOS == "linux":
// Sources:
// syscall(2) man page
// https://stackoverflow.com/a/2538212
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
args := []llvm.Value{num}
argTypes := []llvm.Type{c.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
constraints := "={eax},0"
for i, arg := range call.Args[1:] {
constraints += "," + [...]string{
"{ebx}",
"{ecx}",
"{edx}",
"{esi}",
"{edi}",
"{ebp}",
}[i]
llvmValue := c.getValue(frame, arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
syscallResult = c.builder.CreateCall(target, args, "")
case c.GOARCH == "arm" && c.GOOS == "linux": case c.GOARCH == "arm" && c.GOOS == "linux":
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
@@ -106,7 +81,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
args = append(args, num) args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType) argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ { for i := len(call.Args) - 1; i < 4; i++ {
@@ -136,7 +111,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
args = append(args, num) args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType) argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ { for i := len(call.Args) - 1; i < 8; i++ {
@@ -165,7 +140,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096 inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
hasError := c.builder.CreateAnd(inrange1, inrange2, "") hasError := c.builder.CreateAnd(inrange1, inrange2, "")
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError") errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "") retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "") retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
@@ -181,7 +156,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
zero := llvm.ConstInt(c.uintptrType, 0, false) zero := llvm.ConstInt(c.uintptrType, 0, false)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "") hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError") errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false)) retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "") retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
retval = c.builder.CreateInsertValue(retval, zero, 1, "") retval = c.builder.CreateInsertValue(retval, zero, 1, "")
retval = c.builder.CreateInsertValue(retval, errResult, 2, "") retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
+8 -20
View File
@@ -25,7 +25,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvm.ConstPointerNull(c.i8ptrType) return llvm.ConstPointerNull(c.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind { } else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr") return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
// Packed data fits in a pointer, so store it directly inside the // Packed data fits in a pointer, so store it directly inside the
// pointer. // pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind { if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
@@ -34,14 +34,11 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
} }
// Because packedType is a struct and we have to cast it to a *i8, store // Because packedType is a struct and we have to cast it to a *i8, store
// it in an alloca first for bitcasting (store+bitcast+load). // it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "") packedAlloc = c.builder.CreateAlloca(packedType, "")
} else { } else {
// Packed data is bigger than a pointer, so allocate it on the heap. // Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false) sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "") packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
if c.needsStackObjects() {
c.trackPointer(packedHeapAlloc)
}
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "") packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
} }
// Store all values in the alloca or heap pointer. // Store all values in the alloca or heap pointer.
@@ -57,14 +54,10 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
if packedHeapAlloc.IsNil() { if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca. // Load value (as *i8) from the alloca.
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "") packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
result := c.builder.CreateLoad(packedAlloc, "") return c.builder.CreateLoad(packedAlloc, "")
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
c.emitLifetimeEnd(packedPtr, packedSize)
return result
} else { } else {
// Get the original heap allocation pointer, which already is an *i8. // Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
} }
} }
@@ -73,21 +66,21 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
packedType := c.ctx.StructType(valueTypes, false) packedType := c.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data. // Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value var packedAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType) size := c.targetData.TypeAllocSize(packedType)
if size == 0 { if size == 0 {
// No data to unpack. // No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind { } else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly. // A single pointer is always stored directly.
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")} return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) { } else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
// Packed data stored directly in pointer. // Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind { if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form. // Keep this cast in SSA form.
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")} return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
} }
// Fallback: load it using an alloca. // Fallback: load it using an alloca.
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc") packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value") packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
c.builder.CreateStore(packedRawValue, packedRawAlloc) c.builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc") packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
@@ -101,7 +94,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
for i, valueType := range valueTypes { for i, valueType := range valueTypes {
if c.targetData.TypeAllocSize(valueType) == 0 { if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load. // This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType) values[i] = c.getZeroValue(valueType)
continue continue
} }
indices := []llvm.Value{ indices := []llvm.Value{
@@ -111,10 +104,5 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "") gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = c.builder.CreateLoad(gep, "") values[i] = c.builder.CreateLoad(gep, "")
} }
if !packedRawAlloc.IsNil() {
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
c.emitLifetimeEnd(allocPtr, allocSize)
}
return values return values
} }
-10
View File
@@ -1,10 +0,0 @@
module github.com/tinygo-org/tinygo
go 1.11
require (
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add
)
-16
View File
@@ -1,16 +0,0 @@
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
+14 -53
View File
@@ -86,7 +86,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
case !inst.IsAAllocaInst().IsNil(): case !inst.IsAAllocaInst().IsNil():
allocType := inst.Type().ElementType() allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca") alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType)) alloca.SetInitializer(getZeroValue(allocType))
alloca.SetLinkage(llvm.InternalLinkage) alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{ fr.locals[inst] = &LocalValue{
Underlying: alloca, Underlying: alloca,
@@ -176,20 +176,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
continue // special case: bitcast of alloc continue // special case: bitcast of alloc
} }
} }
if _, ok := fr.getLocal(operand).(*MapValue); ok {
// Special case for runtime.trackPointer calls.
// Note: this might not be entirely sound in some rare cases
// where the map is stored in a dirty global.
uses := getUses(inst)
if len(uses) == 1 {
use := uses[0]
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
continue
}
}
// It is not possible in Go to bitcast a map value to a pointer.
panic("unimplemented: bitcast of map")
}
value := fr.getLocal(operand).(*LocalValue) value := fr.getLocal(operand).(*LocalValue)
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")} fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
@@ -198,25 +184,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
predicate := inst.IntPredicate() predicate := inst.IntPredicate()
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
// Unfortunately, the const propagation in the IR builder
// doesn't handle pointer compares of inttoptr values. So we
// implement it manually here.
lhsNil, ok1 := isPointerNil(lhs)
rhsNil, ok2 := isPointerNil(rhs)
if ok1 && ok2 {
if lhsNil && rhsNil {
// Both are nil, so this icmp is always evaluated to true.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
continue
}
if lhsNil != rhsNil {
// Only one of them is nil, so this comparison must return false.
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
continue
}
}
}
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")} fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
case !inst.IsAFCmpInst().IsNil(): case !inst.IsAFCmpInst().IsNil():
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
@@ -253,7 +220,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
allocType = llvm.ArrayType(allocType, elementCount) allocType = llvm.ArrayType(allocType, elementCount)
} }
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc") alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(llvm.ConstNull(allocType)) alloc.SetInitializer(getZeroValue(allocType))
alloc.SetLinkage(llvm.InternalLinkage) alloc.SetLinkage(llvm.InternalLinkage)
result := &LocalValue{ result := &LocalValue{
Underlying: alloc, Underlying: alloc,
@@ -312,7 +279,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
stringType := fr.Mod.GetTypeByName("runtime._string") stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0})) retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false) retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(stringType) ret := getZeroValue(stringType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
fr.locals[inst] = &LocalValue{fr.Eval, ret} fr.locals[inst] = &LocalValue{fr.Eval, ret}
@@ -335,7 +302,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
sliceType := inst.Type() sliceType := inst.Type()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0})) retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false) retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
ret := llvm.ConstNull(sliceType) ret := getZeroValue(sliceType)
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
@@ -382,8 +349,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)} fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case callee.Name() == "llvm.dbg.value": case callee.Name() == "llvm.dbg.value":
// do nothing // do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side // This are all print instructions, which necessarily have side
// effects but no results. // effects but no results.
@@ -496,21 +461,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
} }
thenBB := inst.Operand(1) thenBB := inst.Operand(1)
elseBB := inst.Operand(2) elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() { if !cond.IsConstant() {
return nil, nil, errors.New("interp: branch on a non-constant") return nil, nil, errors.New("interp: branch on a non-constant")
} } else {
if !cond.IsAConstantExpr().IsNil() { switch cond.ZExtValue() {
// This may happen when the instruction builder could not case 0: // false
// const-fold some instructions. return nil, []llvm.Value{thenBB}, nil // then
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression") case 1: // true
} return nil, []llvm.Value{elseBB}, nil // else
switch cond { default:
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false panic("branch was not true or false")
return nil, []llvm.Value{thenBB}, nil // then }
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
} }
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1: case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto) // unconditional branch (goto)
+1 -12
View File
@@ -35,8 +35,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
return &sideEffectResult{severity: sideEffectLimited} return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements": case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone} return &sideEffectResult{severity: sideEffectNone}
case "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value": case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone} return &sideEffectResult{severity: sideEffectNone}
} }
@@ -111,16 +109,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
default: default:
panic("unreachable") panic("unreachable")
} }
case llvm.Load: case llvm.Load, llvm.Store:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
if e.hasLocalSideEffects(dirtyLocals, inst) {
result.updateSeverity(sideEffectLimited)
}
}
case llvm.Store:
if inst.IsVolatile() { if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited) result.updateSeverity(sideEffectLimited)
} }
+44 -26
View File
@@ -16,6 +16,50 @@ func getUses(value llvm.Value) []llvm.Value {
return uses return uses
} }
// Return a zero LLVM value for any LLVM type. Setting this value as an
// initializer has the same effect as setting 'zeroinitializer' on a value.
// Sadly, I haven't found a way to do it directly with the Go API but this works
// just fine.
func getZeroValue(typ llvm.Type) llvm.Value {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
subTyp := typ.ElementType()
subVal := getZeroValue(subTyp)
vals := make([]llvm.Value, typ.ArrayLength())
for i := range vals {
vals[i] = subVal
}
return llvm.ConstArray(subTyp, vals)
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
return llvm.ConstFloat(typ, 0.0)
case llvm.IntegerTypeKind:
return llvm.ConstInt(typ, 0, false)
case llvm.PointerTypeKind:
return llvm.ConstPointerNull(typ)
case llvm.StructTypeKind:
types := typ.StructElementTypes()
vals := make([]llvm.Value, len(types))
for i, subTyp := range types {
val := getZeroValue(subTyp)
vals[i] = val
}
if typ.StructName() != "" {
return llvm.ConstNamedStruct(typ, vals)
} else {
return typ.Context().ConstStruct(vals, false)
}
case llvm.VectorTypeKind:
zero := getZeroValue(typ.ElementType())
vals := make([]llvm.Value, typ.VectorSize())
for i := range vals {
vals[i] = zero
}
return llvm.ConstVector(vals, false)
default:
panic("interp: unknown LLVM type: " + typ.String())
}
}
// getStringBytes loads the byte slice of a Go string represented as a // getStringBytes loads the byte slice of a Go string represented as a
// {ptr, len} pair. // {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte { func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
@@ -50,29 +94,3 @@ func isScalar(t llvm.Type) bool {
return false return false
} }
} }
// isPointerNil returns whether this is a nil pointer or not. The ok value
// indicates whether the result is certain: if it is false the result boolean is
// not valid.
func isPointerNil(v llvm.Value) (result bool, ok bool) {
if !v.IsAConstantExpr().IsNil() {
switch v.Opcode() {
case llvm.IntToPtr:
// Whether a constant inttoptr is nil is easy to
// determine.
operand := v.Operand(0)
if operand.IsConstant() {
return operand.ZExtValue() == 0, true
}
case llvm.BitCast, llvm.GetElementPtr:
// These const instructions are just a kind of wrappers for the
// underlying pointer.
return isPointerNil(v.Operand(0))
}
}
if !v.IsAConstantPointerNull().IsNil() {
// A constant pointer null is always null, of course.
return true, true
}
return false, false // not valid
}
+2 -2
View File
@@ -162,7 +162,7 @@ func (v *MapValue) newBucket() llvm.Value {
llvm.ArrayType(v.KeyType, 8), // key type llvm.ArrayType(v.KeyType, 8), // key type
llvm.ArrayType(v.ValueType, 8), // value type llvm.ArrayType(v.ValueType, 8), // value type
}, false) }, false)
bucketValue := llvm.ConstNull(bucketType) bucketValue := getZeroValue(bucketType)
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket") bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
bucket.SetInitializer(bucketValue) bucket.SetInitializer(bucketValue)
bucket.SetLinkage(llvm.InternalLinkage) bucket.SetLinkage(llvm.InternalLinkage)
@@ -311,7 +311,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
keyType := v.Eval.Mod.GetTypeByName("runtime._string") keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType v.KeyType = keyType
key := llvm.ConstNull(keyType) key := getZeroValue(keyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0}) key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1}) key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
+137 -19
View File
@@ -2,6 +2,7 @@ package ir
import ( import (
"go/ast" "go/ast"
"go/token"
"go/types" "go/types"
"sort" "sort"
"strings" "strings"
@@ -22,13 +23,16 @@ type Program struct {
mainPkg *ssa.Package mainPkg *ssa.Package
Functions []*Function Functions []*Function
functionMap map[*ssa.Function]*Function functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
} }
// Function or method. // Function or method.
type Function struct { type Function struct {
*ssa.Function *ssa.Function
LLVMFn llvm.Value LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export exported bool // go:export
nobounds bool // go:nobounds nobounds bool // go:nobounds
@@ -37,6 +41,28 @@ type Function struct {
inline InlineType // go:inline inline InlineType // go:inline
} }
// Global variable, possibly constant.
type Global struct {
*ssa.Global
program *Program
LLVMGlobal llvm.Value
linkName string // go:extern
extern bool // go:extern
}
// Type with a name and possibly methods.
type NamedType struct {
*ssa.Type
LLVMType llvm.Type
}
// Type that is at some point put in an interface.
type TypeWithMethods struct {
t types.Type
Num int
Methods map[string]*types.Selection
}
// Interface type that is at some point used in a type assert (to check whether // Interface type that is at some point used in a type assert (to check whether
// it implements another interface). // it implements another interface).
type Interface struct { type Interface struct {
@@ -57,14 +83,39 @@ const (
// //go:inline). The compiler will be more likely to inline this function, // //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee. // but it is not a guarantee.
InlineHint InlineHint
// Don't inline, just like the GCC noinline attribute. Signalled using
// //go:noinline.
InlineNone
) )
// Create and initialize a new *Program from a *ssa.Program. // Create and initialize a new *Program from a *ssa.Program.
func NewProgram(lprogram *loader.Program, mainPath string) *Program { func NewProgram(lprogram *loader.Program, mainPath string) *Program {
comments := map[string]*ast.CommentGroup{}
for _, pkgInfo := range lprogram.Sorted() {
for _, file := range pkgInfo.Files {
for _, decl := range file.Decls {
switch decl := decl.(type) {
case *ast.GenDecl:
switch decl.Tok {
case token.TYPE, token.VAR:
if len(decl.Specs) != 1 {
continue
}
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.TypeSpec: // decl.Tok == token.TYPE
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
comments[id] = decl.Doc
case *ast.ValueSpec: // decl.Tok == token.VAR
for _, name := range spec.Names {
id := pkgInfo.Pkg.Path() + "." + name.Name
comments[id] = decl.Doc
}
}
}
}
}
}
}
}
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
program.Build() program.Build()
@@ -136,6 +187,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
LoaderProgram: lprogram, LoaderProgram: lprogram,
mainPkg: mainPkg, mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function), functionMap: make(map[*ssa.Function]*Function),
globalMap: make(map[*ssa.Global]*Global),
comments: comments,
} }
for _, pkg := range packageList { for _, pkg := range packageList {
@@ -160,6 +213,8 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
case *ssa.Function: case *ssa.Function:
p.addFunction(member) p.addFunction(member)
case *ssa.Type: case *ssa.Type:
t := &NamedType{Type: member}
p.NamedTypes = append(p.NamedTypes, t)
methods := getAllMethods(pkg.Prog, member.Type()) methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) { if !types.IsInterface(member.Type()) {
// named type // named type
@@ -168,7 +223,13 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
} }
} }
case *ssa.Global: case *ssa.Global:
// Ignore. Globals are not handled here. g := &Global{program: p, Global: member}
doc := p.comments[g.RelString(nil)]
if doc != nil {
g.parsePragmas(doc)
}
p.Globals = append(p.Globals, g)
p.globalMap[member] = g
case *ssa.NamedConst: case *ssa.NamedConst:
// Ignore: these are already resolved. // Ignore: these are already resolved.
default: default:
@@ -202,6 +263,10 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn] return p.functionMap[ssaFn]
} }
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
return p.globalMap[ssaGlobal]
}
func (p *Program) MainPkg() *ssa.Package { func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg return p.mainPkg
} }
@@ -230,16 +295,8 @@ func (f *Function) parsePragmas() {
} }
f.linkName = parts[1] f.linkName = parts[1]
f.exported = true f.exported = true
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
f.module = parts[1]
case "//go:inline": case "//go:inline":
f.inline = InlineHint f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt": case "//go:interrupt":
if len(parts) != 2 { if len(parts) != 2 {
continue continue
@@ -298,11 +355,6 @@ func (f *Function) Inline() InlineType {
return f.inline return f.inline
} }
// Return the module name if not the default.
func (f *Function) Module() string {
return f.module
}
// Return the link name for this function. // Return the link name for this function.
func (f *Function) LinkName() string { func (f *Function) LinkName() string {
if f.linkName != "" { if f.linkName != "" {
@@ -337,6 +389,72 @@ func (f *Function) CName() string {
return "" return ""
} }
// Parse //go: pragma comments from the source.
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
}
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//go:extern":
g.extern = true
if len(parts) == 2 {
g.linkName = parts[1]
}
}
}
}
// Return the link name for this global.
func (g *Global) LinkName() string {
if g.linkName != "" {
return g.linkName
}
if name := g.CName(); name != "" {
return name
}
return g.RelString(nil)
}
func (g *Global) IsExtern() bool {
return g.extern || g.CName() != ""
}
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
// zero-length string.
func (g *Global) CName() string {
name := g.Name()
if strings.HasPrefix(name, "C.") {
// created by ../loader/cgo.go
return name[2:]
}
return ""
}
// Return true if this named type is annotated with the //go:volatile pragma,
// for volatile loads and stores.
func (p *Program) IsVolatile(t types.Type) bool {
if t, ok := t.(*types.Named); !ok {
return false
} else {
if t.Obj().Pkg() == nil {
return false
}
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
doc := p.comments[id]
if doc == nil {
return false
}
for _, line := range doc.List {
if strings.TrimSpace(line.Text) == "//go:volatile" {
return true
}
}
return false
}
}
// Get all methods of a type. // Get all methods of a type.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection { func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
ms := prog.MethodSets.MethodSet(typ) ms := prog.MethodSets.MethodSet(typ)
-1
View File
@@ -63,7 +63,6 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...) cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run() return cmd.Run()
} }
} }
-1
View File
@@ -20,6 +20,5 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...) cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run() return cmd.Run()
} }
+29 -144
View File
@@ -1,7 +1,6 @@
package loader package loader
import ( import (
"bytes"
"errors" "errors"
"go/ast" "go/ast"
"go/build" "go/build"
@@ -11,26 +10,22 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"text/template"
"github.com/tinygo-org/tinygo/cgo" "github.com/tinygo-org/tinygo/cgo"
) )
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
mainPkg string Build *build.Context
Build *build.Context OverlayBuild *build.Context
OverlayBuild *build.Context ShouldOverlay func(path string) bool
OverlayPath func(path string) string Packages map[string]*Package
Packages map[string]*Package sorted []*Package
sorted []*Package fset *token.FileSet
fset *token.FileSet TypeChecker types.Config
TypeChecker types.Config Dir string // current working directory (for error reporting)
Dir string // current working directory (for error reporting) TINYGOROOT string // root of the TinyGo installation or root of the source code
TINYGOROOT string // root of the TinyGo installation or root of the source code CFlags []string
CFlags []string
ClangHeaders string
} }
// Package holds a loaded package, its imports, and its parsed files. // Package holds a loaded package, its imports, and its parsed files.
@@ -53,9 +48,8 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
// Load this package. // Load this package.
ctx := p.Build ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" { if p.ShouldOverlay(path) {
ctx = p.OverlayBuild ctx = p.OverlayBuild
path = newPath
} }
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment) buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
if err != nil { if err != nil {
@@ -68,11 +62,6 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg) pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil return pkg, nil
} }
@@ -102,11 +91,6 @@ func (p *Program) ImportFile(path string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg) pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil return pkg, nil
} }
@@ -185,12 +169,10 @@ func (p *Program) sort() {
// The returned error may be an Errors error, which contains a list of errors. // The returned error may be an Errors error, which contains a list of errors.
// //
// Idempotent. // Idempotent.
func (p *Program) Parse(compileTestBinary bool) error { func (p *Program) Parse() error {
includeTests := compileTestBinary
// Load all imports // Load all imports
for _, pkg := range p.Sorted() { for _, pkg := range p.Sorted() {
err := pkg.importRecursively(includeTests) err := pkg.importRecursively()
if err != nil { if err != nil {
if err, ok := err.(*ImportCycleError); ok { if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] { if pkg.ImportPath != err.Packages[0] {
@@ -203,14 +185,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
// Parse all packages. // Parse all packages.
for _, pkg := range p.Sorted() { for _, pkg := range p.Sorted() {
err := pkg.Parse(includeTests) err := pkg.Parse()
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
if err != nil { if err != nil {
return err return err
} }
@@ -227,83 +202,6 @@ func (p *Program) Parse(compileTestBinary bool) error {
return nil return nil
} }
func (p *Program) SwapTestMain() error {
var tests []string
isTestFunc := func(f *ast.FuncDecl) bool {
// TODO: improve signature check
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
return true
}
return false
}
mainPkg := p.Packages[p.mainPkg]
for _, f := range mainPkg.Files {
for i, d := range f.Decls {
switch v := d.(type) {
case *ast.FuncDecl:
if isTestFunc(v) {
tests = append(tests, v.Name.Name)
}
if v.Name.Name == "main" {
// Remove main
if len(f.Decls) == 1 {
f.Decls = make([]ast.Decl, 0)
} else {
f.Decls[i] = f.Decls[len(f.Decls)-1]
f.Decls = f.Decls[:len(f.Decls)-1]
}
}
}
}
}
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
const mainBody = `package main
import (
"testing"
)
func main () {
m := &testing.M{
Tests: []testing.TestToCall{
{{range .TestFunctions}}
{Name: "{{.}}", Func: {{.}}},
{{end}}
},
}
testing.TestMain(m)
}
`
tmpl := template.Must(template.New("testmain").Parse(mainBody))
b := bytes.Buffer{}
tmplData := struct {
TestFunctions []string
}{
TestFunctions: tests,
}
err := tmpl.Execute(&b, tmplData)
if err != nil {
return err
}
path := filepath.Join(p.mainPkg, "$testmain.go")
if p.fset == nil {
p.fset = token.NewFileSet()
}
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
if err != nil {
return err
}
mainPkg.Files = append(mainPkg.Files, newMain)
return nil
}
// parseFile is a wrapper around parser.ParseFile. // parseFile is a wrapper around parser.ParseFile.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) { func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
if p.fset == nil { if p.fset == nil {
@@ -328,7 +226,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
// Parse parses and typechecks this package. // Parse parses and typechecks this package.
// //
// Idempotent. // Idempotent.
func (p *Package) Parse(includeTests bool) error { func (p *Package) Parse() error {
if len(p.Files) != 0 { if len(p.Files) != 0 {
return nil return nil
} }
@@ -342,7 +240,7 @@ func (p *Package) Parse(includeTests bool) error {
return nil return nil
} }
files, err := p.parseFiles(includeTests) files, err := p.parseFiles()
if err != nil { if err != nil {
return err return err
} }
@@ -381,21 +279,11 @@ func (p *Package) Check() error {
} }
// parseFiles parses the loaded list of files and returns this list. // parseFiles parses the loaded list of files and returns this list.
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) { func (p *Package) parseFiles() ([]*ast.File, error) {
// TODO: do this concurrently. // TODO: do this concurrently.
var files []*ast.File var files []*ast.File
var fileErrs []error var fileErrs []error
for _, file := range p.GoFiles {
var gofiles []string
if includeTests {
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
gofiles = append(gofiles, p.GoFiles...)
gofiles = append(gofiles, p.TestGoFiles...)
} else {
gofiles = p.GoFiles
}
for _, file := range gofiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments) f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil { if err != nil {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
@@ -417,11 +305,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
files = append(files, f) files = append(files, f)
} }
if len(p.CgoFiles) != 0 { if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir) clangIncludes := ""
if p.ClangHeaders != "" { if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
cflags = append(cflags, "-I"+p.ClangHeaders) // Running from the source directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
} else {
// Running from the installation directory.
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
} }
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags) generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
if errs != nil { if errs != nil {
fileErrs = append(fileErrs, errs...) fileErrs = append(fileErrs, errs...)
} }
@@ -430,7 +322,6 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
if len(fileErrs) != 0 { if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs} return nil, Errors{p, fileErrs}
} }
return files, nil return files, nil
} }
@@ -451,15 +342,9 @@ func (p *Package) Import(to string) (*types.Package, error) {
// importRecursively() on the imported packages as well. // importRecursively() on the imported packages as well.
// //
// Idempotent. // Idempotent.
func (p *Package) importRecursively(includeTests bool) error { func (p *Package) importRecursively() error {
p.Importing = true p.Importing = true
for _, to := range p.Package.Imports {
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
if to == "C" { if to == "C" {
// Do CGo processing in a later stage. // Do CGo processing in a later stage.
continue continue
@@ -477,7 +362,7 @@ func (p *Package) importRecursively(includeTests bool) error {
if importedPkg.Importing { if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]} return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
} }
err = importedPkg.importRecursively(false) err = importedPkg.importRecursively()
if err != nil { if err != nil {
if err, ok := err.(*ImportCycleError); ok { if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...) err.Packages = append([]string{p.ImportPath}, err.Packages...)
+23 -132
View File
@@ -47,18 +47,13 @@ type BuildConfig struct {
opt string opt string
gc string gc string
panicStrategy string panicStrategy string
scheduler string
printIR bool printIR bool
dumpSSA bool dumpSSA bool
verifyIR bool
debug bool debug bool
printSizes string printSizes string
cFlags []string cFlags []string
ldFlags []string ldFlags []string
tags string
wasmAbi string wasmAbi string
heapSize int64
testConfig compiler.TestConfig
} }
// Helper function for Compiler object. // Helper function for Compiler object.
@@ -85,44 +80,21 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if goroot == "" { if goroot == "" {
return errors.New("cannot locate $GOROOT, please set it manually") return errors.New("cannot locate $GOROOT, please set it manually")
} }
tags := spec.BuildTags
major, minor, err := getGorootVersion(goroot)
if err != nil {
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || (minor != 11 && minor != 12) {
return fmt.Errorf("requires go version 1.11 or 1.12, got go%d.%d", major, minor)
}
for i := 1; i <= minor; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
tags = append(tags, extraTags...)
}
scheduler := spec.Scheduler
if config.scheduler != "" {
scheduler = config.scheduler
}
compilerConfig := compiler.Config{ compilerConfig := compiler.Config{
Triple: spec.Triple, Triple: spec.Triple,
CPU: spec.CPU, CPU: spec.CPU,
Features: spec.Features,
GOOS: spec.GOOS, GOOS: spec.GOOS,
GOARCH: spec.GOARCH, GOARCH: spec.GOARCH,
GC: config.gc, GC: config.gc,
PanicStrategy: config.panicStrategy, PanicStrategy: config.panicStrategy,
Scheduler: scheduler,
CFlags: cflags, CFlags: cflags,
LDFlags: ldflags, LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
Debug: config.debug, Debug: config.debug,
DumpSSA: config.dumpSSA, DumpSSA: config.dumpSSA,
VerifyIR: config.verifyIR,
TINYGOROOT: root, TINYGOROOT: root,
GOROOT: goroot, GOROOT: goroot,
GOPATH: getGopath(), GOPATH: getGopath(),
BuildTags: tags, BuildTags: spec.BuildTags,
TestConfig: config.testConfig,
} }
c, err := compiler.NewCompiler(pkgName, compilerConfig) c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil { if err != nil {
@@ -156,6 +128,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if spec.GOOS != "darwin" { if spec.GOOS != "darwin" {
c.ApplyFunctionSections() // -ffunction-sections c.ApplyFunctionSections() // -ffunction-sections
} }
if err := c.Verify(); err != nil {
return errors.New("verification error after applying function sections")
}
// Browsers cannot handle external functions that have type i64 because it // Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To // cannot be represented exactly in JavaScript (JS only has doubles). To
@@ -167,6 +142,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if err != nil { if err != nil {
return err return err
} }
if err := c.Verify(); err != nil {
return errors.New("verification error after running the wasm i64 hack")
}
} }
// Optimization levels here are roughly the same as Clang, but probably not // Optimization levels here are roughly the same as Clang, but probably not
@@ -243,16 +221,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command. // Prepare link command.
executable := filepath.Join(dir, "main") executable := filepath.Join(dir, "main")
tmppath := executable // final file tmppath := executable // final file
ldflags = append(ldflags, "-o", executable, objfile, "-L", root) ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
if spec.RTLib == "compiler-rt" { if spec.RTLib == "compiler-rt" {
ldflags = append(ldflags, librt) ldflags = append(ldflags, librt)
} }
if spec.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
// Compile extra files. // Compile extra files.
for i, path := range spec.ExtraFiles { for i, path := range spec.ExtraFiles {
@@ -364,33 +336,6 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
}) })
} }
func Test(pkgName, target string, config *BuildConfig) error {
spec, err := LoadTarget(target)
if err != nil {
return err
}
spec.BuildTags = append(spec.BuildTags, "test")
config.testConfig.CompileTestBinary = true
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
cmd := exec.Command(tmppath)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
// Propagate the exit code
if err, ok := err.(*exec.ExitError); ok {
if status, ok := err.Sys().(syscall.WaitStatus); ok {
os.Exit(status.ExitStatus())
}
os.Exit(1)
}
return &commandError{"failed to run compiled binary", tmppath, err}
}
return nil
})
}
func Flash(pkgName, target, port string, config *BuildConfig) error { func Flash(pkgName, target, port string, config *BuildConfig) error {
spec, err := LoadTarget(target) spec, err := LoadTarget(target)
if err != nil { if err != nil {
@@ -549,30 +494,6 @@ func Run(pkgName, target string, config *BuildConfig) error {
}) })
} }
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
// number.
func parseSize(s string) (int64, error) {
s = strings.ToLower(strings.TrimSpace(s))
if len(s) == 0 {
return 0, errors.New("no size provided")
}
multiply := int64(1)
switch s[len(s)-1] {
case 'k':
multiply = 1 << 10
case 'm':
multiply = 1 << 20
case 'g':
multiply = 1 << 30
}
if multiply != 1 {
s = s[:len(s)-1]
}
n, err := strconv.ParseInt(s, 0, 64)
n *= multiply
return n, err
}
func usage() { func usage() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.") fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version) fmt.Fprintln(os.Stderr, "version:", version)
@@ -580,7 +501,6 @@ func usage() {
fmt.Fprintln(os.Stderr, "\ncommands:") fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies") fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
fmt.Fprintln(os.Stderr, " run: compile and run immediately") fmt.Fprintln(os.Stderr, " run: compile and run immediately")
fmt.Fprintln(os.Stderr, " test: test packages")
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device") fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB") fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")") fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
@@ -591,24 +511,23 @@ func usage() {
func handleCompilerError(err error) { func handleCompilerError(err error) {
if err != nil { if err != nil {
switch err := err.(type) { if errUnsupported, ok := err.(*interp.Unsupported); ok {
case *interp.Unsupported:
// hit an unknown/unsupported instruction // hit an unknown/unsupported instruction
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:") fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
err.Inst.Dump() errUnsupported.Inst.Dump()
fmt.Fprintln(os.Stderr) fmt.Fprintln(os.Stderr)
case types.Error: } else if errCompiler, ok := err.(types.Error); ok {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, errCompiler)
case loader.Errors: } else if errLoader, ok := err.(loader.Errors); ok {
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath) fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
for _, err := range err.Errs { for _, err := range errLoader.Errs {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
case *multiError: } else if errMulti, ok := err.(*multiError); ok {
for _, err := range err.Errs { for _, err := range errMulti.Errs {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
} }
default: } else {
fmt.Fprintln(os.Stderr, "error:", err) fmt.Fprintln(os.Stderr, "error:", err)
} }
os.Exit(1) os.Exit(1)
@@ -619,13 +538,10 @@ func main() {
outpath := flag.String("o", "", "output filename") outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z") opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)") gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)") panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
printIR := flag.Bool("printir", false, "print LLVM IR") printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA") dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR") target := flag.String("target", "", "LLVM target")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
printSize := flag.String("size", "", "print sizes (none, short, full)") printSize := flag.String("size", "", "print sizes (none, short, full)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation") nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
@@ -633,7 +549,6 @@ func main() {
cFlags := flag.String("cflags", "", "additional cflags for compiler") cFlags := flag.String("cflags", "", "additional cflags for compiler")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker") ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic") wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
if len(os.Args) < 2 { if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.") fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -647,13 +562,10 @@ func main() {
opt: *opt, opt: *opt,
gc: *gc, gc: *gc,
panicStrategy: *panicStrategy, panicStrategy: *panicStrategy,
scheduler: *scheduler,
printIR: *printIR, printIR: *printIR,
dumpSSA: *dumpSSA, dumpSSA: *dumpSSA,
verifyIR: *verifyIR,
debug: !*nodebug, debug: !*nodebug,
printSizes: *printSize, printSizes: *printSize,
tags: *tags,
wasmAbi: *wasmAbi, wasmAbi: *wasmAbi,
} }
@@ -671,13 +583,6 @@ func main() {
os.Exit(1) os.Exit(1)
} }
var err error
if config.heapSize, err = parseSize(*heapSize); err != nil {
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
usage()
os.Exit(1)
}
os.Setenv("CC", "clang -target="+*target) os.Setenv("CC", "clang -target="+*target)
switch command { switch command {
@@ -687,11 +592,8 @@ func main() {
usage() usage()
os.Exit(1) os.Exit(1)
} }
pkgName := "." if flag.NArg() != 1 {
if flag.NArg() == 1 { fmt.Fprintln(os.Stderr, "No package specified.")
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
usage() usage()
os.Exit(1) os.Exit(1)
} }
@@ -699,7 +601,7 @@ func main() {
if target == "" && filepath.Ext(*outpath) == ".wasm" { if target == "" && filepath.Ext(*outpath) == ".wasm" {
target = "wasm" target = "wasm"
} }
err := Build(pkgName, *outpath, target, config) err := Build(flag.Arg(0), *outpath, target, config)
handleCompilerError(err) handleCompilerError(err)
case "build-builtins": case "build-builtins":
// Note: this command is only meant to be used while making a release! // Note: this command is only meant to be used while making a release!
@@ -741,17 +643,6 @@ func main() {
} }
err := Run(flag.Arg(0), *target, config) err := Run(flag.Arg(0), *target, config)
handleCompilerError(err) handleCompilerError(err)
case "test":
pkgName := "."
if flag.NArg() == 1 {
pkgName = flag.Arg(0)
} else if flag.NArg() > 1 {
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
usage()
os.Exit(1)
}
err := Test(pkgName, *target, config)
handleCompilerError(err)
case "clean": case "clean":
// remove cache directory // remove cache directory
dir := cacheDir() dir := cacheDir()
+3 -1
View File
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
t.Log("running tests for emulated cortex-m3...") t.Log("running tests for emulated cortex-m3...")
for _, path := range matches { for _, path := range matches {
if path == "testdata/reflect.go" {
continue
}
t.Run(path, func(t *testing.T) { t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "qemu", t) runTest(path, tmpdir, "qemu", t)
}) })
@@ -116,7 +119,6 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
opt: "z", opt: "z",
printIR: false, printIR: false,
dumpSSA: false, dumpSSA: false,
verifyIR: true,
debug: false, debug: false,
printSizes: "", printSizes: "",
wasmAbi: "js", wasmAbi: "js",
+11 -44
View File
@@ -30,7 +30,6 @@
package arm package arm
import ( import (
"runtime/volatile"
"unsafe" "unsafe"
) )
@@ -70,58 +69,37 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// Run the following system call (SVCall) with 4 arguments. // Run the following system call (SVCall) with 4 arguments.
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//go:volatile
type RegValue uint32
const ( const (
SCS_BASE = 0xE000E000 SCS_BASE = 0xE000E000
NVIC_BASE = SCS_BASE + 0x0100 NVIC_BASE = SCS_BASE + 0x0100
SCB_BASE = SCS_BASE + 0x0D00
) )
const (
SCB_AIRCR_VECTKEY_Pos = 16
SCB_AIRCR_SYSRESETREQ_Pos = 2
SCB_AIRCR_SYSRESETREQ_Msk = 1 << SCB_AIRCR_SYSRESETREQ_Pos
)
// System Control Block (SCB)
//
// SCB_Type provides the definitions for the System Control Block Registers.
type SCB_Type struct {
CPUID volatile.Register32 // CPUID Base Register
ICSR volatile.Register32 // Interrupt Control and State Register
VTOR volatile.Register32 // Vector Table Offset Register
AIRCR volatile.Register32 // Application Interrupt and Reset Control Register
SCR volatile.Register32 // System Control Register
CCR volatile.Register32 // Configuration Control Register
_ volatile.Register32 // RESERVED1;
SHP [2]volatile.Register32 // System Handlers Priority Registers. [0] is RESERVED
SHCSR volatile.Register32 // System Handler Control and State Register
}
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
// Nested Vectored Interrupt Controller (NVIC). // Nested Vectored Interrupt Controller (NVIC).
// //
// Source: // Source:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
type NVIC_Type struct { type NVIC_Type struct {
ISER [8]volatile.Register32 // Interrupt Set-enable Registers ISER [8]RegValue // Interrupt Set-enable Registers
_ [24]uint32 _ [24]uint32
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers ICER [8]RegValue // Interrupt Clear-enable Registers
_ [24]uint32 _ [24]uint32
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers ISPR [8]RegValue // Interrupt Set-pending Registers
_ [24]uint32 _ [24]uint32
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers ICPR [8]RegValue // Interrupt Clear-pending Registers
_ [24]uint32 _ [24]uint32
IABR [8]volatile.Register32 // Interrupt Active Bit Registers IABR [8]RegValue // Interrupt Active Bit Registers
_ [56]uint32 _ [56]uint32
IPR [60]volatile.Register32 // Interrupt Priority Registers IPR [60]RegValue // Interrupt Priority Registers
} }
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE))) var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
// Enable the given interrupt number. // Enable the given interrupt number.
func EnableIRQ(irq uint32) { func EnableIRQ(irq uint32) {
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F)) NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
} }
// Set the priority of the given interrupt number. // Set the priority of the given interrupt number.
@@ -138,7 +116,7 @@ func SetPriority(irq uint32, priority uint32) {
regpos := irq % 4 regpos := irq % 4
mask := uint32(0xff) << (regpos * 8) // bits to clear mask := uint32(0xff) << (regpos * 8) // bits to clear
priority = priority << (regpos * 8) // bits to set priority = priority << (regpos * 8) // bits to set
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority) NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
} }
// DisableInterrupts disables all interrupts, and returns the old state. // DisableInterrupts disables all interrupts, and returns the old state.
@@ -158,14 +136,3 @@ func DisableInterrupts() uintptr {
func EnableInterrupts(mask uintptr) { func EnableInterrupts(mask uintptr) {
Asm("cpsie if") Asm("cpsie if")
} }
// SystemReset performs a hard system reset.
func SystemReset() {
// SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
// SCB_AIRCR_SYSRESETREQ_Msk);
SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk)
for {
Asm("wfi")
}
}
-21
View File
@@ -1,21 +0,0 @@
.syntax unified
.section .text.HardFault_Handler
.global HardFault_Handler
.type HardFault_Handler, %function
HardFault_Handler:
// Put the old stack pointer in the first argument, for easy debugging. This
// is especially useful on Cortex-M0, which supports far fewer debug
// facilities.
mov r0, sp
// Load the default stack pointer from address 0 so that we can call normal
// functions again that expect a working stack. However, it will corrupt the
// old stack so the function below must not attempt to recover from this
// fault.
movs r3, #0
ldr r3, [r3]
mov sp, r3
// Continue handling this error in Go.
bl handleHardFault
-10
View File
@@ -1,10 +0,0 @@
package riscv
// Run the given assembly code. The code will be marked as having side effects,
// as it doesn't produce output and thus would normally be eliminated by the
// optimizer.
func Asm(asm string)
// ReadRegister returns the contents of the specified register. The register
// must be a processor register, reachable with the "mov" instruction.
func ReadRegister(name string) uintptr
-13
View File
@@ -1,13 +0,0 @@
.section .init
.global _start
.type _start,@function
_start:
// Workaround for missing support of the la pseudo-instruction in Clang 8:
// https://reviews.llvm.org/D55325
lui sp, %hi(_stack_top)
addi sp, sp, %lo(_stack_top)
// see https://gnu-mcu-eclipse.github.io/arch/riscv/programmer/#the-gp-global-pointer-register
lui gp, %hi(__global_pointer$)
addi gp, gp, %lo(__global_pointer$)
call main
+2 -2
View File
@@ -11,8 +11,8 @@ import (
func main() { func main() {
machine.InitADC() machine.InitADC()
led := machine.LED led := machine.GPIO{machine.LED}
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
sensor := machine.ADC{machine.ADC2} sensor := machine.ADC{machine.ADC2}
sensor.Configure() sensor.Configure()
+2 -2
View File
@@ -8,8 +8,8 @@ import (
) )
func main() { func main() {
led := machine.LED led := machine.GPIO{machine.LED}
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for { for {
led.Low() led.Low()
time.Sleep(time.Millisecond * 500) time.Sleep(time.Millisecond * 500)
+4 -4
View File
@@ -16,8 +16,8 @@ func main() {
} }
func led1() { func led1() {
led := machine.LED1 led := machine.GPIO{machine.LED}
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for { for {
println("+") println("+")
led.Low() led.Low()
@@ -30,8 +30,8 @@ func led1() {
} }
func led2() { func led2() {
led := machine.LED2 led := machine.GPIO{machine.LED2}
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for { for {
println(" +") println(" +")
led.Low() led.Low()
+6 -6
View File
@@ -7,14 +7,14 @@ import (
// This example assumes that the button is connected to pin 8. Change the value // This example assumes that the button is connected to pin 8. Change the value
// below to use a different pin. // below to use a different pin.
const ( const buttonPin = 8
led = machine.LED
button = machine.Pin(8)
)
func main() { func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput}) led := machine.GPIO{machine.LED}
button.Configure(machine.PinConfig{Mode: machine.PinInput}) led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
button := machine.GPIO{buttonPin}
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
for { for {
if button.Get() { if button.Get() {
+16 -16
View File
@@ -8,29 +8,29 @@ import (
// This example assumes that you are using the pca10040 board // This example assumes that you are using the pca10040 board
func main() { func main() {
led1 := machine.LED1 led1 := machine.GPIO{machine.LED1}
led1.Configure(machine.PinConfig{Mode: machine.PinOutput}) led1.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led2 := machine.LED2 led2 := machine.GPIO{machine.LED2}
led2.Configure(machine.PinConfig{Mode: machine.PinOutput}) led2.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led3 := machine.LED3 led3 := machine.GPIO{machine.LED3}
led3.Configure(machine.PinConfig{Mode: machine.PinOutput}) led3.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led4 := machine.LED4 led4 := machine.GPIO{machine.LED4}
led4.Configure(machine.PinConfig{Mode: machine.PinOutput}) led4.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
button1 := machine.BUTTON1 button1 := machine.GPIO{machine.BUTTON1}
button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button1.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button2 := machine.BUTTON2 button2 := machine.GPIO{machine.BUTTON2}
button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button2.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button3 := machine.BUTTON3 button3 := machine.GPIO{machine.BUTTON3}
button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button3.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button4 := machine.BUTTON4 button4 := machine.GPIO{machine.BUTTON4}
button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button4.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
for { for {
led1.Set(button1.Get()) led1.Set(button1.Get())
+3 -3
View File
@@ -9,9 +9,9 @@ import (
// change these to test a different UART or pins if available // change these to test a different UART or pins if available
var ( var (
uart = machine.UART0 uart = machine.UART0
tx = machine.UART_TX_PIN tx uint8 = machine.UART_TX_PIN
rx = machine.UART_RX_PIN rx uint8 = machine.UART_RX_PIN
) )
func main() { func main() {
-21
View File
@@ -1,21 +0,0 @@
package main
// Draw a red square on the GameBoy Advance screen.
import (
"image/color"
"machine"
)
var display = machine.Display
func main() {
display.Configure()
for x := int16(30); x < 50; x++ {
for y := int16(80); y < 100; y++ {
display.SetPixel(x, y, color.RGBA{255, 0, 0, 255})
}
}
display.Display()
}
+5 -3
View File
@@ -8,17 +8,19 @@ import (
"time" "time"
) )
// cs is the pin used for Chip Select (CS). Change to whatever is in use on your board. // CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board.
const cs = machine.Pin(3) const CS_PIN = 3
var ( var (
tx []byte tx []byte
rx []byte rx []byte
val, result uint16 val, result uint16
cs machine.GPIO
) )
func main() { func main() {
cs.Configure(machine.PinConfig{Mode: machine.PinOutput}) cs = machine.GPIO{CS_PIN}
cs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
machine.SPI0.Configure(machine.SPIConfig{ machine.SPI0.Configure(machine.SPIConfig{
Frequency: 4000000, Frequency: 4000000,
@@ -9,10 +9,10 @@ import (
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display // The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix // Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
func main() { func main() {
ledrow := machine.LED_ROW_1 ledrow := machine.GPIO{machine.LED_ROW_1}
ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput}) ledrow.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
ledcol := machine.LED_COL_1 ledcol := machine.GPIO{machine.LED_COL_1}
ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput}) ledcol.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
ledcol.Low() ledcol.Low()
for { for {
ledrow.Low() ledrow.Low()
+2 -18
View File
@@ -1,26 +1,10 @@
invoke: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go
cp ./invoke/wasm.js ./html/
cp ./invoke/index.html ./html/
export: clean wasm_exec export: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go
cp ./export/wasm.js ./html/ cp ./export/wasm.js ./html/
cp ./export/index.html ./html/ cp ./export/index.html ./html/
callback: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go
cp ./callback/wasm.js ./html/
cp ./callback/index.html ./html/
slices: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go
cp ./slices/wasm.js ./html/
cp ./slices/index.html ./html/
main: clean wasm_exec main: clean wasm_exec
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go
cp ./main/index.html ./html/ cp ./main/index.html ./html/
wasm_exec: wasm_exec:
+2 -4
View File
@@ -3,9 +3,7 @@
The examples here show two different ways of using WebAssembly with TinyGo: The examples here show two different ways of using WebAssembly with TinyGo:
1. Defining and exporting functions via the `//go:export <name>` directive. See 1. Defining and exporting functions via the `//go:export <name>` directive. See
[the export folder](./export) for an example of this. Additionally, the Wasm [the export folder](./export) for an example of this.
module (which has a default value of `env`) can be specified using
`//go:wasm-module <module>`.
1. Defining and executing a `func main()`. This is similar to how the Go 1. Defining and executing a `func main()`. This is similar to how the Go
standard library implementation works. See [the main folder](./main) for an standard library implementation works. See [the main folder](./main) for an
example of this. example of this.
@@ -36,7 +34,7 @@ $ make main
Start the local web server: Start the local web server:
```bash ```bash
$ go run server.go $ go run main.go
Serving ./html on http://localhost:8080 Serving ./html on http://localhost:8080
``` ```
-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Add two numbers, using WebAssembly:</p>
<input type="number" id="a" value="0" /> + <input type="number" id="b" value="0" /> = <input type="number" id="result" readonly />
</body>
</html>
-27
View File
@@ -1,27 +0,0 @@
package main
import (
"strconv"
"syscall/js"
)
var a, b int
func main() {
document := js.Global().Get("document")
document.Call("getElementById", "a").Set("oninput", updater(&a))
document.Call("getElementById", "b").Set("oninput", updater(&b))
update()
}
func updater(n *int) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
*n, _ = strconv.Atoi(this.Get("value").String())
update()
return nil
})
}
func update() {
js.Global().Get("document").Call("getElementById", "result").Set("value", a+b)
}
-26
View File
@@ -1,26 +0,0 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function init() {
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
}
init();
-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>Edit on either side to mimic values, using WebAssembly:</p>
<input type="text" id="a" value=""/>==<input type="text" id="b" value=""/>
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
package main
import (
"syscall/js"
)
func runner(this js.Value, args []js.Value) interface{} {
return args[0].Invoke(args[1]).String()
}
func main() {
wait := make(chan struct{}, 0)
js.Global().Set("runner", js.FuncOf(runner))
<-wait
}
-43
View File
@@ -1,43 +0,0 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function updateRight() {
const value = document.getElementById("a").value;
window.runner(function (value) {
document.getElementById("b").value = value;
}, value);
}
function updateLeft() {
const value = document.getElementById("b").value;
window.runner(function (value) {
document.getElementById("a").value = value;
}, value);
}
function init() {
document.querySelector('#a').oninput = updateRight;
document.querySelector('#b').oninput = updateLeft;
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
}
init();
-19
View File
@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Go WebAssembly</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<script src="wasm_exec.js" defer></script>
<script src="wasm.js" defer></script>
</head>
<body>
<h1>WebAssembly</h1>
<p>type values separated by comma, using WebAssembly:</p>
<input type="text" id="a" value=""/>==<div id="b"></div>
</body>
</html>
-23
View File
@@ -1,23 +0,0 @@
package main
import (
"strings"
"syscall/js"
)
func splitter(this js.Value, args []js.Value) interface{} {
values := strings.Split(args[0].String(), ",")
result := make([]interface{}, 0)
for _, each := range values {
result = append(result, each)
}
return js.ValueOf(result)
}
func main() {
wait := make(chan struct{}, 0)
js.Global().Set("splitter", js.FuncOf(splitter))
<-wait
}
-33
View File
@@ -1,33 +0,0 @@
'use strict';
const WASM_URL = 'wasm.wasm';
var wasm;
function update() {
const value = document.getElementById("a").value;
document.getElementById("b").innerHTML = JSON.stringify(window.splitter(value));
}
function init() {
document.querySelector('#a').oninput = update;
const go = new Go();
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
}
init();
+10 -10
View File
@@ -1,24 +1,24 @@
// +build arduino // +build avr,arduino
package machine package machine
const CPU_FREQUENCY = 16000000 const CPU_FREQUENCY = 16000000
// LED on the Arduino // LED on the Arduino
const LED Pin = 13 const LED = 13
// ADC on the Arduino // ADC on the Arduino
const ( const (
ADC0 Pin = 0 ADC0 = 0
ADC1 Pin = 1 ADC1 = 1
ADC2 Pin = 2 ADC2 = 2
ADC3 Pin = 3 ADC3 = 3
ADC4 Pin = 4 // Used by TWI for SDA ADC4 = 4 // Used by TWI for SDA
ADC5 Pin = 5 // Used by TWI for SCL ADC5 = 5 // Used by TWI for SCL
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 1 UART_TX_PIN = 1
UART_RX_PIN Pin = 0 UART_RX_PIN = 0
) )
-147
View File
@@ -1,147 +0,0 @@
// +build sam,atsamd21,arduino_nano33
// This contains the pin mappings for the Arduino Nano33 IoT board.
//
// For more information, see: https://store.arduino.cc/nano-33-iot
//
package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0x07738135
// GPIO Pins
const (
RX0 Pin = PB23 // UART2 RX
TX1 Pin = PB22 // UART2 TX
D2 Pin = PB10 // PWM available
D3 Pin = PB11 // PWM available
D4 Pin = PA07
D5 Pin = PA05 // PWM available
D6 Pin = PA04 // PWM available
D7 Pin = PA06
D8 Pin = PA18
D9 Pin = PA20 // PWM available
D10 Pin = PA21 // PWM available
D11 Pin = PA16 // PWM available
D12 Pin = PA19 // PWM available
D13 Pin = PA17
)
// Analog pins
const (
A0 Pin = PA02 // ADC/AIN[0]
A1 Pin = PB02 // ADC/AIN[10]
A2 Pin = PA11 // ADC/AIN[19]
A3 Pin = PA10 // ADC/AIN[18],
A4 Pin = PB08 // ADC/AIN[2], SCL: SERCOM2/PAD[1]
A5 Pin = PB09 // ADC/AIN[3], SDA: SERCOM2/PAD[1]
A6 Pin = PA09 // ADC/AIN[17]
A7 Pin = PB03 // ADC/AIN[11]
)
const (
LED = D13
)
// NINA-W102 Pins
const (
NINA_MOSI Pin = PA12
NINA_MISO Pin = PA13
NINA_CS Pin = PA14
NINA_SCK Pin = PA15
NINA_GPIO0 Pin = PA27
NINA_RESETN Pin = PA08
NINA_ACK Pin = PA28
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN Pin = PA24
USBCDC_DP_PIN Pin = PA25
)
// UART1 on the Arduino Nano 33 connects to the onboard NINA-W102 WiFi chip.
var (
UART1 = UART{Bus: sam.SERCOM5_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM5,
}
)
// UART1 pins
const (
UART_TX_PIN Pin = PA22
UART_RX_PIN Pin = PA23
)
//go:export SERCOM5_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// UART2 on the Arduino Nano 33 connects to the normal TX/RX pins.
var (
UART2 = UART{Bus: sam.SERCOM3_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOMAlt,
IRQVal: sam.IRQ_SERCOM3,
}
)
//go:export SERCOM3_IRQHandler
func handleUART2() {
// should reset IRQ
UART2.Receive(byte((UART2.Bus.DATA.Get() & 0xFF)))
UART2.Bus.INTFLAG.SetBits(sam.SERCOM_USART_INTFLAG_RXC)
}
// I2C pins
const (
SDA_PIN Pin = A4 // SDA: SERCOM4/PAD[1]
SCL_PIN Pin = A5 // SCL: SERCOM4/PAD[1]
)
// I2C on the Arduino Nano 33.
var (
I2C0 = I2C{Bus: sam.SERCOM4_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOMAlt}
)
// SPI pins
const (
SPI0_SCK_PIN Pin = A2 // SCK: SERCOM0/PAD[3]
SPI0_MOSI_PIN Pin = A3 // MOSI: SERCOM0/PAD[2]
SPI0_MISO_PIN Pin = A6 // MISO: SERCOM0/PAD[1]
)
// SPI on the Arduino Nano 33.
var (
SPI0 = SPI{Bus: sam.SERCOM0_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOM}
)
// I2S pins
const (
I2S_SCK_PIN Pin = PA10
I2S_SD_PIN Pin = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Arduino Nano 33.
)
// I2S on the Arduino Nano 33.
var (
I2S0 = I2S{Bus: sam.I2S}
)
+3 -23
View File
@@ -1,9 +1,7 @@
// +build bluepill // +build stm32,bluepill
package machine package machine
import "device/stm32"
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif // https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
const ( const (
PA0 = portA + 0 PA0 = portA + 0
@@ -49,28 +47,10 @@ const (
// UART pins // UART pins
const ( const (
UART_TX_PIN = PA9 UART_TX_PIN = PA9
UART_RX_PIN = PA10 UART_RX_PIN = PA10
UART_ALT_TX_PIN = PB6
UART_ALT_RX_PIN = PB7
) )
var (
// USART1 is the first hardware serial port on the STM32.
// Both UART0 and UART1 refer to USART1.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART1,
IRQVal: stm32.IRQ_USART1,
}
UART1 = &UART0
)
//go:export USART1_IRQHandler
func handleUART1() {
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
}
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN = PA5 SPI0_SCK_PIN = PA5
+5 -28
View File
@@ -4,9 +4,6 @@ package machine
import "device/sam" import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins // GPIO Pins
const ( const (
D0 = PB09 D0 = PB09
@@ -20,7 +17,7 @@ const (
D8 = PB23 D8 = PB23
D9 = PA06 D9 = PA06
D10 = PA07 D10 = PA07
D11 = NoPin // does not seem to exist D11 = 0xff // does not seem to exist
D12 = PA02 D12 = PA02
D13 = PA17 // PWM available D13 = PA17 // PWM available
) )
@@ -68,20 +65,6 @@ const (
UART_RX_PIN = PB09 // PORTB UART_RX_PIN = PB09 // PORTB
) )
// UART1 on the Circuit Playground Express.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins // I2C pins
const ( const (
SDA_PIN = PB02 // I2C0 external SDA_PIN = PB02 // I2C0 external
@@ -97,12 +80,12 @@ var (
I2C0 = I2C{Bus: sam.SERCOM5_I2CM, I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
SDA: SDA_PIN, SDA: SDA_PIN,
SCL: SCL_PIN, SCL: SCL_PIN,
PinMode: PinSERCOM} PinMode: GPIO_SERCOM}
// internal device // internal device
I2C1 = I2C{Bus: sam.SERCOM1_I2CM, I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
SDA: SDA1_PIN, SDA: SDA1_PIN,
SCL: SCL1_PIN, SCL: SCL1_PIN,
PinMode: PinSERCOMAlt} PinMode: GPIO_SERCOM_ALT}
) )
// SPI pins (internal flash) // SPI pins (internal flash)
@@ -114,20 +97,14 @@ const (
// SPI on the Circuit Playground Express. // SPI on the Circuit Playground Express.
var ( var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI, SPI0 = SPI{Bus: sam.SERCOM3_SPI}
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
) )
// I2S pins // I2S pins
const ( const (
I2S_SCK_PIN = PA10 I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08 I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
) )
// I2S on the Circuit Playground Express. // I2S on the Circuit Playground Express.
+2 -2
View File
@@ -1,7 +1,7 @@
// +build digispark // +build attiny85,digispark
package machine package machine
const ( const (
LED Pin = 1 LED = 1
) )
-38
View File
@@ -1,38 +0,0 @@
// +build hifive1b
package machine
const (
P00 Pin = 0
P01 Pin = 1
P02 Pin = 2
P03 Pin = 3
P04 Pin = 4
P05 Pin = 5
P06 Pin = 6
P07 Pin = 7
P08 Pin = 8
P09 Pin = 9
P10 Pin = 10
P11 Pin = 11
P12 Pin = 12
P13 Pin = 13
P14 Pin = 14
P15 Pin = 15
P16 Pin = 16
P17 Pin = 17
P18 Pin = 18
P19 Pin = 19
P20 Pin = 20
P21 Pin = 21
P22 Pin = 22
P23 Pin = 23
P24 Pin = 24
P25 Pin = 25
P26 Pin = 26
P27 Pin = 27
P28 Pin = 28
P29 Pin = 29
P30 Pin = 30
P31 Pin = 31
)
-105
View File
@@ -1,105 +0,0 @@
// +build sam,atsamd21,feather_m0
package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA11 // UART0 RX
D1 = PA10 // UART0 TX
D2 = NoPin // does not seem to exist
D3 = PA09
D4 = PA08
D5 = PA15 // PWM available
D6 = PA20 // PWM available
D7 = NoPin // does not seem to exist
D8 = PA06
D9 = PA07 // PWM available
D10 = PA18 // can be used for PWM or UART1 TX
D11 = PA16 // can be used for PWM or UART1 RX
D12 = PA19 // PWM available
D13 = PA17 // PWM available
)
// Analog pins
const (
A0 = PA02 // ADC/AIN[0]
A1 = PB08 // ADC/AIN[2]
A2 = PB09 // ADC/AIN[3]
A3 = PA04 // ADC/AIN[4]
A4 = PA05 // ADC/AIN[5]
A5 = PB02 // ADC/AIN[10]
)
const (
LED = D13
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART1 pins
const (
UART_TX_PIN = D10
UART_RX_PIN = D11
)
// UART1 on the Feather M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA23 // SCL: SERCOM3/PAD[1]
)
// I2C on the Feather M0.
var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
)
// SPI pins
const (
SPI0_SCK_PIN = PB11 // SCK: SERCOM4/PAD[3]
SPI0_MOSI_PIN = PB10 // MOSI: SERCOM4/PAD[2]
SPI0_MISO_PIN = PA12 // MISO: SERCOM4/PAD[0]
)
// SPI on the Feather M0.
var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Feather M0.
)
-19
View File
@@ -1,19 +0,0 @@
// +build hifive1b
package machine
const (
LED = LED1
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_RED = P22
LED_GREEN = P19
LED_BLUE = P21
)
const (
// TODO: figure out the pin numbers for these.
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
+4 -48
View File
@@ -2,12 +2,7 @@
package machine package machine
import ( import "device/sam"
"device/sam"
)
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins // GPIO Pins
const ( const (
@@ -53,20 +48,6 @@ const (
UART_RX_PIN = D11 UART_RX_PIN = D11
) )
// UART1 on the ItsyBitsy M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// I2C pins // I2C pins
const ( const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0] SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
@@ -78,7 +59,7 @@ var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM, I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN, SDA: SDA_PIN,
SCL: SCL_PIN, SCL: SCL_PIN,
PinMode: PinSERCOM} PinMode: GPIO_SERCOM}
) )
// SPI pins // SPI pins
@@ -90,39 +71,14 @@ const (
// SPI on the ItsyBitsy M0. // SPI on the ItsyBitsy M0.
var ( var (
SPI0 = SPI{Bus: sam.SERCOM4_SPI, SPI0 = SPI{Bus: sam.SERCOM4_SPI}
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0
const (
SPI1_CS_PIN = PA27
SPI1_SCK_PIN = PB23
SPI1_MOSI_PIN = PB22
SPI1_MISO_PIN = PB03
)
// "Internal" SPI on Sercom 5
var (
SPI1 = SPI{Bus: sam.SERCOM5_SPI,
SCK: SPI1_SCK_PIN,
MOSI: SPI1_MOSI_PIN,
MISO: SPI1_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad1,
PinMode: PinSERCOMAlt}
) )
// I2S pins // I2S pins
const ( const (
I2S_SCK_PIN = PA10 I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08 I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0. I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
) )
// I2S on the ItsyBitsy M0. // I2S on the ItsyBitsy M0.
-83
View File
@@ -1,83 +0,0 @@
// +build sam,atsamd51,itsybitsy_m4
package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA16 // UART0 RX/PWM available
D1 = PA17 // UART0 TX/PWM available
D2 = PA07
D3 = PB22
D4 = PA14 // PWM available
D5 = PA15 // PWM available
D6 = PB02 // dotStar clock
D7 = PA18 // PWM available
D8 = PB03 // dotStar data
D9 = PA19 // PWM available
D10 = PA20 // can be used for PWM or UART1 TX
D11 = PA21 // can be used for PWM or UART1 RX
D12 = PA23 // PWM available
D13 = PA22 // PWM available
)
// Analog pins
const (
A0 = PA02 // ADC/AIN[0]
A1 = PB05 // ADC/AIN[2]
A2 = PB08 // ADC/AIN[3]
A3 = PB09 // ADC/AIN[4]
A4 = PA04 // ADC/AIN[5]
A5 = PA06 // ADC/AIN[10]
)
const (
LED = D13
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART1 pins
const (
UART_TX_PIN = D1
UART_RX_PIN = D0
)
// I2C pins
const (
SDA_PIN = PA12 // SDA: SERCOM3/PAD[0]
SCL_PIN = PA13 // SCL: SERCOM3/PAD[1]
)
// I2C on the ItsyBitsy M4.
var (
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
)
// SPI pins
const (
SPI0_SCK_PIN = PA01 // SCK: SERCOM1/PAD[1]
SPI0_MOSI_PIN = PA00 // MOSI: SERCOM1/PAD[0]
SPI0_MISO_PIN = PB23 // MISO: SERCOM1/PAD[3]
)
// SPI on the ItsyBitsy M4.
var (
SPI0 = SPI{Bus: sam.SERCOM1_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0}
)
+43 -43
View File
@@ -1,4 +1,4 @@
// +build microbit // +build nrf51,microbit
package machine package machine
@@ -7,70 +7,70 @@ const HasLowFrequencyCrystal = false
// Buttons on the micro:bit (A and B) // Buttons on the micro:bit (A and B)
const ( const (
BUTTON Pin = BUTTONA BUTTON = BUTTONA
BUTTONA Pin = 17 BUTTONA = 17
BUTTONB Pin = 26 BUTTONB = 26
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 24 UART_TX_PIN = 24
UART_RX_PIN Pin = 25 UART_RX_PIN = 25
) )
// ADC pins // ADC pins
const ( const (
ADC0 Pin = 3 // P0 on the board ADC0 = 3 // P0 on the board
ADC1 Pin = 2 // P1 on the board ADC1 = 2 // P1 on the board
ADC2 Pin = 1 // P2 on the board ADC2 = 1 // P2 on the board
) )
// I2C pins // I2C pins
const ( const (
SDA_PIN Pin = 30 // P20 on the board SDA_PIN = 30 // P20 on the board
SCL_PIN Pin = 0 // P19 on the board SCL_PIN = 0 // P19 on the board
) )
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN Pin = 23 // P13 on the board SPI0_SCK_PIN = 23 // P13 on the board
SPI0_MOSI_PIN Pin = 21 // P15 on the board SPI0_MOSI_PIN = 21 // P15 on the board
SPI0_MISO_PIN Pin = 22 // P14 on the board SPI0_MISO_PIN = 22 // P14 on the board
) )
// GPIO/Analog pins // GPIO/Analog pins
const ( const (
P0 Pin = 3 P0 = 3
P1 Pin = 2 P1 = 2
P2 Pin = 1 P2 = 1
P3 Pin = 4 P3 = 4
P4 Pin = 5 P4 = 5
P5 Pin = 17 P5 = 17
P6 Pin = 12 P6 = 12
P7 Pin = 11 P7 = 11
P8 Pin = 18 P8 = 18
P9 Pin = 10 P9 = 10
P10 Pin = 6 P10 = 6
P11 Pin = 26 P11 = 26
P12 Pin = 20 P12 = 20
P13 Pin = 23 P13 = 23
P14 Pin = 22 P14 = 22
P15 Pin = 21 P15 = 21
P16 Pin = 16 P16 = 16
) )
// LED matrix pins // LED matrix pins
const ( const (
LED_COL_1 Pin = 4 LED_COL_1 = 4
LED_COL_2 Pin = 5 LED_COL_2 = 5
LED_COL_3 Pin = 6 LED_COL_3 = 6
LED_COL_4 Pin = 7 LED_COL_4 = 7
LED_COL_5 Pin = 8 LED_COL_5 = 8
LED_COL_6 Pin = 9 LED_COL_6 = 9
LED_COL_7 Pin = 10 LED_COL_7 = 10
LED_COL_8 Pin = 11 LED_COL_8 = 11
LED_COL_9 Pin = 12 LED_COL_9 = 12
LED_ROW_1 Pin = 13 LED_ROW_1 = 13
LED_ROW_2 Pin = 14 LED_ROW_2 = 14
LED_ROW_3 Pin = 15 LED_ROW_3 = 15
) )
+11 -11
View File
@@ -6,27 +6,27 @@ const HasLowFrequencyCrystal = true
// LEDs on the nrf52840-mdk (nRF52840 dev board) // LEDs on the nrf52840-mdk (nRF52840 dev board)
const ( const (
LED Pin = LED_GREEN LED = LED_GREEN
LED_GREEN Pin = 22 LED_GREEN = 22
LED_RED Pin = 23 LED_RED = 23
LED_BLUE Pin = 24 LED_BLUE = 24
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 20 UART_TX_PIN = 20
UART_RX_PIN Pin = 19 UART_RX_PIN = 19
) )
// I2C pins (unused) // I2C pins (unused)
const ( const (
SDA_PIN = NoPin SDA_PIN = 0xff
SCL_PIN = NoPin SCL_PIN = 0xff
) )
// SPI pins (unused) // SPI pins (unused)
const ( const (
SPI0_SCK_PIN = NoPin SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = NoPin SPI0_MOSI_PIN = 0
SPI0_MISO_PIN = NoPin SPI0_MISO_PIN = 0
) )
-124
View File
@@ -1,124 +0,0 @@
// +build nucleof103rb
package machine
import "device/stm32"
const (
PA0 = portA + 0
PA1 = portA + 1
PA2 = portA + 2
PA3 = portA + 3
PA4 = portA + 4
PA5 = portA + 5
PA6 = portA + 6
PA7 = portA + 7
PA8 = portA + 8
PA9 = portA + 9
PA10 = portA + 10
PA11 = portA + 11
PA12 = portA + 12
PA13 = portA + 13
PA14 = portA + 14
PA15 = portA + 15
PB0 = portB + 0
PB1 = portB + 1
PB2 = portB + 2
PB3 = portB + 3
PB4 = portB + 4
PB5 = portB + 5
PB6 = portB + 6
PB7 = portB + 7
PB8 = portB + 8
PB9 = portB + 9
PB10 = portB + 10
PB11 = portB + 11
PB12 = portB + 12
PB13 = portB + 13
PB14 = portB + 14
PB15 = portB + 15
PC0 = portC + 0
PC1 = portC + 1
PC2 = portC + 2
PC3 = portC + 3
PC4 = portC + 4
PC5 = portC + 5
PC6 = portC + 6
PC7 = portC + 7
PC8 = portC + 8
PC9 = portC + 9
PC10 = portC + 10
PC11 = portC + 11
PC12 = portC + 12
PC13 = portC + 13
PC14 = portC + 14
PC15 = portC + 15
PD0 = portD + 0
PD1 = portD + 1
PD2 = portD + 2
PD3 = portD + 3
PD4 = portD + 4
PD5 = portD + 5
PD6 = portD + 6
PD7 = portD + 7
PD8 = portD + 8
PD9 = portD + 9
PD10 = portD + 10
PD11 = portD + 11
PD12 = portD + 12
PD13 = portD + 13
PD14 = portD + 14
PD15 = portD + 15
)
const (
LED = LED_BUILTIN
LED_BUILTIN = LED_GREEN
LED_GREEN = PA5
)
const (
BUTTON = BUTTON_USER
BUTTON_USER = PC13
)
// UART pins
const (
UART_TX_PIN = PA2
UART_RX_PIN = PA3
UART_ALT_TX_PIN = PD5
UART_ALT_RX_PIN = PD6
)
var (
// USART2 is the hardware serial port connected to the onboard ST-LINK
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
// Both UART0 and UART1 refer to USART2.
UART0 = UART{
Buffer: NewRingBuffer(),
Bus: stm32.USART2,
IRQVal: stm32.IRQ_USART2,
}
UART2 = &UART0
)
//go:export USART2_IRQHandler
func handleUART2() {
UART2.Receive(byte((UART2.Bus.DR.Get() & 0xFF)))
}
// SPI pins
const (
SPI0_SCK_PIN = PA5
SPI0_MISO_PIN = PA6
SPI0_MOSI_PIN = PA7
)
// I2C pins
const (
SCL_PIN = PB6
SDA_PIN = PB7
)
+15 -15
View File
@@ -1,4 +1,4 @@
// +build pca10031 // +build nrf51,pca10031
// pca10031 is a nrf51 based dongle, intended for use in wireless applications. // pca10031 is a nrf51 based dongle, intended for use in wireless applications.
// //
@@ -10,30 +10,30 @@ const HasLowFrequencyCrystal = true
// LED on the pca10031 // LED on the pca10031
const ( const (
LED Pin = LED_RED LED = LED_RED
LED1 Pin = LED_RED LED1 = LED_RED
LED2 Pin = LED_GREEN LED2 = LED_GREEN
LED3 Pin = LED_BLUE LED3 = LED_BLUE
LED_RED Pin = 21 LED_RED = 21
LED_GREEN Pin = 22 LED_GREEN = 22
LED_BLUE Pin = 23 LED_BLUE = 23
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 9 UART_TX_PIN = 9
UART_RX_PIN Pin = 11 UART_RX_PIN = 11
) )
// I2C pins (disabled) // I2C pins (disabled)
const ( const (
SDA_PIN = NoPin SDA_PIN = 0xff
SCL_PIN = NoPin SCL_PIN = 0xff
) )
// SPI pins (unused) // SPI pins (unused)
const ( const (
SPI0_SCK_PIN = NoPin SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = NoPin SPI0_MOSI_PIN = 0
SPI0_MISO_PIN = NoPin SPI0_MISO_PIN = 0
) )
+24 -24
View File
@@ -1,4 +1,4 @@
// +build pca10040 // +build nrf,pca10040
package machine package machine
@@ -7,47 +7,47 @@ const HasLowFrequencyCrystal = true
// LEDs on the PCA10040 (nRF52832 dev board) // LEDs on the PCA10040 (nRF52832 dev board)
const ( const (
LED Pin = LED1 LED = LED1
LED1 Pin = 17 LED1 = 17
LED2 Pin = 18 LED2 = 18
LED3 Pin = 19 LED3 = 19
LED4 Pin = 20 LED4 = 20
) )
// Buttons on the PCA10040 (nRF52832 dev board) // Buttons on the PCA10040 (nRF52832 dev board)
const ( const (
BUTTON Pin = BUTTON1 BUTTON = BUTTON1
BUTTON1 Pin = 13 BUTTON1 = 13
BUTTON2 Pin = 14 BUTTON2 = 14
BUTTON3 Pin = 15 BUTTON3 = 15
BUTTON4 Pin = 16 BUTTON4 = 16
) )
// UART pins for NRF52840-DK // UART pins for NRF52840-DK
const ( const (
UART_TX_PIN Pin = 6 UART_TX_PIN = 6
UART_RX_PIN Pin = 8 UART_RX_PIN = 8
) )
// ADC pins // ADC pins
const ( const (
ADC0 Pin = 3 ADC0 = 3
ADC1 Pin = 4 ADC1 = 4
ADC2 Pin = 28 ADC2 = 28
ADC3 Pin = 29 ADC3 = 29
ADC4 Pin = 30 ADC4 = 30
ADC5 Pin = 31 ADC5 = 31
) )
// I2C pins // I2C pins
const ( const (
SDA_PIN Pin = 26 SDA_PIN = 26
SCL_PIN Pin = 27 SCL_PIN = 27
) )
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN Pin = 25 SPI0_SCK_PIN = 25
SPI0_MOSI_PIN Pin = 23 SPI0_MOSI_PIN = 23
SPI0_MISO_PIN Pin = 24 SPI0_MISO_PIN = 24
) )
+23 -23
View File
@@ -6,47 +6,47 @@ const HasLowFrequencyCrystal = true
// LEDs on the pca10056 // LEDs on the pca10056
const ( const (
LED Pin = LED1 LED = LED1
LED1 Pin = 13 LED1 = 13
LED2 Pin = 14 LED2 = 14
LED3 Pin = 15 LED3 = 15
LED4 Pin = 16 LED4 = 16
) )
// Buttons on the pca10056 // Buttons on the pca10056
const ( const (
BUTTON Pin = BUTTON1 BUTTON = BUTTON1
BUTTON1 Pin = 11 BUTTON1 = 11
BUTTON2 Pin = 12 BUTTON2 = 12
BUTTON3 Pin = 24 BUTTON3 = 24
BUTTON4 Pin = 25 BUTTON4 = 25
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 6 UART_TX_PIN = 6
UART_RX_PIN Pin = 8 UART_RX_PIN = 8
) )
// ADC pins // ADC pins
const ( const (
ADC0 Pin = 3 ADC0 = 3
ADC1 Pin = 4 ADC1 = 4
ADC2 Pin = 28 ADC2 = 28
ADC3 Pin = 29 ADC3 = 29
ADC4 Pin = 30 ADC4 = 30
ADC5 Pin = 31 ADC5 = 31
) )
// I2C pins // I2C pins
const ( const (
SDA_PIN Pin = 26 // P0.26 SDA_PIN = 26 // P0.26
SCL_PIN Pin = 27 // P0.27 SCL_PIN = 27 // P0.27
) )
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN Pin = 47 // P1.15 SPI0_SCK_PIN = 47 // P1.15
SPI0_MOSI_PIN Pin = 45 // P1.13 SPI0_MOSI_PIN = 45 // P1.13
SPI0_MISO_PIN Pin = 46 // P1.14 SPI0_MISO_PIN = 46 // P1.14
) )
+18 -37
View File
@@ -4,58 +4,39 @@ package machine
const HasLowFrequencyCrystal = true const HasLowFrequencyCrystal = true
// Pins on the reel board // LEDs on the reel board
const ( const (
LED Pin = LED1 LED = LED1
LED1 Pin = LED_YELLOW LED1 = LED_YELLOW
LED2 Pin = LED_RED LED2 = LED_RED
LED3 Pin = LED_GREEN LED3 = LED_GREEN
LED4 Pin = LED_BLUE LED4 = LED_BLUE
LED_RED Pin = 11 LED_RED = 11
LED_GREEN Pin = 12 LED_GREEN = 12
LED_BLUE Pin = 41 LED_BLUE = 41
LED_YELLOW Pin = 13 LED_YELLOW = 13
EPD_BUSY_PIN Pin = 14
EPD_RESET_PIN Pin = 15
EPD_DC_PIN Pin = 16
EPD_CS_PIN Pin = 17
EPD_SCK_PIN Pin = 19
EPD_MOSI_PIN Pin = 20
POWER_SUPPLY_PIN Pin = 32
) )
// User "a" button on the reel board // User "a" button on the reel board
const ( const (
BUTTON Pin = 7 BUTTON = 7
) )
// UART pins // UART pins
const ( const (
UART_TX_PIN Pin = 6 UART_TX_PIN = 6
UART_RX_PIN Pin = 8 UART_RX_PIN = 8
) )
// I2C pins // I2C pins
const ( const (
SDA_PIN Pin = 26 SDA_PIN = 26
SCL_PIN Pin = 27 SCL_PIN = 27
) )
// SPI pins // SPI pins
const ( const (
SPI0_SCK_PIN Pin = 47 SPI0_SCK_PIN = 47
SPI0_MOSI_PIN Pin = 45 SPI0_MOSI_PIN = 45
SPI0_MISO_PIN Pin = 46 SPI0_MISO_PIN = 46
) )
// PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false)
// This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and
// other onboard peripherals.
func PowerSupplyActive(active bool) {
POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput})
if active {
POWER_SUPPLY_PIN.High()
} else {
POWER_SUPPLY_PIN.Low()
}
}
-16
View File
@@ -1,16 +0,0 @@
// +build bluepill nucleof103rb stm32f4disco
package machine
// Peripheral abstraction layer for the stm32.
const (
portA Pin = iota * 16
portB
portC
portD
portE
portF
portG
portH
)
+1 -1
View File
@@ -1,4 +1,4 @@
// +build stm32f4disco // +build stm32,stm32f4disco
package machine package machine
-96
View File
@@ -1,96 +0,0 @@
// +build sam,atsamd21,trinket_m0
package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PA08 // PWM available
D1 = PA02
D2 = PA09 // PWM available
D3 = PA07 // PWM available / UART0 RX
D4 = PA06 // PWM available / UART0 TX
D13 = PA10 // LED
)
// Analog pins
const (
A0 = D1
A1 = D2
A2 = D0
A3 = D3
A4 = D4
)
const (
LED = D13
)
// UART0 aka USBCDC pins
const (
USBCDC_DM_PIN = PA24
USBCDC_DP_PIN = PA25
)
// UART1 pins
const (
UART_TX_PIN = D4
UART_RX_PIN = D3
)
// UART1 on the Trinket M0.
var (
UART1 = UART{Bus: sam.SERCOM1_USART,
Buffer: NewRingBuffer(),
Mode: PinSERCOM,
IRQVal: sam.IRQ_SERCOM1,
}
)
//go:export SERCOM1_IRQHandler
func handleUART1() {
defaultUART1Handler()
}
// SPI pins
const (
SPI0_SCK_PIN = D3
SPI0_MOSI_PIN = D4
SPI0_MISO_PIN = D2
)
// SPI on the Trinket M0.
var (
SPI0 = SPI{Bus: sam.SERCOM0_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
)
// I2C pins
const (
SDA_PIN = D0 // SDA
SCL_PIN = D2 // SCL
)
// I2C on the Trinket M0.
var (
I2C0 = I2C{Bus: sam.SERCOM2_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOMAlt}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
I2S_SD_PIN = PA08
I2S_WS_PIN = NoPin // TODO: figure out what this is on Trinket M0.
)
+11 -12
View File
@@ -1,11 +1,10 @@
package machine package machine
import (
"runtime/volatile"
)
const bufferSize = 128 const bufferSize = 128
//go:volatile
type volatileByte byte
// RingBuffer is ring buffer implementation inspired by post at // RingBuffer is ring buffer implementation inspired by post at
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php // https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
// //
@@ -13,9 +12,9 @@ const bufferSize = 128
// members of a struct are not compiled correctly by TinyGo. // members of a struct are not compiled correctly by TinyGo.
// See https://github.com/tinygo-org/tinygo/issues/151 for details. // See https://github.com/tinygo-org/tinygo/issues/151 for details.
type RingBuffer struct { type RingBuffer struct {
rxbuffer [bufferSize]volatile.Register8 rxbuffer [bufferSize]volatileByte
head volatile.Register8 head volatileByte
tail volatile.Register8 tail volatileByte
} }
// NewRingBuffer returns a new ring buffer. // NewRingBuffer returns a new ring buffer.
@@ -25,15 +24,15 @@ func NewRingBuffer() *RingBuffer {
// Used returns how many bytes in buffer have been used. // Used returns how many bytes in buffer have been used.
func (rb *RingBuffer) Used() uint8 { func (rb *RingBuffer) Used() uint8 {
return uint8(rb.head.Get() - rb.tail.Get()) return uint8(rb.head - rb.tail)
} }
// Put stores a byte in the buffer. If the buffer is already // Put stores a byte in the buffer. If the buffer is already
// full, the method will return false. // full, the method will return false.
func (rb *RingBuffer) Put(val byte) bool { func (rb *RingBuffer) Put(val byte) bool {
if rb.Used() != bufferSize { if rb.Used() != bufferSize {
rb.head.Set(rb.head.Get() + 1) rb.head++
rb.rxbuffer[rb.head.Get()%bufferSize].Set(val) rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
return true return true
} }
return false return false
@@ -43,8 +42,8 @@ func (rb *RingBuffer) Put(val byte) bool {
// the method will return a false as the second value. // the method will return a false as the second value.
func (rb *RingBuffer) Get() (byte, bool) { func (rb *RingBuffer) Get() (byte, bool) {
if rb.Used() != 0 { if rb.Used() != 0 {
rb.tail.Set(rb.tail.Get() + 1) rb.tail++
return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true return byte(rb.rxbuffer[rb.tail%bufferSize]), true
} }
return 0, false return 0, false
} }
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32,!stm32f4disco // +build avr nrf sam stm32f103xx
package machine package machine
+3 -3
View File
@@ -41,9 +41,9 @@ const (
// All fields are optional and may not be required or used on a particular platform. // All fields are optional and may not be required or used on a particular platform.
type I2SConfig struct { type I2SConfig struct {
SCK Pin SCK uint8
WS Pin WS uint8
SD Pin SD uint8
Mode I2SMode Mode I2SMode
Standard I2SStandard Standard I2SStandard
ClockSource I2SClockSource ClockSource I2SClockSource
+9 -20
View File
@@ -1,36 +1,25 @@
package machine package machine
type PinConfig struct { type GPIOConfig struct {
Mode PinMode Mode GPIOMode
} }
// Pin is a single pin on a chip, which may be connected to other hardware type GPIO struct {
// devices. It can either be used directly as GPIO pin or it can be used in Pin uint8
// other peripherals like ADC, I2C, etc. }
type Pin int8
// NoPin explicitly indicates "not a pin". Use this pin if you want to leave one func (p GPIO) High() {
// of the pins in a peripheral unconfigured (if supported by the hardware).
const NoPin = Pin(-1)
// High sets this GPIO pin to high, assuming it has been configured as an output
// pin. It is hardware dependent (and often undefined) what happens if you set a
// pin to high that is not configured as an output pin.
func (p Pin) High() {
p.Set(true) p.Set(true)
} }
// Low sets this GPIO pin to low, assuming it has been configured as an output func (p GPIO) Low() {
// pin. It is hardware dependent (and often undefined) what happens if you set a
// pin to low that is not configured as an output pin.
func (p Pin) Low() {
p.Set(false) p.Set(false)
} }
type PWM struct { type PWM struct {
Pin Pin Pin uint8
} }
type ADC struct { type ADC struct {
Pin Pin Pin uint8
} }
+24 -25
View File
@@ -4,42 +4,41 @@ package machine
import ( import (
"device/avr" "device/avr"
"runtime/volatile"
) )
// Configure sets the pin to input or output. // Configure sets the pin to input or output.
func (p Pin) Configure(config PinConfig) { func (p GPIO) Configure(config GPIOConfig) {
if config.Mode == PinOutput { // set output bit if config.Mode == GPIO_OUTPUT { // set output bit
if p < 8 { if p.Pin < 8 {
avr.DDRD.SetBits(1 << uint8(p)) avr.DDRD.SetBits(1 << p.Pin)
} else { } else {
avr.DDRB.SetBits(1 << uint8(p-8)) avr.DDRB.SetBits(1 << (p.Pin - 8))
} }
} else { // configure input: clear output bit } else { // configure input: clear output bit
if p < 8 { if p.Pin < 8 {
avr.DDRD.ClearBits(1 << uint8(p)) avr.DDRD.ClearBits(1 << p.Pin)
} else { } else {
avr.DDRB.ClearBits(1 << uint8(p-8)) avr.DDRB.ClearBits(1 << (p.Pin - 8))
} }
} }
} }
// Get returns the current value of a GPIO pin. // Get returns the current value of a GPIO pin.
func (p Pin) Get() bool { func (p GPIO) Get() bool {
if p < 8 { if p.Pin < 8 {
val := avr.PIND.Get() & (1 << uint8(p)) val := avr.PIND.Get() & (1 << p.Pin)
return (val > 0) return (val > 0)
} else { } else {
val := avr.PINB.Get() & (1 << uint8(p-8)) val := avr.PINB.Get() & (1 << (p.Pin - 8))
return (val > 0) return (val > 0)
} }
} }
func (p Pin) getPortMask() (*volatile.Register8, uint8) { func (p GPIO) getPortMask() (*avr.Register8, uint8) {
if p < 8 { if p.Pin < 8 {
return avr.PORTD, 1 << uint8(p) return avr.PORTD, 1 << p.Pin
} else { } else {
return avr.PORTB, 1 << uint8(p-8) return avr.PORTB, 1 << (p.Pin - 8)
} }
} }
@@ -67,9 +66,9 @@ func InitPWM() {
// Configure configures a PWM pin for output. // Configure configures a PWM pin for output.
func (pwm PWM) Configure() { func (pwm PWM) Configure() {
if pwm.Pin < 8 { if pwm.Pin < 8 {
avr.DDRD.SetBits(1 << uint8(pwm.Pin)) avr.DDRD.SetBits(1 << pwm.Pin)
} else { } else {
avr.DDRB.SetBits(1 << uint8(pwm.Pin-8)) avr.DDRB.SetBits(1 << (pwm.Pin - 8))
} }
} }
@@ -166,7 +165,7 @@ func (i2c I2C) start(address uint8, write bool) {
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN)) avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
// Wait till start condition is transmitted. // Wait till start condition is transmitted.
for !avr.TWCR.HasBits(avr.TWCR_TWINT) { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
// Write 7-bit shifted peripheral address. // Write 7-bit shifted peripheral address.
@@ -183,7 +182,7 @@ func (i2c I2C) stop() {
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
// Wait for stop condition to be executed on bus. // Wait for stop condition to be executed on bus.
for !avr.TWCR.HasBits(avr.TWCR_TWSTO) { for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
} }
} }
@@ -196,7 +195,7 @@ func (i2c I2C) writeByte(data byte) {
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
// Wait till data is transmitted. // Wait till data is transmitted.
for !avr.TWCR.HasBits(avr.TWCR_TWINT) { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
} }
@@ -206,7 +205,7 @@ func (i2c I2C) readByte() byte {
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA) avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
// Wait till read request is transmitted. // Wait till read request is transmitted.
for !avr.TWCR.HasBits(avr.TWCR_TWINT) { for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
} }
return byte(avr.TWDR.Get()) return byte(avr.TWDR.Get())
@@ -240,7 +239,7 @@ func (uart UART) Configure(config UARTConfig) {
// WriteByte writes a byte of data to the UART. // WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error { func (uart UART) WriteByte(c byte) error {
// Wait until UART buffer is not busy. // Wait until UART buffer is not busy.
for !avr.UCSR0A.HasBits(avr.UCSR0A_UDRE0) { for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
} }
avr.UDR0.Set(c) // send char avr.UDR0.Set(c) // send char
return nil return nil
@@ -252,7 +251,7 @@ func handleUSART_RX() {
data := avr.UDR0.Get() data := avr.UDR0.Get()
// Ensure no error. // Ensure no error.
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) { if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
// Put data from UDR register into buffer. // Put data from UDR register into buffer.
UART0.Receive(byte(data)) UART0.Receive(byte(data))
} }

Some files were not shown because too many files have changed in this diff Show More