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 ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
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:
steps:
- restore_cache:
keys:
- llvm-source-8-v5
- llvm-source-8-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-v5
key: llvm-source-8-v2
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:
parameters:
llvm:
type: string
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-8"
llvm: <<parameters.llvm>>
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- dep
- run: go install .
- run: go test -v ./transform .
- run: go test -v
- run: make gen-device -j4
- run: make smoketest RISCV=0
- smoketest
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
build-linux:
steps:
@@ -96,12 +132,12 @@ commands:
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-{{ checksum "Gopkg.lock" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-8-linux-v7
- llvm-build-8-linux-v4
- run:
name: "Build LLVM"
command: |
@@ -119,7 +155,7 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-linux-v7
key: llvm-build-8-linux-v4
paths:
llvm-build
- run:
@@ -128,6 +164,7 @@ commands:
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/wasm-ld /go/bin/wasm-ld-8
- dep
- run:
name: "Test TinyGo"
command: make test
@@ -139,11 +176,10 @@ commands:
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
@@ -151,12 +187,7 @@ commands:
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- run:
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
- smoketest
build-macos:
steps:
- checkout
@@ -164,27 +195,20 @@ commands:
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
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
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-8-macos-v5
- llvm-source-8-macos-v2
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-8-macos-v5
key: llvm-source-8-macos-v2
paths:
- llvm-project
- llvm
- restore_cache:
keys:
- llvm-build-8-macos-v6
- llvm-build-8-macos-v3
- run:
name: "Build LLVM"
command: |
@@ -196,13 +220,16 @@ commands:
make llvm-build
fi
- save_cache:
key: llvm-build-8-macos-v6
key: llvm-build-8-macos-v3
paths:
llvm-build
- run:
name: "Create LLVM symlinks"
command: |
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
- run:
name: "Install Go dependencies"
command: dep ensure --vendor-only
- run:
name: "Test TinyGo"
command: make test
@@ -220,39 +247,34 @@ commands:
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run:
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
- smoketest-no-avr
jobs:
test-llvm8-go111:
docker:
- image: circleci/golang:1.11-stretch
- image: circleci/golang:1.11
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- test-linux
- test-linux:
llvm: "-8"
test-llvm8-go112:
docker:
- image: circleci/golang:1.12-stretch
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- test-linux
- test-linux:
llvm: "-8"
build-linux:
docker:
- image: circleci/golang:1.12-stretch
- image: circleci/golang:1.12
working_directory: /go/src/github.com/tinygo-org/tinygo
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
working_directory: ~/go/src/github.com/tinygo-org/tinygo
steps:
- build-macos
+2 -4
View File
@@ -5,12 +5,10 @@ src/device/avr/*.ld
src/device/avr/*.s
src/device/nrf/*.go
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/*.s
src/device/sam/*.go
src/device/sam/*.s
vendor
llvm
llvm-build
+1 -1
View File
@@ -9,7 +9,7 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "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"]
path = lib/compiler-rt
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.
* Go (1.11+)
* [dep](https://golang.github.io/dep/)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -26,20 +27,16 @@ on a different system like Mac.
## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone
the git repository). Then, inside the directory, download the LLVM source:
The first step is to download the TinyGo sources. Then, inside the directory,
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
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
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
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
---
- **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.
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.
### 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.
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).
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.
FROM golang:1.12 AS tinygo-base
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
FROM golang:latest AS tinygo-base
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 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
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /tinygo/ && \
go install /tinygo/
RUN cd /go/src/github.com/tinygo-org/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.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
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 - && \
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 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
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
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 install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
apt-get remove -y python3 && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
apt-get clean
@@ -51,32 +54,32 @@ RUN cd /tinygo/ && \
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
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 install -y apt-utils python3 make clang-8 && \
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 clean
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
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 install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
make gen-device && \
apt-get remove -y python3 && \
apt-get remove -y python3 make && \
apt-get autoremove -y && \
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
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`.
LLVM_BUILDDIR ?= llvm-build
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
CLANG_SRC ?= llvm/tools/clang
LLD_SRC ?= llvm/tools/lld
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
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)
# 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_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))
endif
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_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
clean:
@@ -40,8 +38,7 @@ fmt:
fmt-check:
@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-sifive gen-device-stm32
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
gen-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
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:
./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
# Get LLVM sources.
llvm-project/README.md:
git clone -b release/8.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
llvm/README.txt:
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
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.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
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
llvm-build/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
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR); ninja
llvm-build: llvm-build/build.ninja
cd llvm-build; ninja
# Build the Go compiler.
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 .
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm ./transform .
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
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
release: build/tinygo gen-device
@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)
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.
@@ -17,8 +17,8 @@ import (
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
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.
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 Feather M0](https://www.adafruit.com/product/2772)
* [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)
* [BBC micro:bit](https://microbit.org/)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [BBC:Microbit](https://microbit.org/)
* [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)
* [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)
-361
View File
@@ -35,8 +35,6 @@ type cgoPackage struct {
globals map[string]globalInfo
typedefs map[string]*typedefInfo
elaboratedTypes map[string]*elaboratedTypeInfo
enums map[string]enumInfo
anonStructNum int
}
// 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
// (struct, union) found in the C AST.
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
pos token.Pos
}
@@ -160,7 +140,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
globals: map[string]globalInfo{},
typedefs: map[string]*typedefInfo{},
elaboratedTypes: map[string]*elaboratedTypeInfo{},
enums: map[string]enumInfo{},
}
// 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.
p.addElaboratedTypes()
// Add enum types and enum constants for C enums.
p.addEnumTypes()
// Patch the AST to use the declared types and functions.
for _, f := range files {
astutil.Apply(f, p.walker, nil)
@@ -594,343 +570,6 @@ func (p *cgoPackage) addElaboratedTypes() {
}
obj.Decl = 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)
}
+69 -208
View File
@@ -49,13 +49,9 @@ GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(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_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
*/
import "C"
@@ -505,94 +501,90 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
switch underlying.kind {
case C.CXType_Record:
return p.makeASTType(underlying, pos)
case C.CXType_Enum:
return p.makeASTType(underlying, pos)
default:
panic("unknown elaborated type")
}
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
if name == "" {
// Anonymous record, probably inside a typedef.
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
if bitfieldList != nil {
// This struct has bitfields, so we have to declare it as a
// named type (for bitfield getters/setters to work).
p.anonStructNum++
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
var cgoName string
switch C.tinygo_clang_getCursorKind(cursor) {
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)
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
}
return typeExpr
} else {
var cgoName string
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
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) {
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{
typeExpr: typeExpr,
pos: pos,
bitfields: bitfieldList,
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
case C.CXCursor_UnionDecl:
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...)
}
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
Fields: fieldList,
},
pos: pos,
}
default:
panic("unreachable")
}
}
case C.CXType_Enum:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
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,
}
return &ast.Ident{
NamePos: pos,
Name: "C." + cgoName,
}
}
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
// type name.
typeName = "C." + spelling
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
}
return &ast.Ident{
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
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
fieldList *ast.FieldList
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
fieldList *ast.FieldList
pkg *cgoPackage
})
fieldList := passed.fieldList
p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
panic("expected field inside cursor")
}
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)
pos := p.getCursorPosition(c)
field := &ast.Field{
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{
&ast.Ident{
NamePos: pos,
NamePos: p.getCursorPosition(c),
Name: name,
Obj: &ast.Object{
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)
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) {
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"
"os"
"os/exec"
"runtime"
"strings"
)
@@ -16,16 +15,6 @@ var commands = map[string][]string{
"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 {
for _, cmdName := range cmdNames {
cmd := exec.Command(cmdName, args...)
@@ -37,7 +26,6 @@ func execCommand(cmdNames []string, args ...string) error {
// this command was not found, try the next
continue
}
return err
}
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.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("lookupPanic", nil, "")
c.createRuntimeCall("lookuppanic", nil, "")
c.builder.CreateUnreachable()
// 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
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// 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() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
@@ -71,9 +71,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = high.Type()
}
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
capacityType = max.Type()
}
if capacityType != capacity.Type() {
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, "")
}
}
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")
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
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "")
c.createRuntimeCall("slicepanic", nil, "")
c.builder.CreateUnreachable()
// 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
// pointer dereferences exist in valid Go code.
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.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
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.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("nilPanic", nil, "")
c.createRuntimeCall("nilpanic", nil, "")
c.builder.CreateUnreachable()
// 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
// parameter list.
// paramter list.
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
@@ -163,7 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
switch t.TypeKind() {
case llvm.StructTypeKind:
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := llvm.ConstNull(t)
value := c.getZeroValue(t)
for i, subtyp := range t.StructElementTypes() {
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
+28 -181
View File
@@ -4,7 +4,6 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"golang.org/x/tools/go/ssa"
@@ -13,63 +12,60 @@ import (
// emitMakeChan returns a new channel value for the given channel type.
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
chanType := c.getLLVMType(expr.Type())
size := c.targetData.TypeAllocSize(chanType.ElementType())
chanType := c.mod.GetTypeByName("runtime.channel")
size := c.targetData.TypeAllocSize(chanType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
ptr = c.builder.CreateBitCast(ptr, chanType, "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)
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
return ptr, nil
}
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
valueType := c.getLLVMType(instr.X.Type())
ch := c.getValue(frame, instr.Chan)
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
valueType := c.getLLVMType(instr.X.Type())
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())
c.builder.CreateStore(chanValue, valueAlloca)
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
// Do the send.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
// Make sure CoroSplit includes the alloca in the coroutine frame.
// This is a bit dirty, but it works (at least in LLVM 8).
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
}
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering.
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
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)
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
// 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.
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
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")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
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.
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)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// 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, "")
}
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
}
-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 (
"errors"
"fmt"
"go/ast"
"go/build"
"go/constant"
"go/token"
@@ -27,50 +26,22 @@ func init() {
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.
type Config struct {
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)
Features []string // LLVM CPU features
GOOS string //
GOARCH string //
GC string // garbage collection strategy
Scheduler string // scheduler implementation ("coroutines" or "tasks")
PanicStrategy string // panic strategy ("print" or "trap")
PanicStrategy string // panic strategy ("abort" or "trap")
CFlags []string // cflags 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
VerifyIR bool // run extra checks on the IR
Debug bool // add debug symbols for gdb
GOROOT string // GOROOT
TINYGOROOT string // GOROOT for TinyGo
GOPATH string // GOPATH, like `go env GOPATH`
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 {
@@ -91,7 +62,6 @@ type Compiler struct {
interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
diagnostics []error
astComments map[string]*ast.CommentGroup
}
type Frame struct {
@@ -108,7 +78,6 @@ type Frame struct {
deferFuncs map[*ir.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ir.Function]int
selectRecvBuf map[*ssa.Select]llvm.Value
}
type Phi struct {
@@ -132,11 +101,7 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
if err != nil {
return nil, err
}
features := ""
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.machine = target.CreateTargetMachine(config.Triple, config.CPU, "", llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
c.targetData = c.machine.CreateTargetData()
c.ctx = llvm.NewContext()
@@ -184,21 +149,15 @@ func (c *Compiler) TargetData() llvm.TargetData {
// selectGC picks an appropriate GC strategy if none was provided.
func (c *Compiler) selectGC() string {
if c.GC != "" {
return c.GC
gc := c.GC
if gc == "" {
gc = "leaking"
}
return "conservative"
return gc
}
// selectScheduler picks an appropriate scheduler for the target if none was
// given.
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"
func (c *Compiler) gcIsPrecise() bool {
return c.GC == "precise"
}
// 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 {
return []error{err}
}
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
lprogram := &loader.Program{
Build: &build.Context{
GOARCH: c.GOARCH,
@@ -227,7 +185,7 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
Compiler: "gc", // must be one of the recognized compilers
BuildTags: buildTags,
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
},
OverlayBuild: &build.Context{
GOARCH: c.GOARCH,
@@ -237,31 +195,24 @@ func (c *Compiler) Compile(mainPath string) []error {
CgoEnabled: true,
UseAllFiles: false,
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 {
// 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/"):]
}
ShouldOverlay: func(path string) bool {
switch path {
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
return path
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
return true
default:
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
return path
return true
} else if path == "syscall" {
for _, tag := range c.BuildTags {
if tag == "baremetal" || tag == "darwin" {
return path
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
return true
}
}
}
}
return ""
return false
},
TypeChecker: types.Config{
Sizes: &StdSizes{
@@ -270,12 +221,10 @@ func (c *Compiler) Compile(mainPath string) []error {
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
},
},
Dir: wd,
TINYGOROOT: c.TINYGOROOT,
CFlags: c.CFlags,
ClangHeaders: c.ClangHeaders,
Dir: wd,
TINYGOROOT: c.TINYGOROOT,
CFlags: c.CFlags,
}
if strings.HasSuffix(mainPath, ".go") {
_, err = lprogram.ImportFile(mainPath)
if err != nil {
@@ -287,13 +236,12 @@ func (c *Compiler) Compile(mainPath string) []error {
return []error{err}
}
}
_, err = lprogram.Import("runtime", "")
if err != nil {
return []error{err}
}
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
err = lprogram.Parse()
if err != nil {
return []error{err}
}
@@ -316,7 +264,39 @@ func (c *Compiler) Compile(mainPath string) []error {
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.
for _, f := range c.ir.Functions {
@@ -364,15 +344,13 @@ func (c *Compiler) Compile(mainPath string) []error {
// would be optimized away.
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
// Make sure these functions are kept in tact during TinyGo transformation passes.
for _, name := range functionsUsedInTransforms {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.ExternalLinkage)
}
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
// Load some attributes
getAttr := func(attrName string) llvm.Attribute {
@@ -392,15 +370,6 @@ func (c *Compiler) Compile(mainPath string) []error {
// See emitNilCheck in asserts.go.
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
// pointer arithmetic is happening in the Go implementation.
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
@@ -433,22 +402,6 @@ func (c *Compiler) Compile(mainPath string) []error {
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 {
switch typ := goType.(type) {
case *types.Array:
@@ -477,7 +430,7 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
case types.Complex128:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString:
return c.getLLVMRuntimeType("_string")
return c.mod.GetTypeByName("runtime._string")
case types.Uintptr:
return c.uintptrType
case types.UnsafePointer:
@@ -486,23 +439,16 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
panic("unknown basic type: " + typ.String())
}
case *types.Chan:
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0)
return llvm.PointerType(c.mod.GetTypeByName("runtime.channel"), 0)
case *types.Interface:
return c.getLLVMRuntimeType("_interface")
return c.mod.GetTypeByName("runtime._interface")
case *types.Map:
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0)
case *types.Named:
if st, ok := typ.Underlying().(*types.Struct); ok {
// Structs are a special case. While other named types are ignored
// 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 _, ok := typ.Underlying().(*types.Struct); ok {
llvmType := c.mod.GetTypeByName(typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
if llvmType.IsNil() {
llvmType = c.ctx.StructCreateNamed(llvmName)
underlying := c.getLLVMType(st)
llvmType.StructSetBody(underlying.StructElementTypes(), false)
panic("underlying type not found: " + typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
}
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.
func isPointer(typ types.Type) bool {
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.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
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")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
@@ -847,10 +824,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
if c.DumpSSA {
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() {
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
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.
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
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.
@@ -1021,56 +990,42 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
} else {
frame.locals[instr] = value
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
c.trackExpr(frame, instr, value)
}
}
case *ssa.DebugRef:
// ignore
case *ssa.Defer:
c.emitDefer(frame, instr)
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.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, c.getValue(frame, param))
}
// Start a new goroutine.
if callee := instr.Call.StaticCallee(); callee != nil {
// 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")
if !calleeFn.IsExported() {
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
}
c.createCall(calleeValue, params, "")
case *ssa.If:
cond := c.getValue(frame, instr.Cond)
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]))
} else {
// 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 {
val := c.getValue(frame, result)
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
@@ -1116,7 +1071,12 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
// nothing to store
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:
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 {
name := fn.RelString(nil)
switch {
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
return c.emitReadRegister(name, instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
case name == "device/arm.ReadRegister":
return c.emitReadRegister(instr.Args)
case name == "device/arm.Asm" || name == "device/avr.Asm":
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)
case strings.HasPrefix(name, "device/arm.SVCall"):
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)
// This is a func value, which cannot be called directly. We have to
// 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")
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)
case *ssa.Global:
value := c.getGlobal(expr)
value := c.ir.GetGlobal(expr).LLVMGlobal
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 value
@@ -1410,6 +1373,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
switch expr := expr.(type) {
case *ssa.Alloc:
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
var buf llvm.Value
if expr.Heap {
size := c.targetData.TypeAllocSize(typ)
// 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.
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)
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), "")
return buf, nil
} else {
buf := c.createEntryBlockAlloca(typ, expr.Comment)
buf = c.builder.CreateAlloca(typ, expr.Comment)
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:
x := c.getValue(frame, expr.X)
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)
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
case *ssa.Extract:
if _, ok := expr.Tuple.(*ssa.Select); ok {
return c.getChanSelectResult(frame, expr), nil
}
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:
value := c.getValue(frame, expr.X)
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
// operation it's much easier to bitcast it through an alloca.
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)
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast")
result := c.builder.CreateLoad(bitcast, "union.result")
c.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
return c.builder.CreateLoad(bitcast, ""), nil
}
result := c.builder.CreateExtractValue(value, expr.Field, "")
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
// 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)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep")
result := c.builder.CreateLoad(ptr, "index.load")
c.emitLifetimeEnd(allocaPtr, allocaSize)
return result, nil
return c.builder.CreateLoad(ptr, "index.load"), nil
case *ssa.IndexAddr:
val := c.getValue(frame, expr.X)
index := c.getValue(frame, expr.Index)
@@ -1661,11 +1619,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
}
// Bounds checking.
lenType := expr.Len.Type().(*types.Basic)
capType := expr.Cap.Type().(*types.Basic)
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
// Allocate the backing array.
// TODO: escape analysis
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
if err != nil {
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())
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key")
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value")
mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key")
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")
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, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
return tuple, nil
}
case *ssa.Phi:
@@ -1725,22 +1682,43 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
var iteratorType llvm.Type
switch typ := expr.X.Type().Underlying().(type) {
case *types.Basic: // string
iteratorType = c.getLLVMRuntimeType("stringIterator")
iteratorType = c.mod.GetTypeByName("runtime.stringIterator")
case *types.Map:
iteratorType = c.getLLVMRuntimeType("hashmapIterator")
iteratorType = c.mod.GetTypeByName("runtime.hashmapIterator")
default:
panic("unknown type in range: " + typ.String())
}
it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it")
c.builder.CreateStore(llvm.ConstNull(iteratorType), it)
it := c.builder.CreateAlloca(iteratorType, "range.it")
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
return it, nil
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:
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)
var lowType, highType, maxType *types.Basic
var low, high, max llvm.Value
var lowType, highType *types.Basic
var low, high llvm.Value
if expr.Low != nil {
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]
}
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) {
case *types.Pointer: // pointer to array
// slice an array
@@ -1793,31 +1757,25 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() {
high = llvmLen
}
if max.IsNil() {
max = llvmLen
}
indices := []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
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
// 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) {
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, "")
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
low = c.builder.CreateTrunc(low, c.uintptrType, "")
}
sliceLen := c.builder.CreateSub(high, low, "slice.len")
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{
llvm.Undef(slicePtr.Type()),
@@ -1837,11 +1795,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
if high.IsNil() {
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
// 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) {
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}, "")
newLen := c.builder.CreateSub(high, low, "")
newCap := c.builder.CreateSub(max, low, "")
newCap := c.builder.CreateSub(oldCap, low, "")
slice := c.ctx.ConstStruct([]llvm.Value{
llvm.Undef(newPtr.Type()),
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())
}
// 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, "")
oldLen := c.builder.CreateExtractValue(value, 1, "")
if high.IsNil() {
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
// 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}, "")
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, newLen, 1, "")
return str, nil
@@ -2276,7 +2223,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
global.SetUnnamedAddr(true)
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
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
} else if typ.Kind() == types.UnsafePointer {
if !expr.IsNil() {
@@ -2314,12 +2261,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
if expr.Value != nil {
panic("expected nil chan constant")
}
return llvm.ConstNull(c.getLLVMType(expr.Type()))
return c.getZeroValue(c.getLLVMType(expr.Type()))
case *types.Signature:
if expr.Value != nil {
panic("expected nil signature constant")
}
return llvm.ConstNull(c.getLLVMType(expr.Type()))
return c.getZeroValue(c.getLLVMType(expr.Type()))
case *types.Interface:
if expr.Value != nil {
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.ConstPointerNull(c.i8ptrType),
}
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
return llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
case *types.Pointer:
if expr.Value != nil {
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")
}
llvmType := c.getLLVMType(typ)
return llvm.ConstNull(llvmType)
return c.getZeroValue(llvmType)
default:
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() {
case types.Byte:
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil
default:
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() {
case types.Byte:
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
case types.Rune:
return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil
default:
panic("unexpected type in string to slice conversion")
return llvm.Value{}, c.makeError(pos, "todo: convert from string: "+elemType.String())
}
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())
}
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 {
// 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") {
// CGo function pointer. The cgo part has rewritten CGo function
// pointers as stub global variables of the form:
// var C.add unsafe.Pointer
// Instead of a load from the global, create a bitcast of the
// function pointer itself.
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName
name := globalName[:len(globalName)-len("$funcaddr")]
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
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 {
c.emitNilCheck(frame, x, "deref")
load := c.builder.CreateLoad(x, "")
if c.ir.IsVolatile(valType) {
// Volatile load, for memory-mapped registers.
load.SetVolatile(true)
}
return load, nil
}
case token.XOR: // ^x, toggle all bits in integer
@@ -2713,7 +2660,7 @@ func (c *Compiler) ExternalInt64AsPtr() error {
// correct calling convention.
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
entryBlock := c.ctx.AddBasicBlock(externalFn, "entry")
entryBlock := llvm.AddBasicBlock(externalFn, "entry")
c.builder.SetInsertPointAtEnd(entryBlock)
var callParams []llvm.Value
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)
// 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")
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.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
deferFrame := c.getZeroValue(deferFrameType)
for i, value := range values {
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.
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
c.builder.CreateStore(deferFrame, alloca)
if c.needsStackObjects() {
c.trackPointer(alloca)
}
// Push it on top of the linked list by replacing deferPtr.
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
@@ -157,10 +154,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// }
// Create loop.
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
@@ -192,7 +189,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Create switch case, for example:
// case 0:
// // 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)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
@@ -203,7 +200,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
}
// 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 {
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
@@ -234,7 +231,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
// Direct call.
// 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 {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
@@ -263,7 +260,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
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()
for i := 0; i < params.Len(); i++ {
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.
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
signatures := map[string]*funcSignatureInfo{}
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
if global.Type() != funcValueWithSignaturePtr {
@@ -152,19 +152,19 @@ func (c *Compiler) LowerFuncValues() {
// There are multiple functions used in a func value that
// implement this signature.
// What we'll do is transform the following:
// rawPtr := runtime.getFuncPtr(func.ptr)
// if rawPtr == nil {
// runtime.nilPanic()
// rawPtr := runtime.getFuncPtr(fn)
// if func.rawPtr == nil {
// runtime.nilpanic()
// }
// result := rawPtr(...args, func.context)
// result := func.rawPtr(...args, func.context)
// into this:
// if false {
// runtime.nilPanic()
// runtime.nilpanic()
// }
// var result // Phi
// switch fn.id {
// case 0:
// runtime.nilPanic()
// runtime.nilpanic()
// case 1:
// result = call first implementation...
// case 2:
@@ -175,111 +175,95 @@ func (c *Compiler) LowerFuncValues() {
// Remove some casts, checks, and the old call which we're going
// to replace.
for _, callIntPtr := range getUses(getFuncPtrCall) {
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
for _, inttoptr := range getUses(callIntPtr) {
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() {
var funcCall llvm.Value
for _, inttoptr := range getUses(getFuncPtrCall) {
if inttoptr.IsAIntToPtrInst().IsNil() {
panic("expected inttoptr")
}
for _, ptrUse := range getUses(callIntPtr) {
for _, ptrUse := range getUses(inttoptr) {
if !ptrUse.IsABitCastInst().IsNil() {
for _, bitcastUse := range getUses(ptrUse) {
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().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:
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "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 {
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
return c.builder.CreateCall(funcPtr, params, "")
}, functions)
ptrUse.EraseFromParentAsInstruction()
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
if !funcCall.IsNil() {
panic("multiple calls on a single runtime.getFuncPtr")
}
funcCall = ptrUse
} else {
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()
}
}
}
}
// 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
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.selectScheduler() {
case "coroutines":
if c.GOARCH == "wasm" {
return funcValueSwitch
case "tasks":
} else {
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}
funcValueScalar = funcPtr
case funcValueSwitch:
sigGlobal := c.getTypeCode(sig)
sigGlobal := c.getFuncSignature(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
@@ -78,6 +73,22 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
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
// a cheap operation.
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
// 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, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
sigGlobal := c.getFuncSignature(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
@@ -115,7 +126,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.getLLVMRuntimeType("funcValue")
return c.mod.GetTypeByName("runtime.funcValue")
default:
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
// 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
// scheduler at runtime. It uses coroutine support in LLVM for this
// transformation: https://llvm.org/docs/Coroutines.html
@@ -73,7 +62,7 @@ package compiler
// llvm.suspend(hdl) // suspend point
// println("some other operation")
// 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
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
// println("done", *i)
@@ -117,69 +106,10 @@ type asyncFunc struct {
unreachableBlock llvm.BasicBlock
}
// LowerGoroutines performs some IR transformations necessary to support
// goroutines. It does something different based on whether it uses the
// coroutine or the tasks implementation of goroutines, and whether goroutines
// are necessary at all.
// LowerGoroutines is a pass called during optimization that transforms the IR
// into one where all blocking functions are turned into goroutines and blocking
// calls into await calls.
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()
if err != nil {
return err
@@ -214,6 +144,12 @@ func (c *Compiler) lowerCoroutines() error {
// main.main was set to external linkage during IR construction. Set it to
// internal linkage to enable interprocedural optimizations.
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
}
@@ -237,9 +173,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
if !sleep.IsNil() {
worklist = append(worklist, sleep)
}
deadlock := c.mod.NamedFunction("runtime.deadlock")
if !deadlock.IsNil() {
worklist = append(worklist, deadlock)
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
if !deadlockStub.IsNil() {
worklist = append(worklist, deadlockStub)
}
chanSend := c.mod.NamedFunction("runtime.chanSend")
if !chanSend.IsNil() {
@@ -275,25 +211,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Add all callees to the worklist.
for _, use := range getUses(f) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
for _, call := range getUses(use) {
if use.IsConstant() && use.Opcode() == llvm.BitCast {
bitcastUses := getUses(use)
for _, call := range bitcastUses {
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
// starting a goroutine is not a blocking operation.
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() {
// Not a call instruction. Maybe a store to a global? In any
// 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
// scheduler is necessary as it can be called directly.
for _, use := range getUses(makeGoroutine) {
// Input param must be const ptrtoint of function.
ptrtoint := use.Operand(0)
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
panic("expected const ptrtoint operand of runtime.makeGoroutine")
// Input param must be const bitcast of function.
bitcast := use.Operand(0)
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
panic("expected const bitcast operand of runtime.makeGoroutine")
}
goroutine := ptrtoint.Operand(0)
goroutine := bitcast.Operand(0)
if _, ok := asyncFuncs[goroutine]; ok {
needsScheduler = true
break
@@ -364,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Transform all async functions into coroutines.
for _, f := range asyncList {
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
continue
}
@@ -381,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
if !inst.IsACallInst().IsNil() {
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
}
asyncCalls = append(asyncCalls, inst)
@@ -393,7 +321,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// Coroutine setup.
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")
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
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")
}
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")
// 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")
c.builder.SetInsertPointBefore(inst)
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
}
// Suspend.
@@ -464,20 +389,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointBefore(inst)
var parentHandle llvm.Value
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())
}
}
parentHandle := f.LastParam()
// Store return values.
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
// parent handle. The parent coroutine has made an alloca that
// 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")
c.builder.CreateStore(inst.Operand(0), alloca)
default:
@@ -505,7 +417,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
// behavior somehow (with the unreachable instruction).
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
llvm.ConstNull(c.ctx.TokenType()),
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
}, "ret")
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
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()
}
// Transform calls to runtime.deadlock into coroutine suspends (without
// Transform calls to runtime.deadlockStub into coroutine suspends (without
// resume).
for _, deadlockCall := range getUses(deadlock) {
for _, deadlockCall := range getUses(deadlockStub) {
// deadlockCall must be a call instruction.
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
@@ -576,7 +488,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
c.builder.SetInsertPointBefore(deadlockCall)
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
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.builder.SetInsertPointBefore(deadlockCall)
@@ -643,14 +555,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
for _, goroutine := range getUses(makeGoroutine) {
ptrtointIn := goroutine.Operand(0)
origFunc := ptrtointIn.Operand(0)
bitcastIn := goroutine.Operand(0)
origFunc := bitcastIn.Operand(0)
uses := getUses(goroutine)
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
}
inttoptrOut := uses[0]
uses = getUses(inttoptrOut)
bitcastOut := uses[0]
uses = getUses(bitcastOut)
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
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.CreateCall(origFunc, params, "")
realCall.EraseFromParentAsInstruction()
inttoptrOut.EraseFromParentAsInstruction()
bitcastOut.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
//
// 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)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
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)
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
+8 -8
View File
@@ -163,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Collect all type codes.
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
var typesInInterfaces []llvm.Value
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
switch global.Type() {
@@ -603,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
// TODO: debug info
// Create all used basic blocks.
entry := p.ctx.AddBasicBlock(fn, "entry")
thenBlock := p.ctx.AddBasicBlock(fn, "then")
elseBlock := p.ctx.AddBasicBlock(fn, "else")
entry := llvm.AddBasicBlock(fn, "entry")
thenBlock := llvm.AddBasicBlock(fn, "then")
elseBlock := llvm.AddBasicBlock(fn, "else")
// Add all possible types as cases.
p.builder.SetInsertPointAtEnd(entry)
@@ -661,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// TODO: debug info
// 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
// possible types are checked).
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
defaultBlock := llvm.AddBasicBlock(fn, "default")
p.builder.SetInsertPointAtEnd(defaultBlock)
p.builder.CreateUnreachable()
@@ -684,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
// Define all possible functions that can be called.
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)
// 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)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
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, itfValue, 1, "")
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
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "reflect/types.type:" + getTypeCodeName(typ)
globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
// Create a new typecode global.
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 = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global.SetGlobalConstant(true)
}
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
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) string {
name := ""
if named, ok := t.(*types.Named); ok {
name = "~" + named.String() + ":"
t = t.Underlying()
}
switch t := t.(type) {
case *types.Named:
return "named:" + t.String()
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:
var kind string
switch t.Kind() {
@@ -181,21 +108,21 @@ func getTypeCodeName(t types.Type) string {
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + kind
return "basic:" + name + kind
case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem())
return "chan:" + name + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}"
return "map:" + name + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
return "pointer:" + getTypeCodeName(t.Elem())
return "pointer:" + name + getTypeCodeName(t.Elem())
case *types.Signature:
params := make([]string, t.Params().Len())
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++ {
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:
return "slice:" + getTypeCodeName(t.Elem())
return "slice:" + name + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
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")
}
for i := 0; i < t.NumFields(); i++ {
embedded := ""
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) + "`"
}
elems[i] = getTypeCodeName(t.Field(i).Type())
}
return "struct:" + "{" + strings.Join(elems, ",") + "}"
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
default:
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)
if ms.Len() == 0 {
// 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())
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
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.
c.builder.SetInsertPointAtEnd(nextBlock)
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 {
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
import (
"reflect"
"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
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
currentBlock := c.builder.GetInsertBlock()
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.
// after the last instruction in the current block. Also, it adds lifetime
// information to 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
// end the lifetime using emitLifetimeEnd after you're done with it.
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
alloca = c.createEntryBlockAlloca(t, name)
// end the lifetime after you're done with it.
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
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")
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
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
// first if it doesn't exist yet.
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
@@ -154,48 +134,3 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
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
// will be zeroed by the hashmap get implementation if the key is not
// 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.
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
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
} else {
// 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
// value if the key doesn't exist in the hashmap.
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
if commaOk {
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) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
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, "")
} else if hashmapIsBinaryKey(keyType) {
// 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)
params := []llvm.Value{m, keyPtr, valuePtr}
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
} 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 {
@@ -82,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
c.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} 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)
params := []llvm.Value{m, keyPtr}
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
return nil
} 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) {
case *types.Basic:
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
case *types.Struct:
for i := 0; i < keyType.NumFields(); i++ {
fieldType := keyType.Field(i).Type().Underlying()
+143 -21
View File
@@ -3,7 +3,6 @@ package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
@@ -23,11 +22,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.replacePanicsWithTrap() // -panic=trap
}
// run a check of all of our code
if c.VerifyIR {
c.checkModule()
}
// Run function passes for each function.
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
defer funcPasses.Dispose()
@@ -43,15 +37,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses := llvm.NewPassManager()
defer goPasses.Dispose()
goPasses.AddGlobalOptimizerPass()
goPasses.AddGlobalDCEPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
transform.OptimizeMaps(c.mod)
c.OptimizeMaps()
c.OptimizeStringToBytes()
transform.OptimizeAllocs(c.mod)
c.OptimizeAllocs()
c.LowerInterfaces()
c.LowerFuncValues()
@@ -61,7 +56,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
transform.OptimizeAllocs(c.mod)
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// 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()
// doesn't get lowered.
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)
modPasses.Run(c.mod)
hasGCPass := c.addGlobalsBitmap()
hasGCPass = c.makeGCStackSlots() || hasGCPass
if hasGCPass {
if c.gcIsPrecise() {
c.addGlobalsBitmap()
if err := c.Verify(); err != nil {
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
// possible. This optimizes the following pattern:
// 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.
func (c *Compiler) isReadOnly(value llvm.Value) bool {
uses := getUses(value)
+73 -406
View File
@@ -1,43 +1,10 @@
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 (
"encoding/binary"
"go/ast"
"math/big"
"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{
"bool": 1,
"int": 2,
@@ -59,72 +26,6 @@ var basicTypes = map[string]int64{
"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) {
fn := c.mod.NamedFunction("reflect.ValueOf")
if fn.IsNil() {
@@ -137,21 +38,13 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
}
// Assign typecodes the way the reflect package expects.
state := typeCodeAssignmentState{
fallbackIndex: 1,
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,
}
fallbackIndex := 1
namedTypes := make(map[string]int)
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() {
// TODO: support this in some way, using a side table for example.
// 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()
}
// 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
// reflect package. Also see getTypeCodeName, which serializes types to a string
// 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.
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 := ""
if class == "named" {
name = value
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
class, value = getClassAndValueFromTypeCode(typecode)
if value[0] == '~' {
name = value[1:strings.IndexByte(value, ':')]
value = value[len(name)+2:]
}
if class == "basic" {
// 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.
num, ok := basicTypes[value]
if !ok {
panic("invalid basic type: " + value)
panic("invalid basic type: " + id)
}
if name != "" {
// 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)
} else {
// Non-baisc types use the following bit pattern:
// Complex types use the following bit pattern:
// ...nxxx1
// where xxx indicates the non-basic type. The upper bits contain
// whatever the type contains. Types that wrap a single other type
// (channel, interface, pointer, slice) just contain the bits of the
// wrapped type. Other types (like struct) need more fields and thus
// cannot be encoded as a simple prefix.
var classNumber int64
if n, ok := nonBasicTypes[class]; ok {
classNumber = n
} else {
panic("unknown type kind: " + class)
}
// where xxx indicates the complex type (any non-basic type). The upper
// bits contain whatever the type contains. Types that wrap a single
// other type (channel, interface, pointer, slice) just contain the bits
// of the wrapped type. Other types (like struct) have a different
// method of encoding the contents of the type.
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 == "" {
num = state.getNonBasicTypeCode(class, typecode)
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
} else {
// We must return a named type here. But first check whether it
// has already been defined.
if index, ok := state.namedNonBasicTypes[name]; ok {
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))
}
// TODO: store num in a sidetable
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
}
// Concatenate the 'num' and 'lowBits' bitstrings.
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
return num
}
}
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
// the type code used there in the type code.
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
switch class {
case "chan", "pointer", "slice":
// Prefix-style type kinds. The upper bits contain the element type.
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
return state.getTypeCodeNum(sub)
case "array":
// 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++
// getNamedTypeNum 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 getNamedTypeNum(namedTypes map[string]int, name string) int {
if num, ok := namedTypes[name]; ok {
return num
} else {
num = len(namedTypes) + 1
namedTypes[name] = 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.
import (
"go/constant"
"strconv"
"golang.org/x/tools/go/ssa"
@@ -13,7 +14,7 @@ import (
// emitSyscall emits an inline system call instruction, depending on the target
// OS/arch.
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
switch {
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.
//
// 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:
// https://stackoverflow.com/a/2538212
// 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}
// Constraints will look something like:
// "={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)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
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":
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
@@ -106,7 +81,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType)
constraints += ",{r7}" // syscall number
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)
argTypes = append(argTypes, llvmValue.Type())
}
args = append(args, num)
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
argTypes = append(argTypes, c.uintptrType)
constraints += ",{x8}" // syscall number
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
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
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, zero, 1, "")
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)
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
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, zero, 1, "")
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)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
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
// pointer.
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
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
packedAlloc = c.builder.CreateAlloca(packedType, "")
} else {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
if c.needsStackObjects() {
c.trackPointer(packedHeapAlloc)
}
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// 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() {
// Load value (as *i8) from the alloca.
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
result := 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
return c.builder.CreateLoad(packedAlloc, "")
} else {
// 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)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
var packedAlloc llvm.Value
size := c.targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
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.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// 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")
c.builder.CreateStore(packedRawValue, packedRawAlloc)
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 {
if c.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType)
values[i] = c.getZeroValue(valueType)
continue
}
indices := []llvm.Value{
@@ -111,10 +104,5 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
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
}
-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():
allocType := inst.Type().ElementType()
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
alloca.SetInitializer(llvm.ConstNull(allocType))
alloca.SetInitializer(getZeroValue(allocType))
alloca.SetLinkage(llvm.InternalLinkage)
fr.locals[inst] = &LocalValue{
Underlying: alloca,
@@ -176,20 +176,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
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)
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
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
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, "")}
case !inst.IsAFCmpInst().IsNil():
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)
}
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
alloc.SetInitializer(llvm.ConstNull(allocType))
alloc.SetInitializer(getZeroValue(allocType))
alloc.SetLinkage(llvm.InternalLinkage)
result := &LocalValue{
Underlying: alloc,
@@ -312,7 +279,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
stringType := fr.Mod.GetTypeByName("runtime._string")
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
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, retLen, []uint32{1})
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()
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
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, retLen, []uint32{1}) // len
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)}
case callee.Name() == "llvm.dbg.value":
// do nothing
case callee.Name() == "runtime.trackPointer":
// do nothing
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// This are all print instructions, which necessarily have side
// effects but no results.
@@ -496,21 +461,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
}
thenBB := inst.Operand(1)
elseBB := inst.Operand(2)
if !cond.IsAInstruction().IsNil() {
if !cond.IsConstant() {
return nil, nil, errors.New("interp: branch on a non-constant")
}
if !cond.IsAConstantExpr().IsNil() {
// This may happen when the instruction builder could not
// const-fold some instructions.
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
}
switch cond {
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // 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")
} else {
switch cond.ZExtValue() {
case 0: // false
return nil, []llvm.Value{thenBB}, nil // then
case 1: // true
return nil, []llvm.Value{elseBB}, nil // else
default:
panic("branch was not true or false")
}
}
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
// unconditional branch (goto)
+1 -12
View File
@@ -35,8 +35,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
case "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}
case "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}
}
@@ -111,16 +109,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
default:
panic("unreachable")
}
case llvm.Load:
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:
case llvm.Load, llvm.Store:
if inst.IsVolatile() {
result.updateSeverity(sideEffectLimited)
}
+44 -26
View File
@@ -16,6 +16,50 @@ func getUses(value llvm.Value) []llvm.Value {
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
// {ptr, len} pair.
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
@@ -50,29 +94,3 @@ func isScalar(t llvm.Type) bool {
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.ValueType, 8), // value type
}, false)
bucketValue := llvm.ConstNull(bucketType)
bucketValue := getZeroValue(bucketType)
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
bucket.SetInitializer(bucketValue)
bucket.SetLinkage(llvm.InternalLinkage)
@@ -311,7 +311,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
v.KeyType = keyType
key := llvm.ConstNull(keyType)
key := getZeroValue(keyType)
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
+137 -19
View File
@@ -2,6 +2,7 @@ package ir
import (
"go/ast"
"go/token"
"go/types"
"sort"
"strings"
@@ -22,13 +23,16 @@ type Program struct {
mainPkg *ssa.Package
Functions []*Function
functionMap map[*ssa.Function]*Function
Globals []*Global
globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup
NamedTypes []*NamedType
}
// Function or method.
type Function struct {
*ssa.Function
LLVMFn llvm.Value
module string // go:wasm-module
linkName string // go:linkname, go:export, go:interrupt
exported bool // go:export
nobounds bool // go:nobounds
@@ -37,6 +41,28 @@ type Function struct {
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
// it implements another interface).
type Interface struct {
@@ -57,14 +83,39 @@ const (
// //go:inline). The compiler will be more likely to inline this function,
// but it is not a guarantee.
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.
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.Build()
@@ -136,6 +187,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
LoaderProgram: lprogram,
mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function),
globalMap: make(map[*ssa.Global]*Global),
comments: comments,
}
for _, pkg := range packageList {
@@ -160,6 +213,8 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
case *ssa.Function:
p.addFunction(member)
case *ssa.Type:
t := &NamedType{Type: member}
p.NamedTypes = append(p.NamedTypes, t)
methods := getAllMethods(pkg.Prog, member.Type())
if !types.IsInterface(member.Type()) {
// named type
@@ -168,7 +223,13 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
}
}
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:
// Ignore: these are already resolved.
default:
@@ -202,6 +263,10 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
return p.functionMap[ssaFn]
}
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
return p.globalMap[ssaGlobal]
}
func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg
}
@@ -230,16 +295,8 @@ func (f *Function) parsePragmas() {
}
f.linkName = parts[1]
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":
f.inline = InlineHint
case "//go:noinline":
f.inline = InlineNone
case "//go:interrupt":
if len(parts) != 2 {
continue
@@ -298,11 +355,6 @@ func (f *Function) Inline() InlineType {
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.
func (f *Function) LinkName() string {
if f.linkName != "" {
@@ -337,6 +389,72 @@ func (f *Function) CName() string {
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.
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
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.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
}
}
-1
View File
@@ -20,6 +20,5 @@ func Link(linker string, flags ...string) error {
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = sourceDir()
return cmd.Run()
}
+29 -144
View File
@@ -1,7 +1,6 @@
package loader
import (
"bytes"
"errors"
"go/ast"
"go/build"
@@ -11,26 +10,22 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"text/template"
"github.com/tinygo-org/tinygo/cgo"
)
// Program holds all packages and some metadata about the program as a whole.
type Program struct {
mainPkg string
Build *build.Context
OverlayBuild *build.Context
OverlayPath func(path string) string
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
ClangHeaders string
Build *build.Context
OverlayBuild *build.Context
ShouldOverlay func(path string) bool
Packages map[string]*Package
sorted []*Package
fset *token.FileSet
TypeChecker types.Config
Dir string // current working directory (for error reporting)
TINYGOROOT string // root of the TinyGo installation or root of the source code
CFlags []string
}
// 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.
ctx := p.Build
if newPath := p.OverlayPath(path); newPath != "" {
if p.ShouldOverlay(path) {
ctx = p.OverlayBuild
path = newPath
}
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
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
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
return pkg, nil
}
@@ -102,11 +91,6 @@ func (p *Program) ImportFile(path string) (*Package, error) {
p.sorted = nil // invalidate the sorted order of packages
pkg := p.newPackage(buildPkg)
p.Packages[buildPkg.ImportPath] = pkg
if p.mainPkg == "" {
p.mainPkg = buildPkg.ImportPath
}
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.
//
// Idempotent.
func (p *Program) Parse(compileTestBinary bool) error {
includeTests := compileTestBinary
func (p *Program) Parse() error {
// Load all imports
for _, pkg := range p.Sorted() {
err := pkg.importRecursively(includeTests)
err := pkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
if pkg.ImportPath != err.Packages[0] {
@@ -203,14 +185,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
// Parse all packages.
for _, pkg := range p.Sorted() {
err := pkg.Parse(includeTests)
if err != nil {
return err
}
}
if compileTestBinary {
err := p.SwapTestMain()
err := pkg.Parse()
if err != nil {
return err
}
@@ -227,83 +202,6 @@ func (p *Program) Parse(compileTestBinary bool) error {
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.
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
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.
//
// Idempotent.
func (p *Package) Parse(includeTests bool) error {
func (p *Package) Parse() error {
if len(p.Files) != 0 {
return nil
}
@@ -342,7 +240,7 @@ func (p *Package) Parse(includeTests bool) error {
return nil
}
files, err := p.parseFiles(includeTests)
files, err := p.parseFiles()
if err != nil {
return err
}
@@ -381,21 +279,11 @@ func (p *Package) Check() error {
}
// 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.
var files []*ast.File
var fileErrs []error
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 {
for _, file := range p.GoFiles {
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
if err != nil {
fileErrs = append(fileErrs, err)
@@ -417,11 +305,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
files = append(files, f)
}
if len(p.CgoFiles) != 0 {
cflags := append(p.CFlags, "-I"+p.Package.Dir)
if p.ClangHeaders != "" {
cflags = append(cflags, "-I"+p.ClangHeaders)
clangIncludes := ""
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
// 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 {
fileErrs = append(fileErrs, errs...)
}
@@ -430,7 +322,6 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
if len(fileErrs) != 0 {
return nil, Errors{p, fileErrs}
}
return files, nil
}
@@ -451,15 +342,9 @@ func (p *Package) Import(to string) (*types.Package, error) {
// importRecursively() on the imported packages as well.
//
// Idempotent.
func (p *Package) importRecursively(includeTests bool) error {
func (p *Package) importRecursively() error {
p.Importing = true
imports := p.Package.Imports
if includeTests {
imports = append(imports, p.Package.TestImports...)
}
for _, to := range imports {
for _, to := range p.Package.Imports {
if to == "C" {
// Do CGo processing in a later stage.
continue
@@ -477,7 +362,7 @@ func (p *Package) importRecursively(includeTests bool) error {
if importedPkg.Importing {
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
}
err = importedPkg.importRecursively(false)
err = importedPkg.importRecursively()
if err != nil {
if err, ok := err.(*ImportCycleError); ok {
err.Packages = append([]string{p.ImportPath}, err.Packages...)
+23 -132
View File
@@ -47,18 +47,13 @@ type BuildConfig struct {
opt string
gc string
panicStrategy string
scheduler string
printIR bool
dumpSSA bool
verifyIR bool
debug bool
printSizes string
cFlags []string
ldFlags []string
tags string
wasmAbi string
heapSize int64
testConfig compiler.TestConfig
}
// Helper function for Compiler object.
@@ -85,44 +80,21 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if goroot == "" {
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{
Triple: spec.Triple,
CPU: spec.CPU,
Features: spec.Features,
GOOS: spec.GOOS,
GOARCH: spec.GOARCH,
GC: config.gc,
PanicStrategy: config.panicStrategy,
Scheduler: scheduler,
CFlags: cflags,
LDFlags: ldflags,
ClangHeaders: getClangHeaderPath(root),
Debug: config.debug,
DumpSSA: config.dumpSSA,
VerifyIR: config.verifyIR,
TINYGOROOT: root,
GOROOT: goroot,
GOPATH: getGopath(),
BuildTags: tags,
TestConfig: config.testConfig,
BuildTags: spec.BuildTags,
}
c, err := compiler.NewCompiler(pkgName, compilerConfig)
if err != nil {
@@ -156,6 +128,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if spec.GOOS != "darwin" {
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
// 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 {
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
@@ -243,16 +221,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// Prepare link command.
executable := filepath.Join(dir, "main")
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" {
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.
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 {
spec, err := LoadTarget(target)
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() {
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
fmt.Fprintln(os.Stderr, "version:", version)
@@ -580,7 +501,6 @@ func usage() {
fmt.Fprintln(os.Stderr, "\ncommands:")
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
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, " gdb: run/flash and immediately enter GDB")
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
@@ -591,24 +511,23 @@ func usage() {
func handleCompilerError(err error) {
if err != nil {
switch err := err.(type) {
case *interp.Unsupported:
if errUnsupported, ok := err.(*interp.Unsupported); ok {
// hit an unknown/unsupported instruction
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
err.Inst.Dump()
errUnsupported.Inst.Dump()
fmt.Fprintln(os.Stderr)
case types.Error:
fmt.Fprintln(os.Stderr, err)
case loader.Errors:
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
for _, err := range err.Errs {
} else if errCompiler, ok := err.(types.Error); ok {
fmt.Fprintln(os.Stderr, errCompiler)
} else if errLoader, ok := err.(loader.Errors); ok {
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
for _, err := range errLoader.Errs {
fmt.Fprintln(os.Stderr, err)
}
case *multiError:
for _, err := range err.Errs {
} else if errMulti, ok := err.(*multiError); ok {
for _, err := range errMulti.Errs {
fmt.Fprintln(os.Stderr, err)
}
default:
} else {
fmt.Fprintln(os.Stderr, "error:", err)
}
os.Exit(1)
@@ -619,13 +538,10 @@ func main() {
outpath := flag.String("o", "", "output filename")
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
printIR := flag.Bool("printir", false, "print LLVM IR")
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
tags := flag.String("tags", "", "a space-separated list of extra build tags")
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
target := flag.String("target", "", "LLVM target")
printSize := flag.String("size", "", "print sizes (none, short, full)")
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
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")
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
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 {
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
@@ -647,13 +562,10 @@ func main() {
opt: *opt,
gc: *gc,
panicStrategy: *panicStrategy,
scheduler: *scheduler,
printIR: *printIR,
dumpSSA: *dumpSSA,
verifyIR: *verifyIR,
debug: !*nodebug,
printSizes: *printSize,
tags: *tags,
wasmAbi: *wasmAbi,
}
@@ -671,13 +583,6 @@ func main() {
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)
switch command {
@@ -687,11 +592,8 @@ func main() {
usage()
os.Exit(1)
}
pkgName := "."
if flag.NArg() == 1 {
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")
if flag.NArg() != 1 {
fmt.Fprintln(os.Stderr, "No package specified.")
usage()
os.Exit(1)
}
@@ -699,7 +601,7 @@ func main() {
if target == "" && filepath.Ext(*outpath) == ".wasm" {
target = "wasm"
}
err := Build(pkgName, *outpath, target, config)
err := Build(flag.Arg(0), *outpath, target, config)
handleCompilerError(err)
case "build-builtins":
// 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)
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":
// remove cache directory
dir := cacheDir()
+3 -1
View File
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
t.Log("running tests for emulated cortex-m3...")
for _, path := range matches {
if path == "testdata/reflect.go" {
continue
}
t.Run(path, func(t *testing.T) {
runTest(path, tmpdir, "qemu", t)
})
@@ -116,7 +119,6 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
opt: "z",
printIR: false,
dumpSSA: false,
verifyIR: true,
debug: false,
printSizes: "",
wasmAbi: "js",
+11 -44
View File
@@ -30,7 +30,6 @@
package arm
import (
"runtime/volatile"
"unsafe"
)
@@ -70,58 +69,37 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// Run the following system call (SVCall) with 4 arguments.
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//go:volatile
type RegValue uint32
const (
SCS_BASE = 0xE000E000
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).
//
// Source:
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
type NVIC_Type struct {
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
ISER [8]RegValue // Interrupt Set-enable Registers
_ [24]uint32
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
ICER [8]RegValue // Interrupt Clear-enable Registers
_ [24]uint32
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
ISPR [8]RegValue // Interrupt Set-pending Registers
_ [24]uint32
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
ICPR [8]RegValue // Interrupt Clear-pending Registers
_ [24]uint32
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
IABR [8]RegValue // Interrupt Active Bit Registers
_ [56]uint32
IPR [60]volatile.Register32 // Interrupt Priority Registers
IPR [60]RegValue // Interrupt Priority Registers
}
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
// Enable the given interrupt number.
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.
@@ -138,7 +116,7 @@ func SetPriority(irq uint32, priority uint32) {
regpos := irq % 4
mask := uint32(0xff) << (regpos * 8) // bits to clear
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.
@@ -158,14 +136,3 @@ func DisableInterrupts() uintptr {
func EnableInterrupts(mask uintptr) {
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() {
machine.InitADC()
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
sensor := machine.ADC{machine.ADC2}
sensor.Configure()
+2 -2
View File
@@ -8,8 +8,8 @@ import (
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
led.Low()
time.Sleep(time.Millisecond * 500)
+4 -4
View File
@@ -16,8 +16,8 @@ func main() {
}
func led1() {
led := machine.LED1
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
println("+")
led.Low()
@@ -30,8 +30,8 @@ func led1() {
}
func led2() {
led := machine.LED2
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
led := machine.GPIO{machine.LED2}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
for {
println(" +")
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
// below to use a different pin.
const (
led = machine.LED
button = machine.Pin(8)
)
const buttonPin = 8
func main() {
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button.Configure(machine.PinConfig{Mode: machine.PinInput})
led := machine.GPIO{machine.LED}
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
button := machine.GPIO{buttonPin}
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
for {
if button.Get() {
+16 -16
View File
@@ -8,29 +8,29 @@ import (
// This example assumes that you are using the pca10040 board
func main() {
led1 := machine.LED1
led1.Configure(machine.PinConfig{Mode: machine.PinOutput})
led1 := machine.GPIO{machine.LED1}
led1.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led2 := machine.LED2
led2.Configure(machine.PinConfig{Mode: machine.PinOutput})
led2 := machine.GPIO{machine.LED2}
led2.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led3 := machine.LED3
led3.Configure(machine.PinConfig{Mode: machine.PinOutput})
led3 := machine.GPIO{machine.LED3}
led3.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
led4 := machine.LED4
led4.Configure(machine.PinConfig{Mode: machine.PinOutput})
led4 := machine.GPIO{machine.LED4}
led4.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
button1 := machine.BUTTON1
button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button1 := machine.GPIO{machine.BUTTON1}
button1.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button2 := machine.BUTTON2
button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button2 := machine.GPIO{machine.BUTTON2}
button2.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button3 := machine.BUTTON3
button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button3 := machine.GPIO{machine.BUTTON3}
button3.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
button4 := machine.BUTTON4
button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
button4 := machine.GPIO{machine.BUTTON4}
button4.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
for {
led1.Set(button1.Get())
+3 -3
View File
@@ -9,9 +9,9 @@ import (
// change these to test a different UART or pins if available
var (
uart = machine.UART0
tx = machine.UART_TX_PIN
rx = machine.UART_RX_PIN
uart = machine.UART0
tx uint8 = machine.UART_TX_PIN
rx uint8 = machine.UART_RX_PIN
)
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"
)
// cs is the pin used for Chip Select (CS). Change to whatever is in use on your board.
const cs = machine.Pin(3)
// CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board.
const CS_PIN = 3
var (
tx []byte
rx []byte
val, result uint16
cs machine.GPIO
)
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{
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
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
func main() {
ledrow := machine.LED_ROW_1
ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledcol := machine.LED_COL_1
ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput})
ledrow := machine.GPIO{machine.LED_ROW_1}
ledrow.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
ledcol := machine.GPIO{machine.LED_COL_1}
ledcol.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
ledcol.Low()
for {
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
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/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
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/
wasm_exec:
+2 -4
View File
@@ -3,9 +3,7 @@
The examples here show two different ways of using WebAssembly with TinyGo:
1. Defining and exporting functions via the `//go:export <name>` directive. See
[the export folder](./export) for an example of this. Additionally, the Wasm
module (which has a default value of `env`) can be specified using
`//go:wasm-module <module>`.
[the export folder](./export) for an example of this.
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
example of this.
@@ -36,7 +34,7 @@ $ make main
Start the local web server:
```bash
$ go run server.go
$ go run main.go
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
const CPU_FREQUENCY = 16000000
// LED on the Arduino
const LED Pin = 13
const LED = 13
// ADC on the Arduino
const (
ADC0 Pin = 0
ADC1 Pin = 1
ADC2 Pin = 2
ADC3 Pin = 3
ADC4 Pin = 4 // Used by TWI for SDA
ADC5 Pin = 5 // Used by TWI for SCL
ADC0 = 0
ADC1 = 1
ADC2 = 2
ADC3 = 3
ADC4 = 4 // Used by TWI for SDA
ADC5 = 5 // Used by TWI for SCL
)
// UART pins
const (
UART_TX_PIN Pin = 1
UART_RX_PIN Pin = 0
UART_TX_PIN = 1
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
import "device/stm32"
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
const (
PA0 = portA + 0
@@ -49,28 +47,10 @@ const (
// UART pins
const (
UART_TX_PIN = PA9
UART_RX_PIN = PA10
UART_ALT_TX_PIN = PB6
UART_ALT_RX_PIN = PB7
UART_TX_PIN = PA9
UART_RX_PIN = PA10
)
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
const (
SPI0_SCK_PIN = PA5
+5 -28
View File
@@ -4,9 +4,6 @@ package machine
import "device/sam"
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
// GPIO Pins
const (
D0 = PB09
@@ -20,7 +17,7 @@ const (
D8 = PB23
D9 = PA06
D10 = PA07
D11 = NoPin // does not seem to exist
D11 = 0xff // does not seem to exist
D12 = PA02
D13 = PA17 // PWM available
)
@@ -68,20 +65,6 @@ const (
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
const (
SDA_PIN = PB02 // I2C0 external
@@ -97,12 +80,12 @@ var (
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
PinMode: GPIO_SERCOM}
// internal device
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
SDA: SDA1_PIN,
SCL: SCL1_PIN,
PinMode: PinSERCOMAlt}
PinMode: GPIO_SERCOM_ALT}
)
// SPI pins (internal flash)
@@ -114,20 +97,14 @@ const (
// SPI on the Circuit Playground Express.
var (
SPI0 = SPI{Bus: sam.SERCOM3_SPI,
SCK: SPI0_SCK_PIN,
MOSI: SPI0_MOSI_PIN,
MISO: SPI0_MISO_PIN,
DOpad: spiTXPad2SCK3,
DIpad: sercomRXPad0,
PinMode: PinSERCOMAlt}
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
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.
+2 -2
View File
@@ -1,7 +1,7 @@
// +build digispark
// +build attiny85,digispark
package machine
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
import (
"device/sam"
)
// used to reset into bootloader
const RESET_MAGIC_VALUE = 0xf01669ef
import "device/sam"
// GPIO Pins
const (
@@ -53,20 +48,6 @@ const (
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
const (
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
@@ -78,7 +59,7 @@ var (
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
SDA: SDA_PIN,
SCL: SCL_PIN,
PinMode: PinSERCOM}
PinMode: GPIO_SERCOM}
)
// SPI pins
@@ -90,39 +71,14 @@ const (
// SPI on the ItsyBitsy 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}
)
// "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}
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
)
// I2S pins
const (
I2S_SCK_PIN = PA10
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.
-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
@@ -7,70 +7,70 @@ const HasLowFrequencyCrystal = false
// Buttons on the micro:bit (A and B)
const (
BUTTON Pin = BUTTONA
BUTTONA Pin = 17
BUTTONB Pin = 26
BUTTON = BUTTONA
BUTTONA = 17
BUTTONB = 26
)
// UART pins
const (
UART_TX_PIN Pin = 24
UART_RX_PIN Pin = 25
UART_TX_PIN = 24
UART_RX_PIN = 25
)
// ADC pins
const (
ADC0 Pin = 3 // P0 on the board
ADC1 Pin = 2 // P1 on the board
ADC2 Pin = 1 // P2 on the board
ADC0 = 3 // P0 on the board
ADC1 = 2 // P1 on the board
ADC2 = 1 // P2 on the board
)
// I2C pins
const (
SDA_PIN Pin = 30 // P20 on the board
SCL_PIN Pin = 0 // P19 on the board
SDA_PIN = 30 // P20 on the board
SCL_PIN = 0 // P19 on the board
)
// SPI pins
const (
SPI0_SCK_PIN Pin = 23 // P13 on the board
SPI0_MOSI_PIN Pin = 21 // P15 on the board
SPI0_MISO_PIN Pin = 22 // P14 on the board
SPI0_SCK_PIN = 23 // P13 on the board
SPI0_MOSI_PIN = 21 // P15 on the board
SPI0_MISO_PIN = 22 // P14 on the board
)
// GPIO/Analog pins
const (
P0 Pin = 3
P1 Pin = 2
P2 Pin = 1
P3 Pin = 4
P4 Pin = 5
P5 Pin = 17
P6 Pin = 12
P7 Pin = 11
P8 Pin = 18
P9 Pin = 10
P10 Pin = 6
P11 Pin = 26
P12 Pin = 20
P13 Pin = 23
P14 Pin = 22
P15 Pin = 21
P16 Pin = 16
P0 = 3
P1 = 2
P2 = 1
P3 = 4
P4 = 5
P5 = 17
P6 = 12
P7 = 11
P8 = 18
P9 = 10
P10 = 6
P11 = 26
P12 = 20
P13 = 23
P14 = 22
P15 = 21
P16 = 16
)
// LED matrix pins
const (
LED_COL_1 Pin = 4
LED_COL_2 Pin = 5
LED_COL_3 Pin = 6
LED_COL_4 Pin = 7
LED_COL_5 Pin = 8
LED_COL_6 Pin = 9
LED_COL_7 Pin = 10
LED_COL_8 Pin = 11
LED_COL_9 Pin = 12
LED_ROW_1 Pin = 13
LED_ROW_2 Pin = 14
LED_ROW_3 Pin = 15
LED_COL_1 = 4
LED_COL_2 = 5
LED_COL_3 = 6
LED_COL_4 = 7
LED_COL_5 = 8
LED_COL_6 = 9
LED_COL_7 = 10
LED_COL_8 = 11
LED_COL_9 = 12
LED_ROW_1 = 13
LED_ROW_2 = 14
LED_ROW_3 = 15
)
+11 -11
View File
@@ -6,27 +6,27 @@ const HasLowFrequencyCrystal = true
// LEDs on the nrf52840-mdk (nRF52840 dev board)
const (
LED Pin = LED_GREEN
LED_GREEN Pin = 22
LED_RED Pin = 23
LED_BLUE Pin = 24
LED = LED_GREEN
LED_GREEN = 22
LED_RED = 23
LED_BLUE = 24
)
// UART pins
const (
UART_TX_PIN Pin = 20
UART_RX_PIN Pin = 19
UART_TX_PIN = 20
UART_RX_PIN = 19
)
// I2C pins (unused)
const (
SDA_PIN = NoPin
SCL_PIN = NoPin
SDA_PIN = 0xff
SCL_PIN = 0xff
)
// SPI pins (unused)
const (
SPI0_SCK_PIN = NoPin
SPI0_MOSI_PIN = NoPin
SPI0_MISO_PIN = NoPin
SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = 0
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.
//
@@ -10,30 +10,30 @@ const HasLowFrequencyCrystal = true
// LED on the pca10031
const (
LED Pin = LED_RED
LED1 Pin = LED_RED
LED2 Pin = LED_GREEN
LED3 Pin = LED_BLUE
LED_RED Pin = 21
LED_GREEN Pin = 22
LED_BLUE Pin = 23
LED = LED_RED
LED1 = LED_RED
LED2 = LED_GREEN
LED3 = LED_BLUE
LED_RED = 21
LED_GREEN = 22
LED_BLUE = 23
)
// UART pins
const (
UART_TX_PIN Pin = 9
UART_RX_PIN Pin = 11
UART_TX_PIN = 9
UART_RX_PIN = 11
)
// I2C pins (disabled)
const (
SDA_PIN = NoPin
SCL_PIN = NoPin
SDA_PIN = 0xff
SCL_PIN = 0xff
)
// SPI pins (unused)
const (
SPI0_SCK_PIN = NoPin
SPI0_MOSI_PIN = NoPin
SPI0_MISO_PIN = NoPin
SPI0_SCK_PIN = 0
SPI0_MOSI_PIN = 0
SPI0_MISO_PIN = 0
)
+24 -24
View File
@@ -1,4 +1,4 @@
// +build pca10040
// +build nrf,pca10040
package machine
@@ -7,47 +7,47 @@ const HasLowFrequencyCrystal = true
// LEDs on the PCA10040 (nRF52832 dev board)
const (
LED Pin = LED1
LED1 Pin = 17
LED2 Pin = 18
LED3 Pin = 19
LED4 Pin = 20
LED = LED1
LED1 = 17
LED2 = 18
LED3 = 19
LED4 = 20
)
// Buttons on the PCA10040 (nRF52832 dev board)
const (
BUTTON Pin = BUTTON1
BUTTON1 Pin = 13
BUTTON2 Pin = 14
BUTTON3 Pin = 15
BUTTON4 Pin = 16
BUTTON = BUTTON1
BUTTON1 = 13
BUTTON2 = 14
BUTTON3 = 15
BUTTON4 = 16
)
// UART pins for NRF52840-DK
const (
UART_TX_PIN Pin = 6
UART_RX_PIN Pin = 8
UART_TX_PIN = 6
UART_RX_PIN = 8
)
// ADC pins
const (
ADC0 Pin = 3
ADC1 Pin = 4
ADC2 Pin = 28
ADC3 Pin = 29
ADC4 Pin = 30
ADC5 Pin = 31
ADC0 = 3
ADC1 = 4
ADC2 = 28
ADC3 = 29
ADC4 = 30
ADC5 = 31
)
// I2C pins
const (
SDA_PIN Pin = 26
SCL_PIN Pin = 27
SDA_PIN = 26
SCL_PIN = 27
)
// SPI pins
const (
SPI0_SCK_PIN Pin = 25
SPI0_MOSI_PIN Pin = 23
SPI0_MISO_PIN Pin = 24
SPI0_SCK_PIN = 25
SPI0_MOSI_PIN = 23
SPI0_MISO_PIN = 24
)
+23 -23
View File
@@ -6,47 +6,47 @@ const HasLowFrequencyCrystal = true
// LEDs on the pca10056
const (
LED Pin = LED1
LED1 Pin = 13
LED2 Pin = 14
LED3 Pin = 15
LED4 Pin = 16
LED = LED1
LED1 = 13
LED2 = 14
LED3 = 15
LED4 = 16
)
// Buttons on the pca10056
const (
BUTTON Pin = BUTTON1
BUTTON1 Pin = 11
BUTTON2 Pin = 12
BUTTON3 Pin = 24
BUTTON4 Pin = 25
BUTTON = BUTTON1
BUTTON1 = 11
BUTTON2 = 12
BUTTON3 = 24
BUTTON4 = 25
)
// UART pins
const (
UART_TX_PIN Pin = 6
UART_RX_PIN Pin = 8
UART_TX_PIN = 6
UART_RX_PIN = 8
)
// ADC pins
const (
ADC0 Pin = 3
ADC1 Pin = 4
ADC2 Pin = 28
ADC3 Pin = 29
ADC4 Pin = 30
ADC5 Pin = 31
ADC0 = 3
ADC1 = 4
ADC2 = 28
ADC3 = 29
ADC4 = 30
ADC5 = 31
)
// I2C pins
const (
SDA_PIN Pin = 26 // P0.26
SCL_PIN Pin = 27 // P0.27
SDA_PIN = 26 // P0.26
SCL_PIN = 27 // P0.27
)
// SPI pins
const (
SPI0_SCK_PIN Pin = 47 // P1.15
SPI0_MOSI_PIN Pin = 45 // P1.13
SPI0_MISO_PIN Pin = 46 // P1.14
SPI0_SCK_PIN = 47 // P1.15
SPI0_MOSI_PIN = 45 // P1.13
SPI0_MISO_PIN = 46 // P1.14
)
+18 -37
View File
@@ -4,58 +4,39 @@ package machine
const HasLowFrequencyCrystal = true
// Pins on the reel board
// LEDs on the reel board
const (
LED Pin = LED1
LED1 Pin = LED_YELLOW
LED2 Pin = LED_RED
LED3 Pin = LED_GREEN
LED4 Pin = LED_BLUE
LED_RED Pin = 11
LED_GREEN Pin = 12
LED_BLUE Pin = 41
LED_YELLOW Pin = 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
LED = LED1
LED1 = LED_YELLOW
LED2 = LED_RED
LED3 = LED_GREEN
LED4 = LED_BLUE
LED_RED = 11
LED_GREEN = 12
LED_BLUE = 41
LED_YELLOW = 13
)
// User "a" button on the reel board
const (
BUTTON Pin = 7
BUTTON = 7
)
// UART pins
const (
UART_TX_PIN Pin = 6
UART_RX_PIN Pin = 8
UART_TX_PIN = 6
UART_RX_PIN = 8
)
// I2C pins
const (
SDA_PIN Pin = 26
SCL_PIN Pin = 27
SDA_PIN = 26
SCL_PIN = 27
)
// SPI pins
const (
SPI0_SCK_PIN Pin = 47
SPI0_MOSI_PIN Pin = 45
SPI0_MISO_PIN Pin = 46
SPI0_SCK_PIN = 47
SPI0_MOSI_PIN = 45
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
-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
import (
"runtime/volatile"
)
const bufferSize = 128
//go:volatile
type volatileByte byte
// RingBuffer is ring buffer implementation inspired by post at
// 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.
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
type RingBuffer struct {
rxbuffer [bufferSize]volatile.Register8
head volatile.Register8
tail volatile.Register8
rxbuffer [bufferSize]volatileByte
head volatileByte
tail volatileByte
}
// NewRingBuffer returns a new ring buffer.
@@ -25,15 +24,15 @@ func NewRingBuffer() *RingBuffer {
// Used returns how many bytes in buffer have been used.
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
// full, the method will return false.
func (rb *RingBuffer) Put(val byte) bool {
if rb.Used() != bufferSize {
rb.head.Set(rb.head.Get() + 1)
rb.rxbuffer[rb.head.Get()%bufferSize].Set(val)
rb.head++
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
return true
}
return false
@@ -43,8 +42,8 @@ func (rb *RingBuffer) Put(val byte) bool {
// the method will return a false as the second value.
func (rb *RingBuffer) Get() (byte, bool) {
if rb.Used() != 0 {
rb.tail.Set(rb.tail.Get() + 1)
return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true
rb.tail++
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
}
return 0, false
}
+1 -1
View File
@@ -1,4 +1,4 @@
// +build avr nrf sam stm32,!stm32f4disco
// +build avr nrf sam stm32f103xx
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.
type I2SConfig struct {
SCK Pin
WS Pin
SD Pin
SCK uint8
WS uint8
SD uint8
Mode I2SMode
Standard I2SStandard
ClockSource I2SClockSource
+9 -20
View File
@@ -1,36 +1,25 @@
package machine
type PinConfig struct {
Mode PinMode
type GPIOConfig struct {
Mode GPIOMode
}
// Pin is a single pin on a chip, which may be connected to other hardware
// devices. It can either be used directly as GPIO pin or it can be used in
// other peripherals like ADC, I2C, etc.
type Pin int8
type GPIO struct {
Pin uint8
}
// NoPin explicitly indicates "not a pin". Use this pin if you want to leave one
// 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() {
func (p GPIO) High() {
p.Set(true)
}
// Low sets this GPIO pin to low, assuming it has been configured as an output
// 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() {
func (p GPIO) Low() {
p.Set(false)
}
type PWM struct {
Pin Pin
Pin uint8
}
type ADC struct {
Pin Pin
Pin uint8
}
+24 -25
View File
@@ -4,42 +4,41 @@ package machine
import (
"device/avr"
"runtime/volatile"
)
// Configure sets the pin to input or output.
func (p Pin) Configure(config PinConfig) {
if config.Mode == PinOutput { // set output bit
if p < 8 {
avr.DDRD.SetBits(1 << uint8(p))
func (p GPIO) Configure(config GPIOConfig) {
if config.Mode == GPIO_OUTPUT { // set output bit
if p.Pin < 8 {
avr.DDRD.SetBits(1 << p.Pin)
} else {
avr.DDRB.SetBits(1 << uint8(p-8))
avr.DDRB.SetBits(1 << (p.Pin - 8))
}
} else { // configure input: clear output bit
if p < 8 {
avr.DDRD.ClearBits(1 << uint8(p))
if p.Pin < 8 {
avr.DDRD.ClearBits(1 << p.Pin)
} else {
avr.DDRB.ClearBits(1 << uint8(p-8))
avr.DDRB.ClearBits(1 << (p.Pin - 8))
}
}
}
// Get returns the current value of a GPIO pin.
func (p Pin) Get() bool {
if p < 8 {
val := avr.PIND.Get() & (1 << uint8(p))
func (p GPIO) Get() bool {
if p.Pin < 8 {
val := avr.PIND.Get() & (1 << p.Pin)
return (val > 0)
} else {
val := avr.PINB.Get() & (1 << uint8(p-8))
val := avr.PINB.Get() & (1 << (p.Pin - 8))
return (val > 0)
}
}
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
if p < 8 {
return avr.PORTD, 1 << uint8(p)
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
if p.Pin < 8 {
return avr.PORTD, 1 << p.Pin
} 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.
func (pwm PWM) Configure() {
if pwm.Pin < 8 {
avr.DDRD.SetBits(1 << uint8(pwm.Pin))
avr.DDRD.SetBits(1 << pwm.Pin)
} 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))
// 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.
@@ -183,7 +182,7 @@ func (i2c I2C) stop() {
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
// 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)
// 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)
// 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())
@@ -240,7 +239,7 @@ func (uart UART) Configure(config UARTConfig) {
// WriteByte writes a byte of data to the UART.
func (uart UART) WriteByte(c byte) error {
// 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
return nil
@@ -252,7 +251,7 @@ func handleUSART_RX() {
data := avr.UDR0.Get()
// 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.
UART0.Receive(byte(data))
}

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