mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-05 03:27:48 +00:00
Compare commits
3 Commits
v0.8.0
..
gc-precise
| Author | SHA1 | Date | |
|---|---|---|---|
| a1e69bbc13 | |||
| 1b4d71bd3d | |||
| 43b3bb6e83 |
+74
-52
@@ -40,40 +40,76 @@ commands:
|
|||||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||||
rm node-v10.15.1-linux-x64.tar.xz
|
rm node-v10.15.1-linux-x64.tar.xz
|
||||||
|
dep:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install Go dependencies"
|
||||||
|
command: |
|
||||||
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
dep ensure --vendor-only
|
||||||
llvm-source-linux:
|
llvm-source-linux:
|
||||||
steps:
|
steps:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-8-v5
|
- llvm-source-8-v2
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-8-v5
|
key: llvm-source-8-v2
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm
|
||||||
|
smoketest:
|
||||||
|
steps:
|
||||||
|
- smoketest-no-avr
|
||||||
|
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||||
|
smoketest-no-avr:
|
||||||
|
steps:
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||||
|
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||||
|
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||||
|
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||||
|
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||||
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||||
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||||
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
test-linux:
|
test-linux:
|
||||||
|
parameters:
|
||||||
|
llvm:
|
||||||
|
type: string
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- submodules
|
- submodules
|
||||||
- apt-dependencies:
|
- apt-dependencies:
|
||||||
llvm: "-8"
|
llvm: <<parameters.llvm>>
|
||||||
- install-node
|
- install-node
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
|
- dep
|
||||||
- run: go install .
|
- run: go install .
|
||||||
- run: go test -v ./transform .
|
- run: go test -v
|
||||||
- run: make gen-device -j4
|
- run: make gen-device -j4
|
||||||
- run: make smoketest RISCV=0
|
- smoketest
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
- ~/.cache/tinygo
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
|
||||||
- run: make fmt-check
|
- run: make fmt-check
|
||||||
build-linux:
|
build-linux:
|
||||||
steps:
|
steps:
|
||||||
@@ -96,12 +132,12 @@ commands:
|
|||||||
- install-node
|
- install-node
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-linux-v7
|
- llvm-build-8-linux-v4
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -119,7 +155,7 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-linux-v7
|
key: llvm-build-8-linux-v4
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
@@ -128,6 +164,7 @@ commands:
|
|||||||
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
||||||
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
||||||
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
||||||
|
- dep
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test
|
command: make test
|
||||||
@@ -139,11 +176,10 @@ commands:
|
|||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
- ~/.cache/tinygo
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
|
||||||
- run:
|
- run:
|
||||||
name: "Extract release tarball"
|
name: "Extract release tarball"
|
||||||
command: |
|
command: |
|
||||||
@@ -151,12 +187,7 @@ commands:
|
|||||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||||
tinygo version
|
tinygo version
|
||||||
- run:
|
- smoketest
|
||||||
name: "Download SiFive GNU toolchain"
|
|
||||||
command: |
|
|
||||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
|
||||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
|
||||||
- run: make smoketest
|
|
||||||
build-macos:
|
build-macos:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -164,27 +195,20 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Install dependencies"
|
name: "Install dependencies"
|
||||||
command: |
|
command: |
|
||||||
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
|
||||||
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
|
|
||||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- llvm-source-8-macos-v2
|
||||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- llvm-source-8-macos-v5
|
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-8-macos-v5
|
key: llvm-source-8-macos-v2
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-8-macos-v6
|
- llvm-build-8-macos-v3
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -196,13 +220,16 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-8-macos-v6
|
key: llvm-build-8-macos-v3
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run:
|
- run:
|
||||||
name: "Create LLVM symlinks"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
||||||
|
- run:
|
||||||
|
name: "Install Go dependencies"
|
||||||
|
command: dep ensure --vendor-only
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test
|
command: make test
|
||||||
@@ -220,39 +247,34 @@ commands:
|
|||||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||||
tinygo version
|
tinygo version
|
||||||
- run:
|
- smoketest-no-avr
|
||||||
name: "Download SiFive GNU toolchain"
|
|
||||||
command: |
|
|
||||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
|
||||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
|
||||||
- run: make smoketest AVR=0
|
|
||||||
- save_cache:
|
|
||||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/go-build
|
|
||||||
- ~/.cache/tinygo
|
|
||||||
- /go/pkg/mod
|
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-llvm8-go111:
|
test-llvm8-go111:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.11-stretch
|
- image: circleci/golang:1.11
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- test-linux
|
- test-linux:
|
||||||
|
llvm: "-8"
|
||||||
test-llvm8-go112:
|
test-llvm8-go112:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.12-stretch
|
- image: circleci/golang:1.12
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- test-linux
|
- test-linux:
|
||||||
|
llvm: "-8"
|
||||||
build-linux:
|
build-linux:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.12-stretch
|
- image: circleci/golang:1.12
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- build-linux
|
- build-linux
|
||||||
build-macos:
|
build-macos:
|
||||||
macos:
|
macos:
|
||||||
xcode: "10.1.0"
|
xcode: "10.1.0"
|
||||||
|
working_directory: ~/go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- build-macos
|
- build-macos
|
||||||
|
|
||||||
|
|||||||
+2
-4
@@ -5,12 +5,10 @@ src/device/avr/*.ld
|
|||||||
src/device/avr/*.s
|
src/device/avr/*.s
|
||||||
src/device/nrf/*.go
|
src/device/nrf/*.go
|
||||||
src/device/nrf/*.s
|
src/device/nrf/*.s
|
||||||
src/device/sam/*.go
|
|
||||||
src/device/sam/*.s
|
|
||||||
src/device/sifive/*.go
|
|
||||||
src/device/sifive/*.s
|
|
||||||
src/device/stm32/*.go
|
src/device/stm32/*.go
|
||||||
src/device/stm32/*.s
|
src/device/stm32/*.s
|
||||||
|
src/device/sam/*.go
|
||||||
|
src/device/sam/*.s
|
||||||
vendor
|
vendor
|
||||||
llvm
|
llvm
|
||||||
llvm-build
|
llvm-build
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@
|
|||||||
url = https://github.com/avr-rust/avr-mcu.git
|
url = https://github.com/avr-rust/avr-mcu.git
|
||||||
[submodule "lib/cmsis-svd"]
|
[submodule "lib/cmsis-svd"]
|
||||||
path = lib/cmsis-svd
|
path = lib/cmsis-svd
|
||||||
url = https://github.com/tinygo-org/cmsis-svd
|
url = https://github.com/posborne/cmsis-svd
|
||||||
[submodule "lib/compiler-rt"]
|
[submodule "lib/compiler-rt"]
|
||||||
path = lib/compiler-rt
|
path = lib/compiler-rt
|
||||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||||
|
|||||||
+5
-8
@@ -16,6 +16,7 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
|||||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||||
|
|
||||||
* Go (1.11+)
|
* Go (1.11+)
|
||||||
|
* [dep](https://golang.github.io/dep/)
|
||||||
* Standard build tools (gcc/clang)
|
* Standard build tools (gcc/clang)
|
||||||
* git
|
* git
|
||||||
* CMake
|
* CMake
|
||||||
@@ -26,20 +27,16 @@ on a different system like Mac.
|
|||||||
|
|
||||||
## Download the source
|
## Download the source
|
||||||
|
|
||||||
The first step is to download the TinyGo sources (use `--recursive` if you clone
|
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||||
the git repository). Then, inside the directory, download the LLVM source:
|
perform these steps:
|
||||||
|
|
||||||
make llvm-source
|
dep ensure -vendor-only # download Go dependencies
|
||||||
|
make llvm-source # download LLVM
|
||||||
|
|
||||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||||
covered by this guide.
|
covered by this guide.
|
||||||
|
|
||||||
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
|
|
||||||
Go below 1.13), make sure that Go modules are enabled:
|
|
||||||
|
|
||||||
export GO111MODULE=on
|
|
||||||
|
|
||||||
## Build LLVM, Clang, LLD
|
## Build LLVM, Clang, LLD
|
||||||
|
|
||||||
Before starting the build, you may want to set the following environment
|
Before starting the build, you may want to set the following environment
|
||||||
|
|||||||
-145
@@ -1,148 +1,3 @@
|
|||||||
0.8.0
|
|
||||||
---
|
|
||||||
* **command line**
|
|
||||||
- fix parsing of beta Go versions
|
|
||||||
- check the major/minor installed version of Go before compiling
|
|
||||||
- validate `-target` flag better to not panic on an invalid target
|
|
||||||
* **compiler**
|
|
||||||
- implement full slice expression: `s[:2:4]`
|
|
||||||
- fix a crash when storing a linked list in an interface
|
|
||||||
- fix comparing struct types by making type IDs more unique
|
|
||||||
- fix some bugs in IR generation
|
|
||||||
- add support for linked lists in reflect data
|
|
||||||
- implement `[]rune` to string conversion
|
|
||||||
- implement support for `go` on func values
|
|
||||||
* **standard library**
|
|
||||||
- `reflect`: add support for named types
|
|
||||||
- `reflect`: add support for `t.Bits()`
|
|
||||||
- `reflect`: add basic support for `t.AssignableTo()`
|
|
||||||
- `reflect`: implement `t.Align()`
|
|
||||||
- `reflect`: add support for struct types
|
|
||||||
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
|
|
||||||
- `reflect`: implement support for array types
|
|
||||||
- `reflect`: implement `t.Comparable()`
|
|
||||||
- `runtime`: implement stack-based scheduler
|
|
||||||
- `runtime`: fix bug in the sleep queue of the scheduler
|
|
||||||
- `runtime`: implement `memcpy` for Cortex-M
|
|
||||||
- `testing`: implement stub `testing.B` struct
|
|
||||||
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
|
|
||||||
* **targets**
|
|
||||||
- `386`: add support for linux/386 syscalls
|
|
||||||
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
|
|
||||||
used
|
|
||||||
- `atsamd21`: correct issue with invalid first reading coming from ADC
|
|
||||||
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
|
|
||||||
- `atsamd21`: make pin selection more flexible for peripherals
|
|
||||||
- `atsamd21`: fix minimum delay in `time.Sleep`
|
|
||||||
- `atsamd51`: fix minimum delay in `time.Sleep`
|
|
||||||
- `nrf`: improve SPI write-only speed, by making use of double buffering
|
|
||||||
- `stm32f103`: fix SPI frequency selection
|
|
||||||
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
|
|
||||||
- `stm32f103`: allow board specific UART usage
|
|
||||||
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
|
|
||||||
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
|
|
||||||
- `cortex-m`: add support for `arm.SystemReset()`
|
|
||||||
- `gameboy-advance`: add initial support for the GameBoy Advance
|
|
||||||
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
|
|
||||||
- `wasm`: add syscall/js.valueSetIndex support
|
|
||||||
- `wasm`: add syscall/js.valueInvoke support
|
|
||||||
|
|
||||||
0.7.1
|
|
||||||
---
|
|
||||||
* **targets**
|
|
||||||
- `atsamd21`: add support for the `-port` flag in the flash subcommand
|
|
||||||
|
|
||||||
0.7.0
|
|
||||||
---
|
|
||||||
* **command line**
|
|
||||||
- try more locations to find Clang built-in headers
|
|
||||||
- add support for `tinygo test`
|
|
||||||
- build current directory if no package is specified
|
|
||||||
- support custom .json target spec with `-target` flag
|
|
||||||
- use zversion.go to detect version of GOROOT version
|
|
||||||
- make initial heap size configurable for some targets (currently WebAssembly
|
|
||||||
only)
|
|
||||||
* **cgo**
|
|
||||||
- add support for bitfields using generated getters and setters
|
|
||||||
- add support for anonymous structs
|
|
||||||
* **compiler**
|
|
||||||
- show an error instead of panicking on duplicate function definitions
|
|
||||||
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
|
|
||||||
- remove `//go:volatile` support
|
|
||||||
It has been replaced with the runtime/volatile package.
|
|
||||||
- allow poiners in map keys
|
|
||||||
- support non-constant syscall numbers
|
|
||||||
- implement non-blocking selects
|
|
||||||
- add support for the `-tags` flag
|
|
||||||
- add support for `string` to `[]rune` conversion
|
|
||||||
- implement a portable conservative garbage collector (with support for wasm)
|
|
||||||
- add the `//go:noinline` pragma
|
|
||||||
* **standard library**
|
|
||||||
- `os`: add `os.Exit` and `syscall.Exit`
|
|
||||||
- `os`: add several stubs
|
|
||||||
- `runtime`: fix heap corruption in conservative GC
|
|
||||||
- `runtime`: add support for math intrinsics where supported, massively
|
|
||||||
speeding up some benchmarks
|
|
||||||
- `testing`: add basic support for testing
|
|
||||||
* **targets**
|
|
||||||
- add support for a generic target that calls `__tinygo_*` functions for
|
|
||||||
peripheral access
|
|
||||||
- `arduino-nano33`: add support for this board
|
|
||||||
- `hifive1`: add support for this RISC-V board
|
|
||||||
- `reelboard`: add e-paper pins
|
|
||||||
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
|
|
||||||
- `wasm`: put the stack at the start of linear memory, to detect stack
|
|
||||||
overflows
|
|
||||||
|
|
||||||
0.6.0
|
|
||||||
---
|
|
||||||
* **command line**
|
|
||||||
- some portability improvements
|
|
||||||
- make `$GOROOT` more robust and configurable
|
|
||||||
- check for Clang at the Homebrew install location as fallback
|
|
||||||
* **compiler driver**
|
|
||||||
- support multiple variations of LLVM commands, for non-Debian distributions
|
|
||||||
* **compiler**
|
|
||||||
- improve code quality in multiple ways
|
|
||||||
- make panic configurable, adding trap on panic
|
|
||||||
- refactor many internal parts of the compiler
|
|
||||||
- print all errors encountered during compilation
|
|
||||||
- implement calling function values of a named type
|
|
||||||
- implement returning values from blocking functions
|
|
||||||
- allow larger-than-int values to be sent across a channel
|
|
||||||
- implement complex arithmetic
|
|
||||||
- improve hashmap support
|
|
||||||
- add debuginfo for function arguments
|
|
||||||
- insert nil checks on stores (increasing code size)
|
|
||||||
- implement volatile operations as compiler builtins
|
|
||||||
- add `//go:inline` pragma
|
|
||||||
- add build tags for the Go stdlib version
|
|
||||||
* **cgo**
|
|
||||||
- implement `char`, `enum` and `void*` types
|
|
||||||
- support `#include` for builtin headers
|
|
||||||
- improve typedef/struct/enum support
|
|
||||||
- only include symbols that are necessary, for broader support
|
|
||||||
- mark external function args as `nocapture`
|
|
||||||
- implement support for some `#define` constants
|
|
||||||
- implement support for multiple CGo files in a single package
|
|
||||||
- **standard library**
|
|
||||||
- `machine`: remove microbit matrix (moved to drivers repository)
|
|
||||||
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
|
|
||||||
- `runtime`: print more interface types on panic, including `error`
|
|
||||||
* **targets**
|
|
||||||
- `arm`: print an error on HardFault (including stack overflows)
|
|
||||||
- `atsamd21`: fix a bug in the ADC peripheral
|
|
||||||
- `atsamd21`: add support for I2S
|
|
||||||
- `feather-m0`: add support for this board
|
|
||||||
- `nrf51`: fix a bug in I2C
|
|
||||||
- `stm32f103xx`: fix a bug in I2C
|
|
||||||
- `syscall`: implement `Exit` on unix
|
|
||||||
- `trinket-m0`: add support for this board
|
|
||||||
- `wasm`: make _main_ example smaller
|
|
||||||
- `wasm`: don't cache wasm file in the server, for ease of debugging
|
|
||||||
- `wasm`: work around bug #41508 that caused a deadlock while linking
|
|
||||||
- `wasm`: add support for `js.FuncOf`
|
|
||||||
|
|
||||||
0.5.0
|
0.5.0
|
||||||
---
|
---
|
||||||
- **compiler driver**
|
- **compiler driver**
|
||||||
|
|||||||
@@ -16,16 +16,12 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
|||||||
|
|
||||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
|
||||||
|
|
||||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||||
|
|
||||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||||
|
|
||||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
|
||||||
|
|
||||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||||
|
|
||||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||||
|
|||||||
@@ -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
@@ -1,31 +1,34 @@
|
|||||||
# TinyGo base stage installs Go 1.12, LLVM 8 and the TinyGo compiler itself.
|
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
|
||||||
FROM golang:1.12 AS tinygo-base
|
FROM golang:latest AS tinygo-base
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||||
|
|
||||||
COPY . /tinygo
|
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||||
|
|
||||||
# remove submodules directories and re-init them to fix any hard-coded paths
|
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||||
# after copying the tinygo directory in the previous step.
|
# after copying the tinygo directory in the previous step.
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
rm -rf ./lib/* && \
|
rm -rf ./lib/* && \
|
||||||
git submodule update --init --recursive --force
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
go install /tinygo/
|
dep ensure --vendor-only && \
|
||||||
|
go install /go/src/github.com/tinygo-org/tinygo/
|
||||||
|
|
||||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||||
FROM tinygo-base AS tinygo-wasm
|
FROM tinygo-base AS tinygo-wasm
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y libllvm8 lld-8
|
apt-get install -y libllvm8 lld-8
|
||||||
|
|
||||||
@@ -33,17 +36,17 @@ RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
|||||||
FROM tinygo-base AS tinygo-avr
|
FROM tinygo-base AS tinygo-avr
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device-avr && \
|
make gen-device-avr && \
|
||||||
apt-get remove -y python3 && \
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
||||||
@@ -51,32 +54,32 @@ RUN cd /tinygo/ && \
|
|||||||
FROM tinygo-base AS tinygo-arm
|
FROM tinygo-base AS tinygo-arm
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils python3 make clang-8 && \
|
apt-get install -y apt-utils python3 make clang-8 && \
|
||||||
make gen-device-nrf && make gen-device-stm32 && \
|
make gen-device-nrf && make gen-device-stm32 && \
|
||||||
apt-get remove -y python3 && \
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
||||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||||
FROM tinygo-wasm AS tinygo-all
|
FROM tinygo-wasm AS tinygo-all
|
||||||
|
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device && \
|
make gen-device && \
|
||||||
apt-get remove -y python3 && \
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
||||||
|
|||||||
Generated
+51
@@ -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
@@ -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
|
||||||
@@ -3,12 +3,12 @@
|
|||||||
all: tinygo
|
all: tinygo
|
||||||
tinygo: build/tinygo
|
tinygo: build/tinygo
|
||||||
|
|
||||||
|
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||||
|
|
||||||
# Default build and source directories, as created by `make llvm-build`.
|
# Default build and source directories, as created by `make llvm-build`.
|
||||||
LLVM_BUILDDIR ?= llvm-build
|
LLVM_BUILDDIR ?= llvm-build
|
||||||
CLANG_SRC ?= llvm-project/clang
|
CLANG_SRC ?= llvm/tools/clang
|
||||||
LLD_SRC ?= llvm-project/lld
|
LLD_SRC ?= llvm/tools/lld
|
||||||
|
|
||||||
.PHONY: all tinygo build/tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
|
||||||
|
|
||||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||||
|
|
||||||
@@ -18,17 +18,15 @@ ifeq ($(UNAME_S),Linux)
|
|||||||
END_GROUP = -Wl,--end-group
|
END_GROUP = -Wl,--end-group
|
||||||
endif
|
endif
|
||||||
|
|
||||||
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
|
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
|
||||||
|
|
||||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||||
|
|
||||||
|
|
||||||
# For static linking.
|
# For static linking.
|
||||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
|
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
CGO_CXXFLAGS=-std=c++11
|
||||||
CGO_CXXFLAGS=-std=c++11
|
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@@ -40,8 +38,7 @@ fmt:
|
|||||||
fmt-check:
|
fmt-check:
|
||||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||||
|
|
||||||
|
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-stm32
|
||||||
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
|
|
||||||
|
|
||||||
gen-device-avr:
|
gen-device-avr:
|
||||||
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||||
@@ -56,86 +53,36 @@ gen-device-sam:
|
|||||||
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||||
go fmt ./src/device/sam
|
go fmt ./src/device/sam
|
||||||
|
|
||||||
gen-device-sifive:
|
|
||||||
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
|
|
||||||
go fmt ./src/device/sifive
|
|
||||||
|
|
||||||
gen-device-stm32:
|
gen-device-stm32:
|
||||||
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||||
go fmt ./src/device/stm32
|
go fmt ./src/device/stm32
|
||||||
|
|
||||||
|
|
||||||
# Get LLVM sources.
|
# Get LLVM sources.
|
||||||
llvm-project/README.md:
|
llvm/README.txt:
|
||||||
git clone -b release/8.x https://github.com/llvm/llvm-project
|
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||||
llvm-source: llvm-project/README.md
|
llvm/tools/clang/README.txt:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||||
|
llvm/tools/lld/README.md:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||||
|
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||||
|
|
||||||
# Configure LLVM.
|
# Configure LLVM.
|
||||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
llvm-build/build.ninja: llvm-source
|
||||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
|
|
||||||
|
|
||||||
# Build LLVM.
|
# Build LLVM.
|
||||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
llvm-build: llvm-build/build.ninja
|
||||||
cd $(LLVM_BUILDDIR); ninja
|
cd llvm-build; ninja
|
||||||
|
|
||||||
|
|
||||||
# Build the Go compiler.
|
# Build the Go compiler.
|
||||||
build/tinygo:
|
build/tinygo:
|
||||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||||
|
|
||||||
test:
|
test:
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm ./transform .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
|
||||||
|
|
||||||
tinygo-test:
|
|
||||||
cd tests/tinygotest && tinygo test
|
|
||||||
|
|
||||||
.PHONY: smoketest
|
|
||||||
smoketest:
|
|
||||||
# test all examples
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/adc
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/button
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/button2
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/echo
|
|
||||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
|
|
||||||
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/serial
|
|
||||||
tinygo build -size short -o test.elf -target=pca10040 examples/test
|
|
||||||
# test all targets/boards
|
|
||||||
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
|
|
||||||
tinygo build -size short -o test.elf -target=microbit examples/echo
|
|
||||||
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
|
||||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
|
||||||
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
|
||||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
|
||||||
tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display
|
|
||||||
tinygo build -size short -o test.elf -target=itsybitsy-m4 examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=nucleo-f103rb examples/blinky1
|
|
||||||
ifneq ($(AVR), 0)
|
|
||||||
tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
|
||||||
tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
|
||||||
endif
|
|
||||||
ifneq ($(RISCV), 0)
|
|
||||||
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
|
|
||||||
endif
|
|
||||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
|
||||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
|
||||||
|
|
||||||
release: build/tinygo gen-device
|
release: build/tinygo gen-device
|
||||||
@mkdir -p build/release/tinygo/bin
|
@mkdir -p build/release/tinygo/bin
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||||
|
|
||||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||||
|
|
||||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
led := machine.LED
|
led := machine.GPIO{machine.LED}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
for {
|
for {
|
||||||
led.Low()
|
led.Low()
|
||||||
time.Sleep(time.Millisecond * 1000)
|
time.Sleep(time.Millisecond * 1000)
|
||||||
@@ -43,20 +43,13 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
|||||||
|
|
||||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||||
|
|
||||||
The following 18 microcontroller boards are currently supported:
|
The following microcontroller boards are currently supported:
|
||||||
|
|
||||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
|
||||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
|
||||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
|
||||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
|
||||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||||
* [BBC micro:bit](https://microbit.org/)
|
* [BBC:Microbit](https://microbit.org/)
|
||||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
|
||||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
|
||||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
|
||||||
* [Digispark](http://digistump.com/products/1)
|
* [Digispark](http://digistump.com/products/1)
|
||||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||||
|
|||||||
-361
@@ -35,8 +35,6 @@ type cgoPackage struct {
|
|||||||
globals map[string]globalInfo
|
globals map[string]globalInfo
|
||||||
typedefs map[string]*typedefInfo
|
typedefs map[string]*typedefInfo
|
||||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||||
enums map[string]enumInfo
|
|
||||||
anonStructNum int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// constantInfo stores some information about a CGo constant found by libclang
|
// constantInfo stores some information about a CGo constant found by libclang
|
||||||
@@ -69,24 +67,6 @@ type typedefInfo struct {
|
|||||||
// elaboratedTypeInfo contains some information about an elaborated type
|
// elaboratedTypeInfo contains some information about an elaborated type
|
||||||
// (struct, union) found in the C AST.
|
// (struct, union) found in the C AST.
|
||||||
type elaboratedTypeInfo struct {
|
type elaboratedTypeInfo struct {
|
||||||
typeExpr *ast.StructType
|
|
||||||
pos token.Pos
|
|
||||||
bitfields []bitfieldInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
// bitfieldInfo contains information about a single bitfield in a struct. It
|
|
||||||
// keeps information about the start, end, and the special (renamed) base field
|
|
||||||
// of this bitfield.
|
|
||||||
type bitfieldInfo struct {
|
|
||||||
field *ast.Field
|
|
||||||
name string
|
|
||||||
pos token.Pos
|
|
||||||
startBit int64
|
|
||||||
endBit int64 // may be 0 meaning "until the end of the field"
|
|
||||||
}
|
|
||||||
|
|
||||||
// enumInfo contains information about an enum in the C.
|
|
||||||
type enumInfo struct {
|
|
||||||
typeExpr ast.Expr
|
typeExpr ast.Expr
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
}
|
}
|
||||||
@@ -160,7 +140,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
globals: map[string]globalInfo{},
|
globals: map[string]globalInfo{},
|
||||||
typedefs: map[string]*typedefInfo{},
|
typedefs: map[string]*typedefInfo{},
|
||||||
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||||
enums: map[string]enumInfo{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a new location for the following file.
|
// Add a new location for the following file.
|
||||||
@@ -264,9 +243,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
// Add elaborated types for C structs and unions.
|
// Add elaborated types for C structs and unions.
|
||||||
p.addElaboratedTypes()
|
p.addElaboratedTypes()
|
||||||
|
|
||||||
// Add enum types and enum constants for C enums.
|
|
||||||
p.addEnumTypes()
|
|
||||||
|
|
||||||
// Patch the AST to use the declared types and functions.
|
// Patch the AST to use the declared types and functions.
|
||||||
for _, f := range files {
|
for _, f := range files {
|
||||||
astutil.Apply(f, p.walker, nil)
|
astutil.Apply(f, p.walker, nil)
|
||||||
@@ -594,343 +570,6 @@ func (p *cgoPackage) addElaboratedTypes() {
|
|||||||
}
|
}
|
||||||
obj.Decl = typeSpec
|
obj.Decl = typeSpec
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
gen.Specs = append(gen.Specs, typeSpec)
|
||||||
// If this struct has bitfields, create getters for them.
|
|
||||||
for _, bitfield := range typ.bitfields {
|
|
||||||
p.createBitfieldGetter(bitfield, typeName)
|
|
||||||
p.createBitfieldSetter(bitfield, typeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createBitfieldGetter creates a bitfield getter function like the following:
|
|
||||||
//
|
|
||||||
// func (s *C.struct_foo) bitfield_b() byte {
|
|
||||||
// return (s.__bitfield_1 >> 5) & 0x1
|
|
||||||
// }
|
|
||||||
func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string) {
|
|
||||||
// The value to return from the getter.
|
|
||||||
// Not complete: this is just an expression to get the complete field.
|
|
||||||
var result ast.Expr = &ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "s",
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: bitfield.field.Names[0].Name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if bitfield.startBit != 0 {
|
|
||||||
// Shift to the right by .startBit so that fields that come before are
|
|
||||||
// shifted off.
|
|
||||||
result = &ast.BinaryExpr{
|
|
||||||
X: result,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.SHR,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: strconv.FormatInt(bitfield.startBit, 10),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bitfield.endBit != 0 {
|
|
||||||
// Mask off the high bits so that fields that come after this field are
|
|
||||||
// masked off.
|
|
||||||
and := (uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1
|
|
||||||
result = &ast.BinaryExpr{
|
|
||||||
X: result,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.AND,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0x" + strconv.FormatUint(and, 16),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the getter function.
|
|
||||||
getter := &ast.FuncDecl{
|
|
||||||
Recv: &ast.FieldList{
|
|
||||||
Opening: bitfield.pos,
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Names: []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "s",
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: "s",
|
|
||||||
Decl: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: &ast.StarExpr{
|
|
||||||
Star: bitfield.pos,
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Closing: bitfield.pos,
|
|
||||||
},
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "bitfield_" + bitfield.name,
|
|
||||||
},
|
|
||||||
Type: &ast.FuncType{
|
|
||||||
Func: bitfield.pos,
|
|
||||||
Params: &ast.FieldList{
|
|
||||||
Opening: bitfield.pos,
|
|
||||||
Closing: bitfield.pos,
|
|
||||||
},
|
|
||||||
Results: &ast.FieldList{
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Type: bitfield.field.Type,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Body: &ast.BlockStmt{
|
|
||||||
Lbrace: bitfield.pos,
|
|
||||||
List: []ast.Stmt{
|
|
||||||
&ast.ReturnStmt{
|
|
||||||
Return: bitfield.pos,
|
|
||||||
Results: []ast.Expr{
|
|
||||||
result,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Rbrace: bitfield.pos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
p.generated.Decls = append(p.generated.Decls, getter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createBitfieldSetter creates a bitfield setter function like the following:
|
|
||||||
//
|
|
||||||
// func (s *C.struct_foo) set_bitfield_b(value byte) {
|
|
||||||
// s.__bitfield_1 = s.__bitfield_1 ^ 0x60 | ((value & 1) << 5)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Or the following:
|
|
||||||
//
|
|
||||||
// func (s *C.struct_foo) set_bitfield_c(value byte) {
|
|
||||||
// s.__bitfield_1 = s.__bitfield_1 & 0x3f | (value << 6)
|
|
||||||
// }
|
|
||||||
func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string) {
|
|
||||||
// The full field with all bitfields.
|
|
||||||
var field ast.Expr = &ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "s",
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: bitfield.field.Names[0].Name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// The value to insert into the field.
|
|
||||||
var valueToInsert ast.Expr = &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "value",
|
|
||||||
}
|
|
||||||
|
|
||||||
if bitfield.endBit != 0 {
|
|
||||||
// Make sure the value is in range with a mask.
|
|
||||||
valueToInsert = &ast.BinaryExpr{
|
|
||||||
X: valueToInsert,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.AND,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0x" + strconv.FormatUint((uint64(1)<<uint64(bitfield.endBit-bitfield.startBit))-1, 16),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// Create a mask for the AND NOT operation.
|
|
||||||
mask := ((uint64(1) << uint64(bitfield.endBit-bitfield.startBit)) - 1) << uint64(bitfield.startBit)
|
|
||||||
// Zero the bits in the field that will soon be inserted.
|
|
||||||
field = &ast.BinaryExpr{
|
|
||||||
X: field,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.AND_NOT,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0x" + strconv.FormatUint(mask, 16),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else { // bitfield.endBit == 0
|
|
||||||
// We don't know exactly how many high bits should be zeroed. So we do
|
|
||||||
// something different: keep the low bits with a mask and OR the new
|
|
||||||
// value with it.
|
|
||||||
mask := (uint64(1) << uint64(bitfield.startBit)) - 1
|
|
||||||
// Extract the lower bits.
|
|
||||||
field = &ast.BinaryExpr{
|
|
||||||
X: field,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.AND,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0x" + strconv.FormatUint(mask, 16),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bitwise OR with the new value (after the new value has been shifted).
|
|
||||||
field = &ast.BinaryExpr{
|
|
||||||
X: field,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.OR,
|
|
||||||
Y: &ast.BinaryExpr{
|
|
||||||
X: valueToInsert,
|
|
||||||
OpPos: bitfield.pos,
|
|
||||||
Op: token.SHL,
|
|
||||||
Y: &ast.BasicLit{
|
|
||||||
ValuePos: bitfield.pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: strconv.FormatInt(bitfield.startBit, 10),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the setter function.
|
|
||||||
setter := &ast.FuncDecl{
|
|
||||||
Recv: &ast.FieldList{
|
|
||||||
Opening: bitfield.pos,
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Names: []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "s",
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: "s",
|
|
||||||
Decl: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: &ast.StarExpr{
|
|
||||||
Star: bitfield.pos,
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Closing: bitfield.pos,
|
|
||||||
},
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "set_bitfield_" + bitfield.name,
|
|
||||||
},
|
|
||||||
Type: &ast.FuncType{
|
|
||||||
Func: bitfield.pos,
|
|
||||||
Params: &ast.FieldList{
|
|
||||||
Opening: bitfield.pos,
|
|
||||||
List: []*ast.Field{
|
|
||||||
&ast.Field{
|
|
||||||
Names: []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "value",
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Type: bitfield.field.Type,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Closing: bitfield.pos,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Body: &ast.BlockStmt{
|
|
||||||
Lbrace: bitfield.pos,
|
|
||||||
List: []ast.Stmt{
|
|
||||||
&ast.AssignStmt{
|
|
||||||
Lhs: []ast.Expr{
|
|
||||||
&ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: "s",
|
|
||||||
Obj: nil,
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: bitfield.pos,
|
|
||||||
Name: bitfield.field.Names[0].Name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TokPos: bitfield.pos,
|
|
||||||
Tok: token.ASSIGN,
|
|
||||||
Rhs: []ast.Expr{
|
|
||||||
field,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Rbrace: bitfield.pos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
p.generated.Decls = append(p.generated.Decls, setter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addEnumTypes adds C enums to the AST. For example, the following C code:
|
|
||||||
//
|
|
||||||
// enum option {
|
|
||||||
// optionA,
|
|
||||||
// optionB = 5,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// is translated to the following Go code equivalent:
|
|
||||||
//
|
|
||||||
// type C.enum_option int32
|
|
||||||
//
|
|
||||||
// The constants are treated just like macros so are inserted into the AST by
|
|
||||||
// addConstDecls.
|
|
||||||
// See also: https://en.cppreference.com/w/c/language/enum
|
|
||||||
func (p *cgoPackage) addEnumTypes() {
|
|
||||||
if len(p.enums) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gen := &ast.GenDecl{
|
|
||||||
TokPos: token.NoPos,
|
|
||||||
Tok: token.TYPE,
|
|
||||||
}
|
|
||||||
names := make([]string, 0, len(p.enums))
|
|
||||||
for name := range p.enums {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
for _, name := range names {
|
|
||||||
typ := p.enums[name]
|
|
||||||
typeName := "C.enum_" + name
|
|
||||||
obj := &ast.Object{
|
|
||||||
Kind: ast.Typ,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
typeSpec := &ast.TypeSpec{
|
|
||||||
Name: &ast.Ident{
|
|
||||||
NamePos: typ.pos,
|
|
||||||
Name: typeName,
|
|
||||||
Obj: obj,
|
|
||||||
},
|
|
||||||
Type: typ.typeExpr,
|
|
||||||
}
|
|
||||||
obj.Decl = typeSpec
|
|
||||||
gen.Specs = append(gen.Specs, typeSpec)
|
|
||||||
}
|
}
|
||||||
p.generated.Decls = append(p.generated.Decls, gen)
|
p.generated.Decls = append(p.generated.Decls, gen)
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-208
@@ -49,13 +49,9 @@ GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
|||||||
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
||||||
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||||
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
|
|
||||||
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
|
||||||
|
|
||||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||||
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -505,94 +501,90 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
switch underlying.kind {
|
switch underlying.kind {
|
||||||
case C.CXType_Record:
|
case C.CXType_Record:
|
||||||
return p.makeASTType(underlying, pos)
|
return p.makeASTType(underlying, pos)
|
||||||
case C.CXType_Enum:
|
|
||||||
return p.makeASTType(underlying, pos)
|
|
||||||
default:
|
default:
|
||||||
panic("unknown elaborated type")
|
panic("unknown elaborated type")
|
||||||
}
|
}
|
||||||
case C.CXType_Record:
|
case C.CXType_Record:
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||||
if name == "" {
|
var cgoName string
|
||||||
// Anonymous record, probably inside a typedef.
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
case C.CXCursor_StructDecl:
|
||||||
if bitfieldList != nil {
|
cgoName = "struct_" + name
|
||||||
// This struct has bitfields, so we have to declare it as a
|
case C.CXCursor_UnionDecl:
|
||||||
// named type (for bitfield getters/setters to work).
|
cgoName = "union_" + name
|
||||||
p.anonStructNum++
|
default:
|
||||||
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
|
panic("unknown record declaration")
|
||||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
}
|
||||||
typeExpr: typeExpr,
|
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||||
pos: pos,
|
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||||
bitfields: bitfieldList,
|
fieldList := &ast.FieldList{
|
||||||
}
|
Opening: pos,
|
||||||
return &ast.Ident{
|
Closing: pos,
|
||||||
NamePos: pos,
|
|
||||||
Name: "C." + cgoName,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return typeExpr
|
ref := storedRefs.Put(struct {
|
||||||
} else {
|
fieldList *ast.FieldList
|
||||||
var cgoName string
|
pkg *cgoPackage
|
||||||
|
}{fieldList, p})
|
||||||
|
defer storedRefs.Remove(ref)
|
||||||
|
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||||
case C.CXCursor_StructDecl:
|
case C.CXCursor_StructDecl:
|
||||||
cgoName = "struct_" + name
|
|
||||||
case C.CXCursor_UnionDecl:
|
|
||||||
cgoName = "union_" + name
|
|
||||||
default:
|
|
||||||
panic("unknown record declaration")
|
|
||||||
}
|
|
||||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
|
||||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
|
||||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
|
||||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||||
typeExpr: typeExpr,
|
typeExpr: &ast.StructType{
|
||||||
pos: pos,
|
Struct: pos,
|
||||||
bitfields: bitfieldList,
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
}
|
}
|
||||||
}
|
case C.CXCursor_UnionDecl:
|
||||||
return &ast.Ident{
|
if len(fieldList.List) > 1 {
|
||||||
NamePos: pos,
|
// Insert a special field at the front (of zero width) as a
|
||||||
Name: "C." + cgoName,
|
// marker that this is struct is actually a union. This is done
|
||||||
|
// by giving the field a name that cannot be expressed directly
|
||||||
|
// in Go.
|
||||||
|
// Other parts of the compiler look at the first element in a
|
||||||
|
// struct (of size > 2) to know whether this is a union.
|
||||||
|
// Note that we don't have to insert it for single-element
|
||||||
|
// unions as they're basically equivalent to a struct.
|
||||||
|
unionMarker := &ast.Field{
|
||||||
|
Type: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
unionMarker.Names = []*ast.Ident{
|
||||||
|
&ast.Ident{
|
||||||
|
NamePos: pos,
|
||||||
|
Name: "C union",
|
||||||
|
Obj: &ast.Object{
|
||||||
|
Kind: ast.Var,
|
||||||
|
Name: "C union",
|
||||||
|
Decl: unionMarker,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||||
|
}
|
||||||
|
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||||
|
typeExpr: &ast.StructType{
|
||||||
|
Struct: pos,
|
||||||
|
Fields: fieldList,
|
||||||
|
},
|
||||||
|
pos: pos,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case C.CXType_Enum:
|
return &ast.Ident{
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
NamePos: pos,
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
Name: "C." + cgoName,
|
||||||
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
|
|
||||||
if name == "" {
|
|
||||||
// anonymous enum
|
|
||||||
ref := storedRefs.Put(p)
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
|
||||||
return p.makeASTType(underlying, pos)
|
|
||||||
} else {
|
|
||||||
// named enum
|
|
||||||
if _, ok := p.enums[name]; !ok {
|
|
||||||
ref := storedRefs.Put(p)
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
|
||||||
p.enums[name] = enumInfo{
|
|
||||||
typeExpr: p.makeASTType(underlying, pos),
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C.enum_" + name,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if typeName == "" {
|
if typeName == "" {
|
||||||
// Report this as an error.
|
|
||||||
spelling := getString(C.clang_getTypeSpelling(typ))
|
|
||||||
p.errors = append(p.errors, scanner.Error{
|
|
||||||
Pos: p.fset.PositionFor(pos, true),
|
|
||||||
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
|
|
||||||
})
|
|
||||||
// Fallback, probably incorrect but at least the error points to an odd
|
// Fallback, probably incorrect but at least the error points to an odd
|
||||||
// type name.
|
// type name.
|
||||||
typeName = "C." + spelling
|
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
||||||
}
|
}
|
||||||
return &ast.Ident{
|
return &ast.Ident{
|
||||||
NamePos: pos,
|
NamePos: pos,
|
||||||
@@ -600,143 +592,25 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
|
||||||
// a Go struct type. Unions are implemented by setting the first field to a
|
|
||||||
// zero-lengt "C union" field, which cannot be written in Go directly.
|
|
||||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
|
|
||||||
fieldList := &ast.FieldList{
|
|
||||||
Opening: pos,
|
|
||||||
Closing: pos,
|
|
||||||
}
|
|
||||||
var bitfieldList []bitfieldInfo
|
|
||||||
inBitfield := false
|
|
||||||
bitfieldNum := 0
|
|
||||||
ref := storedRefs.Put(struct {
|
|
||||||
fieldList *ast.FieldList
|
|
||||||
pkg *cgoPackage
|
|
||||||
inBitfield *bool
|
|
||||||
bitfieldNum *int
|
|
||||||
bitfieldList *[]bitfieldInfo
|
|
||||||
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
|
||||||
case C.CXCursor_StructDecl:
|
|
||||||
return &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
Fields: fieldList,
|
|
||||||
}, bitfieldList
|
|
||||||
case C.CXCursor_UnionDecl:
|
|
||||||
if bitfieldList != nil {
|
|
||||||
// This is valid C... but please don't do this.
|
|
||||||
p.errors = append(p.errors, scanner.Error{
|
|
||||||
Pos: p.fset.PositionFor(pos, true),
|
|
||||||
Msg: fmt.Sprintf("bitfield in a union is not supported"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if len(fieldList.List) > 1 {
|
|
||||||
// Insert a special field at the front (of zero width) as a
|
|
||||||
// marker that this is struct is actually a union. This is done
|
|
||||||
// by giving the field a name that cannot be expressed directly
|
|
||||||
// in Go.
|
|
||||||
// Other parts of the compiler look at the first element in a
|
|
||||||
// struct (of size > 2) to know whether this is a union.
|
|
||||||
// Note that we don't have to insert it for single-element
|
|
||||||
// unions as they're basically equivalent to a struct.
|
|
||||||
unionMarker := &ast.Field{
|
|
||||||
Type: &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
unionMarker.Names = []*ast.Ident{
|
|
||||||
&ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C union",
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: "C union",
|
|
||||||
Decl: unionMarker,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
|
||||||
}
|
|
||||||
return &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
Fields: fieldList,
|
|
||||||
}, bitfieldList
|
|
||||||
default:
|
|
||||||
panic("unknown record declaration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_struct_visitor
|
//export tinygo_clang_struct_visitor
|
||||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||||
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
||||||
fieldList *ast.FieldList
|
fieldList *ast.FieldList
|
||||||
pkg *cgoPackage
|
pkg *cgoPackage
|
||||||
inBitfield *bool
|
|
||||||
bitfieldNum *int
|
|
||||||
bitfieldList *[]bitfieldInfo
|
|
||||||
})
|
})
|
||||||
fieldList := passed.fieldList
|
fieldList := passed.fieldList
|
||||||
p := passed.pkg
|
p := passed.pkg
|
||||||
inBitfield := passed.inBitfield
|
|
||||||
bitfieldNum := passed.bitfieldNum
|
|
||||||
bitfieldList := passed.bitfieldList
|
|
||||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||||
panic("expected field inside cursor")
|
panic("expected field inside cursor")
|
||||||
}
|
}
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||||
if name == "" {
|
|
||||||
// Assume this is a bitfield of 0 bits.
|
|
||||||
// Warning: this is not necessarily true!
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
typ := C.tinygo_clang_getCursorType(c)
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
field := &ast.Field{
|
field := &ast.Field{
|
||||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||||
}
|
}
|
||||||
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
|
|
||||||
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
|
|
||||||
bitfieldOffset := offsetof % alignOf
|
|
||||||
if bitfieldOffset != 0 {
|
|
||||||
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
|
||||||
panic("expected a bitfield")
|
|
||||||
}
|
|
||||||
if !*inBitfield {
|
|
||||||
*bitfieldNum++
|
|
||||||
}
|
|
||||||
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
|
|
||||||
prevField := fieldList.List[len(fieldList.List)-1]
|
|
||||||
if !*inBitfield {
|
|
||||||
// The previous element also was a bitfield, but wasn't noticed
|
|
||||||
// then. Add it now.
|
|
||||||
*inBitfield = true
|
|
||||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
|
||||||
field: prevField,
|
|
||||||
name: prevField.Names[0].Name,
|
|
||||||
startBit: 0,
|
|
||||||
pos: prevField.Names[0].NamePos,
|
|
||||||
})
|
|
||||||
prevField.Names[0].Name = bitfieldName
|
|
||||||
prevField.Names[0].Obj.Name = bitfieldName
|
|
||||||
}
|
|
||||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
|
||||||
prevBitfield.endBit = bitfieldOffset
|
|
||||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
|
||||||
field: prevField,
|
|
||||||
name: name,
|
|
||||||
startBit: bitfieldOffset,
|
|
||||||
pos: pos,
|
|
||||||
})
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
*inBitfield = false
|
|
||||||
field.Names = []*ast.Ident{
|
field.Names = []*ast.Ident{
|
||||||
&ast.Ident{
|
&ast.Ident{
|
||||||
NamePos: pos,
|
NamePos: p.getCursorPosition(c),
|
||||||
Name: name,
|
Name: name,
|
||||||
Obj: &ast.Object{
|
Obj: &ast.Object{
|
||||||
Kind: ast.Var,
|
Kind: ast.Var,
|
||||||
@@ -748,16 +622,3 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
|||||||
fieldList.List = append(fieldList.List, field)
|
fieldList.List = append(fieldList.List, field)
|
||||||
return C.CXChildVisit_Continue
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//export tinygo_clang_enum_visitor
|
|
||||||
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
|
||||||
p.constants[name] = constantInfo{
|
|
||||||
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -56,15 +56,3 @@ CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
|||||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||||
return clang_Cursor_getTranslationUnit(c);
|
return clang_Cursor_getTranslationUnit(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
|
|
||||||
return clang_getEnumConstantDeclValue(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
|
|
||||||
return clang_getEnumDeclIntegerType(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
|
||||||
return clang_Cursor_isBitField(c);
|
|
||||||
}
|
|
||||||
-12
@@ -4,7 +4,6 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,16 +15,6 @@ var commands = map[string][]string{
|
|||||||
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
|
|
||||||
// manually set $PATH).
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
|
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
|
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCommand(cmdNames []string, args ...string) error {
|
func execCommand(cmdNames []string, args ...string) error {
|
||||||
for _, cmdName := range cmdNames {
|
for _, cmdName := range cmdNames {
|
||||||
cmd := exec.Command(cmdName, args...)
|
cmd := exec.Command(cmdName, args...)
|
||||||
@@ -37,7 +26,6 @@ func execCommand(cmdNames []string, args ...string) error {
|
|||||||
// this command was not found, try the next
|
// this command was not found, try the next
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-23
@@ -42,7 +42,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
|||||||
|
|
||||||
// Fail: this is a nil pointer, exit with a panic.
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
c.createRuntimeCall("lookupPanic", nil, "")
|
c.createRuntimeCall("lookuppanic", nil, "")
|
||||||
c.builder.CreateUnreachable()
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
// Ok: this is a valid pointer.
|
// Ok: this is a valid pointer.
|
||||||
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
|||||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||||
// logic is the same in both cases.
|
// logic is the same in both cases.
|
||||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||||
if frame.fn.IsNoBounds() {
|
if frame.fn.IsNoBounds() {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
// checking.
|
// checking.
|
||||||
@@ -71,9 +71,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
|
|||||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||||
capacityType = high.Type()
|
capacityType = high.Type()
|
||||||
}
|
}
|
||||||
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
|
||||||
capacityType = max.Type()
|
|
||||||
}
|
|
||||||
if capacityType != capacity.Type() {
|
if capacityType != capacity.Type() {
|
||||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||||
}
|
}
|
||||||
@@ -93,13 +90,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
|
|||||||
high = c.builder.CreateSExt(high, capacityType, "")
|
high = c.builder.CreateSExt(high, capacityType, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
|
||||||
if maxType.Info()&types.IsUnsigned != 0 {
|
|
||||||
max = c.builder.CreateZExt(max, capacityType, "")
|
|
||||||
} else {
|
|
||||||
max = c.builder.CreateSExt(max, capacityType, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||||
@@ -107,15 +97,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
|
|||||||
|
|
||||||
// Now do the bounds check: low > high || high > capacity
|
// Now do the bounds check: low > high || high > capacity
|
||||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
|
||||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
|
||||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||||
|
|
||||||
// Fail: this is a nil pointer, exit with a panic.
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
c.createRuntimeCall("slicePanic", nil, "")
|
c.createRuntimeCall("slicepanic", nil, "")
|
||||||
c.builder.CreateUnreachable()
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
// Ok: this is a valid pointer.
|
// Ok: this is a valid pointer.
|
||||||
@@ -126,11 +114,6 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
|
|||||||
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||||
// pointer dereferences exist in valid Go code.
|
// pointer dereferences exist in valid Go code.
|
||||||
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||||
// Check whether we need to emit this check at all.
|
|
||||||
if !ptr.IsAGlobalValue().IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether this is a nil pointer.
|
// Check whether this is a nil pointer.
|
||||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||||
@@ -158,7 +141,7 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
|
|||||||
|
|
||||||
// Fail: this is a nil pointer, exit with a panic.
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
c.createRuntimeCall("nilPanic", nil, "")
|
c.createRuntimeCall("nilpanic", nil, "")
|
||||||
c.builder.CreateUnreachable()
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
// Ok: this is a valid pointer.
|
// Ok: this is a valid pointer.
|
||||||
|
|||||||
+2
-2
@@ -38,7 +38,7 @@ func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expand an argument type to a list that can be used in a function call
|
// Expand an argument type to a list that can be used in a function call
|
||||||
// parameter list.
|
// paramter list.
|
||||||
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
@@ -163,7 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
|||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||||
value := llvm.ConstNull(t)
|
value := c.getZeroValue(t)
|
||||||
for i, subtyp := range t.StructElementTypes() {
|
for i, subtyp := range t.StructElementTypes() {
|
||||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||||
fields = remaining
|
fields = remaining
|
||||||
|
|||||||
+28
-181
@@ -4,7 +4,6 @@ package compiler
|
|||||||
// or pseudo-operations that are lowered during goroutine lowering.
|
// or pseudo-operations that are lowered during goroutine lowering.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
@@ -13,63 +12,60 @@ import (
|
|||||||
|
|
||||||
// emitMakeChan returns a new channel value for the given channel type.
|
// emitMakeChan returns a new channel value for the given channel type.
|
||||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||||
chanType := c.getLLVMType(expr.Type())
|
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||||
size := c.targetData.TypeAllocSize(chanType.ElementType())
|
size := c.targetData.TypeAllocSize(chanType)
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
||||||
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
|
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
|
||||||
// Set the elementSize field
|
|
||||||
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
}, "")
|
|
||||||
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
|
||||||
if elementSize > 0xffff {
|
|
||||||
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
|
|
||||||
}
|
|
||||||
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
|
|
||||||
c.builder.CreateStore(elementSizeValue, elementSizePtr)
|
|
||||||
return ptr, nil
|
return ptr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||||
// channel send operation during goroutine lowering.
|
// channel send operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||||
|
valueType := c.getLLVMType(instr.X.Type())
|
||||||
ch := c.getValue(frame, instr.Chan)
|
ch := c.getValue(frame, instr.Chan)
|
||||||
chanValue := c.getValue(frame, instr.X)
|
chanValue := c.getValue(frame, instr.X)
|
||||||
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
|
|
||||||
// store value-to-send
|
// store value-to-send
|
||||||
valueType := c.getLLVMType(instr.X.Type())
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
c.builder.CreateStore(chanValue, valueAlloca)
|
c.builder.CreateStore(chanValue, valueAlloca)
|
||||||
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
|
|
||||||
// Do the send.
|
// Do the send.
|
||||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
|
||||||
|
|
||||||
// End the lifetime of the alloca.
|
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
||||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
// This is a bit dirty, but it works (at least in LLVM 8).
|
||||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||||
// actual channel receive operation during goroutine lowering.
|
// actual channel receive operation during goroutine lowering.
|
||||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||||
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||||
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
ch := c.getValue(frame, unop.X)
|
ch := c.getValue(frame, unop.X)
|
||||||
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
|
|
||||||
// Allocate memory to receive into.
|
// Allocate memory to receive into.
|
||||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
|
|
||||||
// Do the receive.
|
// Do the receive.
|
||||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
|
||||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
|
||||||
|
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||||
@@ -82,157 +78,8 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
|||||||
|
|
||||||
// emitChanClose closes the given channel.
|
// emitChanClose closes the given channel.
|
||||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||||
|
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||||
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
ch := c.getValue(frame, param)
|
ch := c.getValue(frame, param)
|
||||||
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||||
}
|
|
||||||
|
|
||||||
// emitSelect emits all IR necessary for a select statements. That's a
|
|
||||||
// non-trivial amount of code because select is very complex to implement.
|
|
||||||
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
|
||||||
if len(expr.States) == 0 {
|
|
||||||
// Shortcuts for some simple selects.
|
|
||||||
llvmType := c.getLLVMType(expr.Type())
|
|
||||||
if expr.Blocking {
|
|
||||||
// Blocks forever:
|
|
||||||
// select {}
|
|
||||||
c.createRuntimeCall("deadlock", nil, "")
|
|
||||||
return llvm.Undef(llvmType)
|
|
||||||
} else {
|
|
||||||
// No-op:
|
|
||||||
// select {
|
|
||||||
// default:
|
|
||||||
// }
|
|
||||||
retval := llvm.Undef(llvmType)
|
|
||||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
|
||||||
return retval // {-1, false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code create a (stack-allocated) slice containing all the select
|
|
||||||
// cases and then calls runtime.chanSelect to perform the actual select
|
|
||||||
// statement.
|
|
||||||
// Simple selects (blocking and with just one case) are already transformed
|
|
||||||
// into regular chan operations during SSA construction so we don't have to
|
|
||||||
// optimize such small selects.
|
|
||||||
|
|
||||||
// Go through all the cases. Create the selectStates slice and and
|
|
||||||
// determine the receive buffer size and alignment.
|
|
||||||
recvbufSize := uint64(0)
|
|
||||||
recvbufAlign := 0
|
|
||||||
hasReceives := false
|
|
||||||
var selectStates []llvm.Value
|
|
||||||
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
|
|
||||||
for _, state := range expr.States {
|
|
||||||
ch := c.getValue(frame, state.Chan)
|
|
||||||
selectState := llvm.ConstNull(chanSelectStateType)
|
|
||||||
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
|
|
||||||
switch state.Dir {
|
|
||||||
case types.RecvOnly:
|
|
||||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
|
||||||
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
|
|
||||||
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
|
||||||
recvbufSize = size
|
|
||||||
}
|
|
||||||
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
|
||||||
recvbufAlign = align
|
|
||||||
}
|
|
||||||
hasReceives = true
|
|
||||||
case types.SendOnly:
|
|
||||||
// Store this value in an alloca and put a pointer to this alloca
|
|
||||||
// in the send state.
|
|
||||||
sendValue := c.getValue(frame, state.Send)
|
|
||||||
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
|
|
||||||
c.builder.CreateStore(sendValue, alloca)
|
|
||||||
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
|
||||||
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
selectStates = append(selectStates, selectState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a receive buffer, where the received value will be stored.
|
|
||||||
recvbuf := llvm.Undef(c.i8ptrType)
|
|
||||||
if hasReceives {
|
|
||||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
|
||||||
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
|
|
||||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
|
||||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
}, "select.recvbuf")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the states slice (allocated on the stack).
|
|
||||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
|
||||||
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
|
|
||||||
for i, state := range selectStates {
|
|
||||||
// Set each slice element to the appropriate channel.
|
|
||||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
|
||||||
}, "")
|
|
||||||
c.builder.CreateStore(state, gep)
|
|
||||||
}
|
|
||||||
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
}, "select.states")
|
|
||||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
|
||||||
|
|
||||||
// Convert the 'blocking' flag on this select into a LLVM value.
|
|
||||||
blockingInt := uint64(0)
|
|
||||||
if expr.Blocking {
|
|
||||||
blockingInt = 1
|
|
||||||
}
|
|
||||||
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
|
|
||||||
|
|
||||||
// Do the select in the runtime.
|
|
||||||
results := c.createRuntimeCall("chanSelect", []llvm.Value{
|
|
||||||
recvbuf,
|
|
||||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
|
||||||
blockingValue,
|
|
||||||
}, "")
|
|
||||||
|
|
||||||
// The result value does not include all the possible received values,
|
|
||||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
|
||||||
// instruction will treat a *ssa.Select specially and load it there inline.
|
|
||||||
// Store the receive alloca in a sidetable until we hit this extract
|
|
||||||
// instruction.
|
|
||||||
if frame.selectRecvBuf == nil {
|
|
||||||
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
|
||||||
}
|
|
||||||
frame.selectRecvBuf[expr] = recvbuf
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// getChanSelectResult returns the special values from a *ssa.Extract expression
|
|
||||||
// when extracting a value from a select statement (*ssa.Select). Because
|
|
||||||
// *ssa.Select cannot load all values in advance, it does this later in the
|
|
||||||
// *ssa.Extract expression.
|
|
||||||
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
|
|
||||||
if expr.Index == 0 {
|
|
||||||
// index
|
|
||||||
value := c.getValue(frame, expr.Tuple)
|
|
||||||
index := c.builder.CreateExtractValue(value, expr.Index, "")
|
|
||||||
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
|
|
||||||
index = c.builder.CreateSExt(index, c.intType, "")
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
} else if expr.Index == 1 {
|
|
||||||
// comma-ok
|
|
||||||
value := c.getValue(frame, expr.Tuple)
|
|
||||||
return c.builder.CreateExtractValue(value, expr.Index, "")
|
|
||||||
} else {
|
|
||||||
// Select statements are (index, ok, ...) where ... is a number of
|
|
||||||
// received values, depending on how many receive statements there
|
|
||||||
// are. They are all combined into one alloca (because only one
|
|
||||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
|
||||||
// it to the correct type, and dereference it.
|
|
||||||
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
|
||||||
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
|
|
||||||
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
|
|
||||||
return c.builder.CreateLoad(ptr, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -3,7 +3,6 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/constant"
|
"go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
@@ -27,50 +26,22 @@ func init() {
|
|||||||
llvm.InitializeAllAsmPrinters()
|
llvm.InitializeAllAsmPrinters()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The TinyGo import path.
|
|
||||||
const tinygoPath = "github.com/tinygo-org/tinygo"
|
|
||||||
|
|
||||||
// functionsUsedInTransform is a list of function symbols that may be used
|
|
||||||
// during TinyGo optimization passes so they have to be marked as external
|
|
||||||
// linkage until all TinyGo passes have finished.
|
|
||||||
var functionsUsedInTransforms = []string{
|
|
||||||
"runtime.alloc",
|
|
||||||
"runtime.free",
|
|
||||||
"runtime.sleepTask",
|
|
||||||
"runtime.sleepCurrentTask",
|
|
||||||
"runtime.setTaskStatePtr",
|
|
||||||
"runtime.getTaskStatePtr",
|
|
||||||
"runtime.activateTask",
|
|
||||||
"runtime.scheduler",
|
|
||||||
"runtime.startGoroutine",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure the compiler.
|
// Configure the compiler.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
Triple string // LLVM target triple, e.g. x86_64-unknown-linux-gnu (empty string means default)
|
||||||
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
|
CPU string // LLVM CPU name, e.g. atmega328p (empty string means default)
|
||||||
Features []string // LLVM CPU features
|
|
||||||
GOOS string //
|
GOOS string //
|
||||||
GOARCH string //
|
GOARCH string //
|
||||||
GC string // garbage collection strategy
|
GC string // garbage collection strategy
|
||||||
Scheduler string // scheduler implementation ("coroutines" or "tasks")
|
PanicStrategy string // panic strategy ("abort" or "trap")
|
||||||
PanicStrategy string // panic strategy ("print" or "trap")
|
|
||||||
CFlags []string // cflags to pass to cgo
|
CFlags []string // cflags to pass to cgo
|
||||||
LDFlags []string // ldflags to pass to cgo
|
LDFlags []string // ldflags to pass to cgo
|
||||||
ClangHeaders string // Clang built-in header include path
|
|
||||||
DumpSSA bool // dump Go SSA, for compiler debugging
|
DumpSSA bool // dump Go SSA, for compiler debugging
|
||||||
VerifyIR bool // run extra checks on the IR
|
|
||||||
Debug bool // add debug symbols for gdb
|
Debug bool // add debug symbols for gdb
|
||||||
GOROOT string // GOROOT
|
GOROOT string // GOROOT
|
||||||
TINYGOROOT string // GOROOT for TinyGo
|
TINYGOROOT string // GOROOT for TinyGo
|
||||||
GOPATH string // GOPATH, like `go env GOPATH`
|
GOPATH string // GOPATH, like `go env GOPATH`
|
||||||
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
|
BuildTags []string // build tags for TinyGo (empty means {Config.GOOS/Config.GOARCH})
|
||||||
TestConfig TestConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestConfig struct {
|
|
||||||
CompileTestBinary bool
|
|
||||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Compiler struct {
|
type Compiler struct {
|
||||||
@@ -91,7 +62,6 @@ type Compiler struct {
|
|||||||
interfaceInvokeWrappers []interfaceInvokeWrapper
|
interfaceInvokeWrappers []interfaceInvokeWrapper
|
||||||
ir *ir.Program
|
ir *ir.Program
|
||||||
diagnostics []error
|
diagnostics []error
|
||||||
astComments map[string]*ast.CommentGroup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Frame struct {
|
type Frame struct {
|
||||||
@@ -108,7 +78,6 @@ type Frame struct {
|
|||||||
deferFuncs map[*ir.Function]int
|
deferFuncs map[*ir.Function]int
|
||||||
deferInvokeFuncs map[string]int
|
deferInvokeFuncs map[string]int
|
||||||
deferClosureFuncs map[*ir.Function]int
|
deferClosureFuncs map[*ir.Function]int
|
||||||
selectRecvBuf map[*ssa.Select]llvm.Value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Phi struct {
|
type Phi struct {
|
||||||
@@ -132,11 +101,7 @@ func NewCompiler(pkgName string, config Config) (*Compiler, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
features := ""
|
c.machine = target.CreateTargetMachine(config.Triple, config.CPU, "", llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
|
||||||
if len(config.Features) > 0 {
|
|
||||||
features = strings.Join(config.Features, `,`)
|
|
||||||
}
|
|
||||||
c.machine = target.CreateTargetMachine(config.Triple, config.CPU, features, llvm.CodeGenLevelDefault, llvm.RelocStatic, llvm.CodeModelDefault)
|
|
||||||
c.targetData = c.machine.CreateTargetData()
|
c.targetData = c.machine.CreateTargetData()
|
||||||
|
|
||||||
c.ctx = llvm.NewContext()
|
c.ctx = llvm.NewContext()
|
||||||
@@ -184,21 +149,15 @@ func (c *Compiler) TargetData() llvm.TargetData {
|
|||||||
|
|
||||||
// selectGC picks an appropriate GC strategy if none was provided.
|
// selectGC picks an appropriate GC strategy if none was provided.
|
||||||
func (c *Compiler) selectGC() string {
|
func (c *Compiler) selectGC() string {
|
||||||
if c.GC != "" {
|
gc := c.GC
|
||||||
return c.GC
|
if gc == "" {
|
||||||
|
gc = "leaking"
|
||||||
}
|
}
|
||||||
return "conservative"
|
return gc
|
||||||
}
|
}
|
||||||
|
|
||||||
// selectScheduler picks an appropriate scheduler for the target if none was
|
func (c *Compiler) gcIsPrecise() bool {
|
||||||
// given.
|
return c.GC == "precise"
|
||||||
func (c *Compiler) selectScheduler() string {
|
|
||||||
if c.Scheduler != "" {
|
|
||||||
// A scheduler was specified in the target description.
|
|
||||||
return c.Scheduler
|
|
||||||
}
|
|
||||||
// Fall back to coroutines, which are supported everywhere.
|
|
||||||
return "coroutines"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compile the given package path or .go file path. Return an error when this
|
// Compile the given package path or .go file path. Return an error when this
|
||||||
@@ -217,7 +176,6 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
buildTags := append([]string{"tinygo", "gc." + c.selectGC(), "scheduler." + c.selectScheduler()}, c.BuildTags...)
|
|
||||||
lprogram := &loader.Program{
|
lprogram := &loader.Program{
|
||||||
Build: &build.Context{
|
Build: &build.Context{
|
||||||
GOARCH: c.GOARCH,
|
GOARCH: c.GOARCH,
|
||||||
@@ -227,7 +185,7 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
CgoEnabled: true,
|
CgoEnabled: true,
|
||||||
UseAllFiles: false,
|
UseAllFiles: false,
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: buildTags,
|
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||||
},
|
},
|
||||||
OverlayBuild: &build.Context{
|
OverlayBuild: &build.Context{
|
||||||
GOARCH: c.GOARCH,
|
GOARCH: c.GOARCH,
|
||||||
@@ -237,31 +195,24 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
CgoEnabled: true,
|
CgoEnabled: true,
|
||||||
UseAllFiles: false,
|
UseAllFiles: false,
|
||||||
Compiler: "gc", // must be one of the recognized compilers
|
Compiler: "gc", // must be one of the recognized compilers
|
||||||
BuildTags: buildTags,
|
BuildTags: append([]string{"tinygo", "gc." + c.selectGC()}, c.BuildTags...),
|
||||||
},
|
},
|
||||||
OverlayPath: func(path string) string {
|
ShouldOverlay: func(path string) bool {
|
||||||
// Return the (overlay) import path when it should be overlaid, and
|
|
||||||
// "" if it should not.
|
|
||||||
if strings.HasPrefix(path, tinygoPath+"/src/") {
|
|
||||||
// Avoid issues with packages that are imported twice, one from
|
|
||||||
// GOPATH and one from TINYGOPATH.
|
|
||||||
path = path[len(tinygoPath+"/src/"):]
|
|
||||||
}
|
|
||||||
switch path {
|
switch path {
|
||||||
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync", "testing":
|
case "machine", "os", "reflect", "runtime", "runtime/volatile", "sync":
|
||||||
return path
|
return true
|
||||||
default:
|
default:
|
||||||
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
if strings.HasPrefix(path, "device/") || strings.HasPrefix(path, "examples/") {
|
||||||
return path
|
return true
|
||||||
} else if path == "syscall" {
|
} else if path == "syscall" {
|
||||||
for _, tag := range c.BuildTags {
|
for _, tag := range c.BuildTags {
|
||||||
if tag == "baremetal" || tag == "darwin" {
|
if tag == "avr" || tag == "cortexm" || tag == "darwin" {
|
||||||
return path
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return false
|
||||||
},
|
},
|
||||||
TypeChecker: types.Config{
|
TypeChecker: types.Config{
|
||||||
Sizes: &StdSizes{
|
Sizes: &StdSizes{
|
||||||
@@ -270,12 +221,10 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
|
MaxAlign: int64(c.targetData.PrefTypeAlignment(c.i8ptrType)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Dir: wd,
|
Dir: wd,
|
||||||
TINYGOROOT: c.TINYGOROOT,
|
TINYGOROOT: c.TINYGOROOT,
|
||||||
CFlags: c.CFlags,
|
CFlags: c.CFlags,
|
||||||
ClangHeaders: c.ClangHeaders,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(mainPath, ".go") {
|
if strings.HasSuffix(mainPath, ".go") {
|
||||||
_, err = lprogram.ImportFile(mainPath)
|
_, err = lprogram.ImportFile(mainPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -287,13 +236,12 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = lprogram.Import("runtime", "")
|
_, err = lprogram.Import("runtime", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = lprogram.Parse(c.TestConfig.CompileTestBinary)
|
err = lprogram.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
@@ -316,7 +264,39 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
|
|
||||||
var frames []*Frame
|
var frames []*Frame
|
||||||
|
|
||||||
c.loadASTComments(lprogram)
|
// Declare all named struct types.
|
||||||
|
for _, t := range c.ir.NamedTypes {
|
||||||
|
if named, ok := t.Type.Type().(*types.Named); ok {
|
||||||
|
if _, ok := named.Underlying().(*types.Struct); ok {
|
||||||
|
t.LLVMType = c.ctx.StructCreateNamed(named.Obj().Pkg().Path() + "." + named.Obj().Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all named struct types.
|
||||||
|
for _, t := range c.ir.NamedTypes {
|
||||||
|
if named, ok := t.Type.Type().(*types.Named); ok {
|
||||||
|
if st, ok := named.Underlying().(*types.Struct); ok {
|
||||||
|
llvmType := c.getLLVMType(st)
|
||||||
|
t.LLVMType.StructSetBody(llvmType.StructElementTypes(), false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare all globals.
|
||||||
|
for _, g := range c.ir.Globals {
|
||||||
|
typ := g.Type().(*types.Pointer).Elem()
|
||||||
|
llvmType := c.getLLVMType(typ)
|
||||||
|
global := c.mod.NamedGlobal(g.LinkName())
|
||||||
|
if global.IsNil() {
|
||||||
|
global = llvm.AddGlobal(c.mod, llvmType, g.LinkName())
|
||||||
|
}
|
||||||
|
g.LLVMGlobal = global
|
||||||
|
if !g.IsExtern() {
|
||||||
|
global.SetLinkage(llvm.InternalLinkage)
|
||||||
|
global.SetInitializer(c.getZeroValue(llvmType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Declare all functions.
|
// Declare all functions.
|
||||||
for _, f := range c.ir.Functions {
|
for _, f := range c.ir.Functions {
|
||||||
@@ -364,15 +344,13 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
// would be optimized away.
|
// would be optimized away.
|
||||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||||
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
|
realMain.SetLinkage(llvm.ExternalLinkage) // keep alive until goroutine lowering
|
||||||
|
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.ExternalLinkage)
|
||||||
// Make sure these functions are kept in tact during TinyGo transformation passes.
|
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.ExternalLinkage)
|
||||||
for _, name := range functionsUsedInTransforms {
|
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.ExternalLinkage)
|
||||||
fn := c.mod.NamedFunction(name)
|
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
|
||||||
if fn.IsNil() {
|
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.ExternalLinkage)
|
||||||
continue
|
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.ExternalLinkage)
|
||||||
}
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.ExternalLinkage)
|
||||||
fn.SetLinkage(llvm.ExternalLinkage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load some attributes
|
// Load some attributes
|
||||||
getAttr := func(attrName string) llvm.Attribute {
|
getAttr := func(attrName string) llvm.Attribute {
|
||||||
@@ -392,15 +370,6 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
// See emitNilCheck in asserts.go.
|
// See emitNilCheck in asserts.go.
|
||||||
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
|
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, nocapture)
|
||||||
|
|
||||||
// This function is necessary for tracking pointers on the stack in a
|
|
||||||
// portable way (see gc.go). Indicate to the optimizer that the only thing
|
|
||||||
// we'll do is read the pointer.
|
|
||||||
trackPointer := c.mod.NamedFunction("runtime.trackPointer")
|
|
||||||
if !trackPointer.IsNil() {
|
|
||||||
trackPointer.AddAttributeAtIndex(1, nocapture)
|
|
||||||
trackPointer.AddAttributeAtIndex(1, readonly)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Memory copy operations do not capture pointers, even though some weird
|
// Memory copy operations do not capture pointers, even though some weird
|
||||||
// pointer arithmetic is happening in the Go implementation.
|
// pointer arithmetic is happening in the Go implementation.
|
||||||
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
|
for _, fnName := range []string{"runtime.memcpy", "runtime.memmove"} {
|
||||||
@@ -433,22 +402,6 @@ func (c *Compiler) Compile(mainPath string) []error {
|
|||||||
return c.diagnostics
|
return c.diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRuntimeType obtains a named type from the runtime package and returns it
|
|
||||||
// as a Go type.
|
|
||||||
func (c *Compiler) getRuntimeType(name string) types.Type {
|
|
||||||
return c.ir.Program.ImportedPackage("runtime").Type(name).Type()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLLVMRuntimeType obtains a named type from the runtime package and returns
|
|
||||||
// it as a LLVM type, creating it if necessary. It is a shorthand for
|
|
||||||
// getLLVMType(getRuntimeType(name)).
|
|
||||||
func (c *Compiler) getLLVMRuntimeType(name string) llvm.Type {
|
|
||||||
return c.getLLVMType(c.getRuntimeType(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLLVMType creates and returns a LLVM type for a Go type. In the case of
|
|
||||||
// named struct types (or Go types implemented as named LLVM structs such as
|
|
||||||
// strings) it also creates it first if necessary.
|
|
||||||
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
||||||
switch typ := goType.(type) {
|
switch typ := goType.(type) {
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
@@ -477,7 +430,7 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
|||||||
case types.Complex128:
|
case types.Complex128:
|
||||||
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
|
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
|
||||||
case types.String, types.UntypedString:
|
case types.String, types.UntypedString:
|
||||||
return c.getLLVMRuntimeType("_string")
|
return c.mod.GetTypeByName("runtime._string")
|
||||||
case types.Uintptr:
|
case types.Uintptr:
|
||||||
return c.uintptrType
|
return c.uintptrType
|
||||||
case types.UnsafePointer:
|
case types.UnsafePointer:
|
||||||
@@ -486,23 +439,16 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
|||||||
panic("unknown basic type: " + typ.String())
|
panic("unknown basic type: " + typ.String())
|
||||||
}
|
}
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
return llvm.PointerType(c.getLLVMRuntimeType("channel"), 0)
|
return llvm.PointerType(c.mod.GetTypeByName("runtime.channel"), 0)
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
return c.getLLVMRuntimeType("_interface")
|
return c.mod.GetTypeByName("runtime._interface")
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
return llvm.PointerType(c.getLLVMRuntimeType("hashmap"), 0)
|
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0)
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
if st, ok := typ.Underlying().(*types.Struct); ok {
|
if _, ok := typ.Underlying().(*types.Struct); ok {
|
||||||
// Structs are a special case. While other named types are ignored
|
llvmType := c.mod.GetTypeByName(typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
|
||||||
// in LLVM IR, named structs are implemented as named structs in
|
|
||||||
// LLVM. This is because it is otherwise impossible to create
|
|
||||||
// self-referencing types such as linked lists.
|
|
||||||
llvmName := typ.Obj().Pkg().Path() + "." + typ.Obj().Name()
|
|
||||||
llvmType := c.mod.GetTypeByName(llvmName)
|
|
||||||
if llvmType.IsNil() {
|
if llvmType.IsNil() {
|
||||||
llvmType = c.ctx.StructCreateNamed(llvmName)
|
panic("underlying type not found: " + typ.Obj().Pkg().Path() + "." + typ.Obj().Name())
|
||||||
underlying := c.getLLVMType(st)
|
|
||||||
llvmType.StructSetBody(underlying.StructElementTypes(), false)
|
|
||||||
}
|
}
|
||||||
return llvmType
|
return llvmType
|
||||||
}
|
}
|
||||||
@@ -564,6 +510,42 @@ func (c *Compiler) getLLVMType(goType types.Type) llvm.Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a zero LLVM value for any LLVM type. Setting this value as an
|
||||||
|
// initializer has the same effect as setting 'zeroinitializer' on a value.
|
||||||
|
// Sadly, I haven't found a way to do it directly with the Go API but this works
|
||||||
|
// just fine.
|
||||||
|
func (c *Compiler) getZeroValue(typ llvm.Type) llvm.Value {
|
||||||
|
switch typ.TypeKind() {
|
||||||
|
case llvm.ArrayTypeKind:
|
||||||
|
subTyp := typ.ElementType()
|
||||||
|
subVal := c.getZeroValue(subTyp)
|
||||||
|
vals := make([]llvm.Value, typ.ArrayLength())
|
||||||
|
for i := range vals {
|
||||||
|
vals[i] = subVal
|
||||||
|
}
|
||||||
|
return llvm.ConstArray(subTyp, vals)
|
||||||
|
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||||
|
return llvm.ConstFloat(typ, 0.0)
|
||||||
|
case llvm.IntegerTypeKind:
|
||||||
|
return llvm.ConstInt(typ, 0, false)
|
||||||
|
case llvm.PointerTypeKind:
|
||||||
|
return llvm.ConstPointerNull(typ)
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
types := typ.StructElementTypes()
|
||||||
|
vals := make([]llvm.Value, len(types))
|
||||||
|
for i, subTyp := range types {
|
||||||
|
vals[i] = c.getZeroValue(subTyp)
|
||||||
|
}
|
||||||
|
if typ.StructName() != "" {
|
||||||
|
return llvm.ConstNamedStruct(typ, vals)
|
||||||
|
} else {
|
||||||
|
return c.ctx.ConstStruct(vals, false)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic("unknown LLVM zero inititializer: " + typ.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer.
|
// Is this a pointer type of some sort? Can be unsafe.Pointer or any *T pointer.
|
||||||
func isPointer(typ types.Type) bool {
|
func isPointer(typ types.Type) bool {
|
||||||
if _, ok := typ.(*types.Pointer); ok {
|
if _, ok := typ.(*types.Pointer); ok {
|
||||||
@@ -786,11 +768,6 @@ func (c *Compiler) parseFuncDecl(f *ir.Function) *Frame {
|
|||||||
// External/exported functions may not retain pointer values.
|
// External/exported functions may not retain pointer values.
|
||||||
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
||||||
if f.IsExported() {
|
if f.IsExported() {
|
||||||
// Set the wasm-import-module attribute if the function's module is set.
|
|
||||||
if f.Module() != "" {
|
|
||||||
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", f.Module())
|
|
||||||
frame.fn.LLVMFn.AddFunctionAttr(wasmImportModuleAttr)
|
|
||||||
}
|
|
||||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||||
for i, typ := range paramTypes {
|
for i, typ := range paramTypes {
|
||||||
@@ -847,10 +824,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
if c.DumpSSA {
|
if c.DumpSSA {
|
||||||
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
fmt.Printf("\nfunc %s:\n", frame.fn.Function)
|
||||||
}
|
}
|
||||||
if !frame.fn.LLVMFn.IsDeclaration() {
|
|
||||||
c.addError(frame.fn.Pos(), "function is already defined:"+frame.fn.LLVMFn.Name())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !frame.fn.IsExported() {
|
if !frame.fn.IsExported() {
|
||||||
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
frame.fn.LLVMFn.SetLinkage(llvm.InternalLinkage)
|
||||||
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
frame.fn.LLVMFn.SetUnnamedAddr(true)
|
||||||
@@ -865,10 +838,6 @@ func (c *Compiler) parseFunc(frame *Frame) {
|
|||||||
// Add LLVM inline hint to functions with //go:inline pragma.
|
// Add LLVM inline hint to functions with //go:inline pragma.
|
||||||
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
|
inline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("inlinehint"), 0)
|
||||||
frame.fn.LLVMFn.AddFunctionAttr(inline)
|
frame.fn.LLVMFn.AddFunctionAttr(inline)
|
||||||
case ir.InlineNone:
|
|
||||||
// Add LLVM attribute to always avoid inlining this function.
|
|
||||||
noinline := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
|
|
||||||
frame.fn.LLVMFn.AddFunctionAttr(noinline)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add debug info, if needed.
|
// Add debug info, if needed.
|
||||||
@@ -1021,56 +990,42 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
|||||||
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
|
frame.locals[instr] = llvm.Undef(c.getLLVMType(instr.Type()))
|
||||||
} else {
|
} else {
|
||||||
frame.locals[instr] = value
|
frame.locals[instr] = value
|
||||||
if len(*instr.Referrers()) != 0 && c.needsStackObjects() {
|
|
||||||
c.trackExpr(frame, instr, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case *ssa.DebugRef:
|
case *ssa.DebugRef:
|
||||||
// ignore
|
// ignore
|
||||||
case *ssa.Defer:
|
case *ssa.Defer:
|
||||||
c.emitDefer(frame, instr)
|
c.emitDefer(frame, instr)
|
||||||
case *ssa.Go:
|
case *ssa.Go:
|
||||||
|
if instr.Call.IsInvoke() {
|
||||||
|
c.addError(instr.Pos(), "todo: go on method receiver")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callee := instr.Call.StaticCallee()
|
||||||
|
if callee == nil {
|
||||||
|
c.addError(instr.Pos(), "todo: go on non-direct function (function pointer, etc.)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
calleeFn := c.ir.GetFunction(callee)
|
||||||
|
|
||||||
|
// Mark this function as a 'go' invocation and break invalid
|
||||||
|
// interprocedural optimizations. For example, heap-to-stack
|
||||||
|
// transformations are not sound as goroutines can outlive their parent.
|
||||||
|
calleeType := calleeFn.LLVMFn.Type()
|
||||||
|
calleeValue := c.builder.CreateBitCast(calleeFn.LLVMFn, c.i8ptrType, "")
|
||||||
|
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||||
|
calleeValue = c.builder.CreateBitCast(calleeValue, calleeType, "")
|
||||||
|
|
||||||
// Get all function parameters to pass to the goroutine.
|
// Get all function parameters to pass to the goroutine.
|
||||||
var params []llvm.Value
|
var params []llvm.Value
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
params = append(params, c.getValue(frame, param))
|
params = append(params, c.getValue(frame, param))
|
||||||
}
|
}
|
||||||
|
if !calleeFn.IsExported() {
|
||||||
// Start a new goroutine.
|
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
||||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
params = append(params, llvm.Undef(c.i8ptrType)) // parent coroutine handle
|
||||||
// Static callee is known. This makes it easier to start a new
|
|
||||||
// goroutine.
|
|
||||||
calleeFn := c.ir.GetFunction(callee)
|
|
||||||
if !calleeFn.IsExported() && c.selectScheduler() != "tasks" {
|
|
||||||
// For coroutine scheduling, this is only required when calling
|
|
||||||
// an external function.
|
|
||||||
// For tasks, because all params are stored in a single object,
|
|
||||||
// no unnecessary parameters should be stored anyway.
|
|
||||||
params = append(params, llvm.Undef(c.i8ptrType)) // context parameter
|
|
||||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
|
||||||
}
|
|
||||||
c.emitStartGoroutine(calleeFn.LLVMFn, params)
|
|
||||||
} else if !instr.Call.IsInvoke() {
|
|
||||||
// This is a function pointer.
|
|
||||||
// At the moment, two extra params are passed to the newly started
|
|
||||||
// goroutine:
|
|
||||||
// * The function context, for closures.
|
|
||||||
// * The parent handle (for coroutines) or the function pointer
|
|
||||||
// itself (for tasks).
|
|
||||||
funcPtr, context := c.decodeFuncValue(c.getValue(frame, instr.Call.Value), instr.Call.Value.Type().(*types.Signature))
|
|
||||||
params = append(params, context) // context parameter
|
|
||||||
switch c.selectScheduler() {
|
|
||||||
case "coroutines":
|
|
||||||
params = append(params, llvm.ConstPointerNull(c.i8ptrType)) // parent coroutine handle
|
|
||||||
case "tasks":
|
|
||||||
params = append(params, funcPtr)
|
|
||||||
default:
|
|
||||||
panic("unknown scheduler type")
|
|
||||||
}
|
|
||||||
c.emitStartGoroutine(funcPtr, params)
|
|
||||||
} else {
|
|
||||||
c.addError(instr.Pos(), "todo: go on interface call")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.createCall(calleeValue, params, "")
|
||||||
case *ssa.If:
|
case *ssa.If:
|
||||||
cond := c.getValue(frame, instr.Cond)
|
cond := c.getValue(frame, instr.Cond)
|
||||||
block := instr.Block()
|
block := instr.Block()
|
||||||
@@ -1097,7 +1052,7 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
|||||||
c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
|
c.builder.CreateRet(c.getValue(frame, instr.Results[0]))
|
||||||
} else {
|
} else {
|
||||||
// Multiple return values. Put them all in a struct.
|
// Multiple return values. Put them all in a struct.
|
||||||
retVal := llvm.ConstNull(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
retVal := c.getZeroValue(frame.fn.LLVMFn.Type().ElementType().ReturnType())
|
||||||
for i, result := range instr.Results {
|
for i, result := range instr.Results {
|
||||||
val := c.getValue(frame, result)
|
val := c.getValue(frame, result)
|
||||||
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
|
retVal = c.builder.CreateInsertValue(retVal, val, i, "")
|
||||||
@@ -1116,7 +1071,12 @@ func (c *Compiler) parseInstr(frame *Frame, instr ssa.Instruction) {
|
|||||||
// nothing to store
|
// nothing to store
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.builder.CreateStore(llvmVal, llvmAddr)
|
store := c.builder.CreateStore(llvmVal, llvmAddr)
|
||||||
|
valType := instr.Addr.Type().Underlying().(*types.Pointer).Elem()
|
||||||
|
if c.ir.IsVolatile(valType) {
|
||||||
|
// Volatile store, for memory-mapped registers.
|
||||||
|
store.SetVolatile(true)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
c.addError(instr.Pos(), "unknown instruction: "+instr.String())
|
c.addError(instr.Pos(), "unknown instruction: "+instr.String())
|
||||||
}
|
}
|
||||||
@@ -1319,11 +1279,11 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
|||||||
if fn := instr.StaticCallee(); fn != nil {
|
if fn := instr.StaticCallee(); fn != nil {
|
||||||
name := fn.RelString(nil)
|
name := fn.RelString(nil)
|
||||||
switch {
|
switch {
|
||||||
case name == "device/arm.ReadRegister" || name == "device/riscv.ReadRegister":
|
case name == "device/arm.ReadRegister":
|
||||||
return c.emitReadRegister(name, instr.Args)
|
return c.emitReadRegister(instr.Args)
|
||||||
case name == "device/arm.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
|
case name == "device/arm.Asm" || name == "device/avr.Asm":
|
||||||
return c.emitAsm(instr.Args)
|
return c.emitAsm(instr.Args)
|
||||||
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull" || name == "device/riscv.AsmFull":
|
case name == "device/arm.AsmFull" || name == "device/avr.AsmFull":
|
||||||
return c.emitAsmFull(frame, instr)
|
return c.emitAsmFull(frame, instr)
|
||||||
case strings.HasPrefix(name, "device/arm.SVCall"):
|
case strings.HasPrefix(name, "device/arm.SVCall"):
|
||||||
return c.emitSVCall(frame, instr.Args)
|
return c.emitSVCall(frame, instr.Args)
|
||||||
@@ -1363,7 +1323,10 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
|
|||||||
value := c.getValue(frame, instr.Value)
|
value := c.getValue(frame, instr.Value)
|
||||||
// This is a func value, which cannot be called directly. We have to
|
// This is a func value, which cannot be called directly. We have to
|
||||||
// extract the function pointer and context first from the func value.
|
// extract the function pointer and context first from the func value.
|
||||||
funcPtr, context := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
|
funcPtr, context, err := c.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
|
||||||
|
if err != nil {
|
||||||
|
return llvm.Value{}, err
|
||||||
|
}
|
||||||
c.emitNilCheck(frame, funcPtr, "fpcall")
|
c.emitNilCheck(frame, funcPtr, "fpcall")
|
||||||
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
|
return c.parseFunctionCall(frame, instr.Args, funcPtr, context, false), nil
|
||||||
}
|
}
|
||||||
@@ -1383,9 +1346,9 @@ func (c *Compiler) getValue(frame *Frame, expr ssa.Value) llvm.Value {
|
|||||||
}
|
}
|
||||||
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
|
return c.createFuncValue(fn.LLVMFn, llvm.Undef(c.i8ptrType), fn.Signature)
|
||||||
case *ssa.Global:
|
case *ssa.Global:
|
||||||
value := c.getGlobal(expr)
|
value := c.ir.GetGlobal(expr).LLVMGlobal
|
||||||
if value.IsNil() {
|
if value.IsNil() {
|
||||||
c.addError(expr.Pos(), "global not found: "+expr.RelString(nil))
|
c.addError(expr.Pos(), "global not found: "+c.ir.GetGlobal(expr).LinkName())
|
||||||
return llvm.Undef(c.getLLVMType(expr.Type()))
|
return llvm.Undef(c.getLLVMType(expr.Type()))
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
@@ -1410,6 +1373,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
switch expr := expr.(type) {
|
switch expr := expr.(type) {
|
||||||
case *ssa.Alloc:
|
case *ssa.Alloc:
|
||||||
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
|
typ := c.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
|
||||||
|
var buf llvm.Value
|
||||||
if expr.Heap {
|
if expr.Heap {
|
||||||
size := c.targetData.TypeAllocSize(typ)
|
size := c.targetData.TypeAllocSize(typ)
|
||||||
// Calculate ^uintptr(0)
|
// Calculate ^uintptr(0)
|
||||||
@@ -1418,17 +1382,17 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
// Size would be truncated if truncated to uintptr.
|
// Size would be truncated if truncated to uintptr.
|
||||||
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
|
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("value is too big (%v bytes)", size))
|
||||||
}
|
}
|
||||||
|
// TODO: escape analysis
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
buf := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
|
buf = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, expr.Comment)
|
||||||
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
buf = c.builder.CreateBitCast(buf, llvm.PointerType(typ, 0), "")
|
||||||
return buf, nil
|
|
||||||
} else {
|
} else {
|
||||||
buf := c.createEntryBlockAlloca(typ, expr.Comment)
|
buf = c.builder.CreateAlloca(typ, expr.Comment)
|
||||||
if c.targetData.TypeAllocSize(typ) != 0 {
|
if c.targetData.TypeAllocSize(typ) != 0 {
|
||||||
c.builder.CreateStore(llvm.ConstNull(typ), buf) // zero-initialize var
|
c.builder.CreateStore(c.getZeroValue(typ), buf) // zero-initialize var
|
||||||
}
|
}
|
||||||
return buf, nil
|
|
||||||
}
|
}
|
||||||
|
return buf, nil
|
||||||
case *ssa.BinOp:
|
case *ssa.BinOp:
|
||||||
x := c.getValue(frame, expr.X)
|
x := c.getValue(frame, expr.X)
|
||||||
y := c.getValue(frame, expr.Y)
|
y := c.getValue(frame, expr.Y)
|
||||||
@@ -1481,11 +1445,9 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
x := c.getValue(frame, expr.X)
|
x := c.getValue(frame, expr.X)
|
||||||
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
|
return c.parseConvert(expr.X.Type(), expr.Type(), x, expr.Pos())
|
||||||
case *ssa.Extract:
|
case *ssa.Extract:
|
||||||
if _, ok := expr.Tuple.(*ssa.Select); ok {
|
|
||||||
return c.getChanSelectResult(frame, expr), nil
|
|
||||||
}
|
|
||||||
value := c.getValue(frame, expr.Tuple)
|
value := c.getValue(frame, expr.Tuple)
|
||||||
return c.builder.CreateExtractValue(value, expr.Index, ""), nil
|
result := c.builder.CreateExtractValue(value, expr.Index, "")
|
||||||
|
return result, nil
|
||||||
case *ssa.Field:
|
case *ssa.Field:
|
||||||
value := c.getValue(frame, expr.X)
|
value := c.getValue(frame, expr.X)
|
||||||
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
if s := expr.X.Type().Underlying().(*types.Struct); s.NumFields() > 2 && s.Field(0).Name() == "C union" {
|
||||||
@@ -1493,12 +1455,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
// This could be done directly, but as this is a very infrequent
|
// This could be done directly, but as this is a very infrequent
|
||||||
// operation it's much easier to bitcast it through an alloca.
|
// operation it's much easier to bitcast it through an alloca.
|
||||||
resultType := c.getLLVMType(expr.Type())
|
resultType := c.getLLVMType(expr.Type())
|
||||||
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(value.Type(), "union.alloca")
|
alloca := c.builder.CreateAlloca(value.Type(), "")
|
||||||
c.builder.CreateStore(value, alloca)
|
c.builder.CreateStore(value, alloca)
|
||||||
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "union.bitcast")
|
bitcast := c.builder.CreateBitCast(alloca, llvm.PointerType(resultType, 0), "")
|
||||||
result := c.builder.CreateLoad(bitcast, "union.result")
|
return c.builder.CreateLoad(bitcast, ""), nil
|
||||||
c.emitLifetimeEnd(allocaPtr, allocaSize)
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
result := c.builder.CreateExtractValue(value, expr.Field, "")
|
||||||
return result, nil
|
return result, nil
|
||||||
@@ -1538,13 +1498,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
|
|
||||||
// Can't load directly from array (as index is non-constant), so have to
|
// Can't load directly from array (as index is non-constant), so have to
|
||||||
// do it using an alloca+gep+load.
|
// do it using an alloca+gep+load.
|
||||||
alloca, allocaPtr, allocaSize := c.createTemporaryAlloca(array.Type(), "index.alloca")
|
alloca := c.builder.CreateAlloca(array.Type(), "index.alloca")
|
||||||
c.builder.CreateStore(array, alloca)
|
c.builder.CreateStore(array, alloca)
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep")
|
ptr := c.builder.CreateInBoundsGEP(alloca, []llvm.Value{zero, index}, "index.gep")
|
||||||
result := c.builder.CreateLoad(ptr, "index.load")
|
return c.builder.CreateLoad(ptr, "index.load"), nil
|
||||||
c.emitLifetimeEnd(allocaPtr, allocaSize)
|
|
||||||
return result, nil
|
|
||||||
case *ssa.IndexAddr:
|
case *ssa.IndexAddr:
|
||||||
val := c.getValue(frame, expr.X)
|
val := c.getValue(frame, expr.X)
|
||||||
index := c.getValue(frame, expr.Index)
|
index := c.getValue(frame, expr.Index)
|
||||||
@@ -1661,11 +1619,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bounds checking.
|
// Bounds checking.
|
||||||
lenType := expr.Len.Type().(*types.Basic)
|
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, expr.Len.Type().(*types.Basic), expr.Cap.Type().(*types.Basic))
|
||||||
capType := expr.Cap.Type().(*types.Basic)
|
|
||||||
c.emitSliceBoundsCheck(frame, maxSize, sliceLen, sliceCap, sliceCap, lenType, capType, capType)
|
|
||||||
|
|
||||||
// Allocate the backing array.
|
// Allocate the backing array.
|
||||||
|
// TODO: escape analysis
|
||||||
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
|
sliceCapCast, err := c.parseConvert(expr.Cap.Type(), types.Typ[types.Uintptr], sliceCap, expr.Pos())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Value{}, err
|
return llvm.Value{}, err
|
||||||
@@ -1705,16 +1662,16 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key())
|
llvmKeyType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Key())
|
||||||
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
|
llvmValueType := c.getLLVMType(rangeVal.Type().Underlying().(*types.Map).Elem())
|
||||||
|
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(llvmKeyType, "range.key")
|
mapKeyAlloca := c.builder.CreateAlloca(llvmKeyType, "range.key")
|
||||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "range.value")
|
mapKeyPtr := c.builder.CreateBitCast(mapKeyAlloca, c.i8ptrType, "range.keyptr")
|
||||||
|
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "range.value")
|
||||||
|
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "range.valueptr")
|
||||||
ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
|
ok := c.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
|
||||||
|
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{c.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
|
||||||
tuple = c.builder.CreateInsertValue(tuple, ok, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, ok, 0, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
|
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapKeyAlloca, ""), 1, "")
|
||||||
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
|
tuple = c.builder.CreateInsertValue(tuple, c.builder.CreateLoad(mapValueAlloca, ""), 2, "")
|
||||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
|
||||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
|
||||||
return tuple, nil
|
return tuple, nil
|
||||||
}
|
}
|
||||||
case *ssa.Phi:
|
case *ssa.Phi:
|
||||||
@@ -1725,22 +1682,43 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
var iteratorType llvm.Type
|
var iteratorType llvm.Type
|
||||||
switch typ := expr.X.Type().Underlying().(type) {
|
switch typ := expr.X.Type().Underlying().(type) {
|
||||||
case *types.Basic: // string
|
case *types.Basic: // string
|
||||||
iteratorType = c.getLLVMRuntimeType("stringIterator")
|
iteratorType = c.mod.GetTypeByName("runtime.stringIterator")
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
iteratorType = c.getLLVMRuntimeType("hashmapIterator")
|
iteratorType = c.mod.GetTypeByName("runtime.hashmapIterator")
|
||||||
default:
|
default:
|
||||||
panic("unknown type in range: " + typ.String())
|
panic("unknown type in range: " + typ.String())
|
||||||
}
|
}
|
||||||
it, _, _ := c.createTemporaryAlloca(iteratorType, "range.it")
|
it := c.builder.CreateAlloca(iteratorType, "range.it")
|
||||||
c.builder.CreateStore(llvm.ConstNull(iteratorType), it)
|
c.builder.CreateStore(c.getZeroValue(iteratorType), it)
|
||||||
return it, nil
|
return it, nil
|
||||||
case *ssa.Select:
|
case *ssa.Select:
|
||||||
return c.emitSelect(frame, expr), nil
|
if len(expr.States) == 0 {
|
||||||
|
// Shortcuts for some simple selects.
|
||||||
|
llvmType := c.getLLVMType(expr.Type())
|
||||||
|
if expr.Blocking {
|
||||||
|
// Blocks forever:
|
||||||
|
// select {}
|
||||||
|
c.createRuntimeCall("deadlockStub", nil, "")
|
||||||
|
return llvm.Undef(llvmType), nil
|
||||||
|
} else {
|
||||||
|
// No-op:
|
||||||
|
// select {
|
||||||
|
// default:
|
||||||
|
// }
|
||||||
|
retval := llvm.Undef(llvmType)
|
||||||
|
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||||
|
return retval, nil // {-1, false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return llvm.Value{}, c.makeError(expr.Pos(), "unimplemented: "+expr.String())
|
||||||
case *ssa.Slice:
|
case *ssa.Slice:
|
||||||
|
if expr.Max != nil {
|
||||||
|
return llvm.Value{}, c.makeError(expr.Pos(), "todo: full slice expressions (with max): "+expr.Type().String())
|
||||||
|
}
|
||||||
value := c.getValue(frame, expr.X)
|
value := c.getValue(frame, expr.X)
|
||||||
|
|
||||||
var lowType, highType, maxType *types.Basic
|
var lowType, highType *types.Basic
|
||||||
var low, high, max llvm.Value
|
var low, high llvm.Value
|
||||||
|
|
||||||
if expr.Low != nil {
|
if expr.Low != nil {
|
||||||
lowType = expr.Low.Type().Underlying().(*types.Basic)
|
lowType = expr.Low.Type().Underlying().(*types.Basic)
|
||||||
@@ -1771,20 +1749,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
highType = types.Typ[types.Uintptr]
|
highType = types.Typ[types.Uintptr]
|
||||||
}
|
}
|
||||||
|
|
||||||
if expr.Max != nil {
|
|
||||||
maxType = expr.Max.Type().Underlying().(*types.Basic)
|
|
||||||
max = c.getValue(frame, expr.Max)
|
|
||||||
if max.Type().IntTypeWidth() < c.uintptrType.IntTypeWidth() {
|
|
||||||
if maxType.Info()&types.IsUnsigned != 0 {
|
|
||||||
max = c.builder.CreateZExt(max, c.uintptrType, "")
|
|
||||||
} else {
|
|
||||||
max = c.builder.CreateSExt(max, c.uintptrType, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
maxType = types.Typ[types.Uintptr]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch typ := expr.X.Type().Underlying().(type) {
|
switch typ := expr.X.Type().Underlying().(type) {
|
||||||
case *types.Pointer: // pointer to array
|
case *types.Pointer: // pointer to array
|
||||||
// slice an array
|
// slice an array
|
||||||
@@ -1793,31 +1757,25 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
if high.IsNil() {
|
if high.IsNil() {
|
||||||
high = llvmLen
|
high = llvmLen
|
||||||
}
|
}
|
||||||
if max.IsNil() {
|
|
||||||
max = llvmLen
|
|
||||||
}
|
|
||||||
indices := []llvm.Value{
|
indices := []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
low,
|
low,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.emitSliceBoundsCheck(frame, llvmLen, low, high, max, lowType, highType, maxType)
|
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
|
||||||
|
|
||||||
// Truncate ints bigger than uintptr. This is after the bounds
|
// Truncate ints bigger than uintptr. This is after the bounds
|
||||||
// check so it's safe.
|
// check so it's safe.
|
||||||
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
|
||||||
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
|
||||||
}
|
|
||||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||||
}
|
}
|
||||||
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
if c.targetData.TypeAllocSize(low.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||||
max = c.builder.CreateTrunc(max, c.uintptrType, "")
|
low = c.builder.CreateTrunc(low, c.uintptrType, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
sliceLen := c.builder.CreateSub(high, low, "slice.len")
|
sliceLen := c.builder.CreateSub(high, low, "slice.len")
|
||||||
slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr")
|
slicePtr := c.builder.CreateInBoundsGEP(value, indices, "slice.ptr")
|
||||||
sliceCap := c.builder.CreateSub(max, low, "slice.cap")
|
sliceCap := c.builder.CreateSub(llvmLen, low, "slice.cap")
|
||||||
|
|
||||||
slice := c.ctx.ConstStruct([]llvm.Value{
|
slice := c.ctx.ConstStruct([]llvm.Value{
|
||||||
llvm.Undef(slicePtr.Type()),
|
llvm.Undef(slicePtr.Type()),
|
||||||
@@ -1837,11 +1795,8 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
if high.IsNil() {
|
if high.IsNil() {
|
||||||
high = oldLen
|
high = oldLen
|
||||||
}
|
}
|
||||||
if max.IsNil() {
|
|
||||||
max = oldCap
|
|
||||||
}
|
|
||||||
|
|
||||||
c.emitSliceBoundsCheck(frame, oldCap, low, high, max, lowType, highType, maxType)
|
c.emitSliceBoundsCheck(frame, oldCap, low, high, lowType, highType)
|
||||||
|
|
||||||
// Truncate ints bigger than uintptr. This is after the bounds
|
// Truncate ints bigger than uintptr. This is after the bounds
|
||||||
// check so it's safe.
|
// check so it's safe.
|
||||||
@@ -1851,13 +1806,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||||
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
high = c.builder.CreateTrunc(high, c.uintptrType, "")
|
||||||
}
|
}
|
||||||
if c.targetData.TypeAllocSize(max.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
|
||||||
max = c.builder.CreateTrunc(max, c.uintptrType, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
||||||
newLen := c.builder.CreateSub(high, low, "")
|
newLen := c.builder.CreateSub(high, low, "")
|
||||||
newCap := c.builder.CreateSub(max, low, "")
|
newCap := c.builder.CreateSub(oldCap, low, "")
|
||||||
slice := c.ctx.ConstStruct([]llvm.Value{
|
slice := c.ctx.ConstStruct([]llvm.Value{
|
||||||
llvm.Undef(newPtr.Type()),
|
llvm.Undef(newPtr.Type()),
|
||||||
llvm.Undef(c.uintptrType),
|
llvm.Undef(c.uintptrType),
|
||||||
@@ -1873,18 +1825,13 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String())
|
return llvm.Value{}, c.makeError(expr.Pos(), "unknown slice type: "+typ.String())
|
||||||
}
|
}
|
||||||
// slice a string
|
// slice a string
|
||||||
if expr.Max != nil {
|
|
||||||
// This might as well be a panic, as the frontend should have
|
|
||||||
// handled this already.
|
|
||||||
return llvm.Value{}, c.makeError(expr.Pos(), "slicing a string with a max parameter is not allowed by the spec")
|
|
||||||
}
|
|
||||||
oldPtr := c.builder.CreateExtractValue(value, 0, "")
|
oldPtr := c.builder.CreateExtractValue(value, 0, "")
|
||||||
oldLen := c.builder.CreateExtractValue(value, 1, "")
|
oldLen := c.builder.CreateExtractValue(value, 1, "")
|
||||||
if high.IsNil() {
|
if high.IsNil() {
|
||||||
high = oldLen
|
high = oldLen
|
||||||
}
|
}
|
||||||
|
|
||||||
c.emitSliceBoundsCheck(frame, oldLen, low, high, high, lowType, highType, maxType)
|
c.emitSliceBoundsCheck(frame, oldLen, low, high, lowType, highType)
|
||||||
|
|
||||||
// Truncate ints bigger than uintptr. This is after the bounds
|
// Truncate ints bigger than uintptr. This is after the bounds
|
||||||
// check so it's safe.
|
// check so it's safe.
|
||||||
@@ -1897,7 +1844,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
|||||||
|
|
||||||
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
newPtr := c.builder.CreateInBoundsGEP(oldPtr, []llvm.Value{low}, "")
|
||||||
newLen := c.builder.CreateSub(high, low, "")
|
newLen := c.builder.CreateSub(high, low, "")
|
||||||
str := llvm.Undef(c.getLLVMRuntimeType("_string"))
|
str := llvm.Undef(c.mod.GetTypeByName("runtime._string"))
|
||||||
str = c.builder.CreateInsertValue(str, newPtr, 0, "")
|
str = c.builder.CreateInsertValue(str, newPtr, 0, "")
|
||||||
str = c.builder.CreateInsertValue(str, newLen, 1, "")
|
str = c.builder.CreateInsertValue(str, newLen, 1, "")
|
||||||
return str, nil
|
return str, nil
|
||||||
@@ -2276,7 +2223,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
|||||||
global.SetUnnamedAddr(true)
|
global.SetUnnamedAddr(true)
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
|
strPtr := c.builder.CreateInBoundsGEP(global, []llvm.Value{zero, zero}, "")
|
||||||
strObj := llvm.ConstNamedStruct(c.getLLVMRuntimeType("_string"), []llvm.Value{strPtr, strLen})
|
strObj := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._string"), []llvm.Value{strPtr, strLen})
|
||||||
return strObj
|
return strObj
|
||||||
} else if typ.Kind() == types.UnsafePointer {
|
} else if typ.Kind() == types.UnsafePointer {
|
||||||
if !expr.IsNil() {
|
if !expr.IsNil() {
|
||||||
@@ -2314,12 +2261,12 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
|||||||
if expr.Value != nil {
|
if expr.Value != nil {
|
||||||
panic("expected nil chan constant")
|
panic("expected nil chan constant")
|
||||||
}
|
}
|
||||||
return llvm.ConstNull(c.getLLVMType(expr.Type()))
|
return c.getZeroValue(c.getLLVMType(expr.Type()))
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
if expr.Value != nil {
|
if expr.Value != nil {
|
||||||
panic("expected nil signature constant")
|
panic("expected nil signature constant")
|
||||||
}
|
}
|
||||||
return llvm.ConstNull(c.getLLVMType(expr.Type()))
|
return c.getZeroValue(c.getLLVMType(expr.Type()))
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
if expr.Value != nil {
|
if expr.Value != nil {
|
||||||
panic("expected nil interface constant")
|
panic("expected nil interface constant")
|
||||||
@@ -2329,7 +2276,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
|||||||
llvm.ConstInt(c.uintptrType, 0, false),
|
llvm.ConstInt(c.uintptrType, 0, false),
|
||||||
llvm.ConstPointerNull(c.i8ptrType),
|
llvm.ConstPointerNull(c.i8ptrType),
|
||||||
}
|
}
|
||||||
return llvm.ConstNamedStruct(c.getLLVMRuntimeType("_interface"), fields)
|
return llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
if expr.Value != nil {
|
if expr.Value != nil {
|
||||||
panic("expected nil pointer constant")
|
panic("expected nil pointer constant")
|
||||||
@@ -2354,7 +2301,7 @@ func (c *Compiler) parseConst(prefix string, expr *ssa.Const) llvm.Value {
|
|||||||
panic("non-nil map constant")
|
panic("non-nil map constant")
|
||||||
}
|
}
|
||||||
llvmType := c.getLLVMType(typ)
|
llvmType := c.getLLVMType(typ)
|
||||||
return llvm.ConstNull(llvmType)
|
return c.getZeroValue(llvmType)
|
||||||
default:
|
default:
|
||||||
panic("unknown constant: " + expr.String())
|
panic("unknown constant: " + expr.String())
|
||||||
}
|
}
|
||||||
@@ -2426,8 +2373,6 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
|||||||
switch typeFrom.Elem().(*types.Basic).Kind() {
|
switch typeFrom.Elem().(*types.Basic).Kind() {
|
||||||
case types.Byte:
|
case types.Byte:
|
||||||
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
|
return c.createRuntimeCall("stringFromBytes", []llvm.Value{value}, ""), nil
|
||||||
case types.Rune:
|
|
||||||
return c.createRuntimeCall("stringFromRunes", []llvm.Value{value}, ""), nil
|
|
||||||
default:
|
default:
|
||||||
return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String())
|
return llvm.Value{}, c.makeError(pos, "todo: convert to string: "+typeFrom.String())
|
||||||
}
|
}
|
||||||
@@ -2514,10 +2459,8 @@ func (c *Compiler) parseConvert(typeFrom, typeTo types.Type, value llvm.Value, p
|
|||||||
switch elemType.Kind() {
|
switch elemType.Kind() {
|
||||||
case types.Byte:
|
case types.Byte:
|
||||||
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
|
return c.createRuntimeCall("stringToBytes", []llvm.Value{value}, ""), nil
|
||||||
case types.Rune:
|
|
||||||
return c.createRuntimeCall("stringToRunes", []llvm.Value{value}, ""), nil
|
|
||||||
default:
|
default:
|
||||||
panic("unexpected type in string to slice conversion")
|
return llvm.Value{}, c.makeError(pos, "todo: convert from string: "+elemType.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -2543,18 +2486,18 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
|||||||
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String())
|
return llvm.Value{}, c.makeError(unop.Pos(), "todo: unknown type for negate: "+unop.X.Type().Underlying().String())
|
||||||
}
|
}
|
||||||
case token.MUL: // *x, dereference pointer
|
case token.MUL: // *x, dereference pointer
|
||||||
unop.X.Type().Underlying().(*types.Pointer).Elem()
|
valType := unop.X.Type().Underlying().(*types.Pointer).Elem()
|
||||||
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
|
if c.targetData.TypeAllocSize(x.Type().ElementType()) == 0 {
|
||||||
// zero-length data
|
// zero-length data
|
||||||
return llvm.ConstNull(x.Type().ElementType()), nil
|
return c.getZeroValue(x.Type().ElementType()), nil
|
||||||
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
|
} else if strings.HasSuffix(unop.X.String(), "$funcaddr") {
|
||||||
// CGo function pointer. The cgo part has rewritten CGo function
|
// CGo function pointer. The cgo part has rewritten CGo function
|
||||||
// pointers as stub global variables of the form:
|
// pointers as stub global variables of the form:
|
||||||
// var C.add unsafe.Pointer
|
// var C.add unsafe.Pointer
|
||||||
// Instead of a load from the global, create a bitcast of the
|
// Instead of a load from the global, create a bitcast of the
|
||||||
// function pointer itself.
|
// function pointer itself.
|
||||||
globalName := c.getGlobalInfo(unop.X.(*ssa.Global)).linkName
|
global := c.ir.GetGlobal(unop.X.(*ssa.Global))
|
||||||
name := globalName[:len(globalName)-len("$funcaddr")]
|
name := global.LinkName()[:len(global.LinkName())-len("$funcaddr")]
|
||||||
fn := c.mod.NamedFunction(name)
|
fn := c.mod.NamedFunction(name)
|
||||||
if fn.IsNil() {
|
if fn.IsNil() {
|
||||||
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
|
return llvm.Value{}, c.makeError(unop.Pos(), "cgo function not found: "+name)
|
||||||
@@ -2563,6 +2506,10 @@ func (c *Compiler) parseUnOp(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
|||||||
} else {
|
} else {
|
||||||
c.emitNilCheck(frame, x, "deref")
|
c.emitNilCheck(frame, x, "deref")
|
||||||
load := c.builder.CreateLoad(x, "")
|
load := c.builder.CreateLoad(x, "")
|
||||||
|
if c.ir.IsVolatile(valType) {
|
||||||
|
// Volatile load, for memory-mapped registers.
|
||||||
|
load.SetVolatile(true)
|
||||||
|
}
|
||||||
return load, nil
|
return load, nil
|
||||||
}
|
}
|
||||||
case token.XOR: // ^x, toggle all bits in integer
|
case token.XOR: // ^x, toggle all bits in integer
|
||||||
@@ -2713,7 +2660,7 @@ func (c *Compiler) ExternalInt64AsPtr() error {
|
|||||||
// correct calling convention.
|
// correct calling convention.
|
||||||
fn.SetLinkage(llvm.InternalLinkage)
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
fn.SetUnnamedAddr(true)
|
fn.SetUnnamedAddr(true)
|
||||||
entryBlock := c.ctx.AddBasicBlock(externalFn, "entry")
|
entryBlock := llvm.AddBasicBlock(externalFn, "entry")
|
||||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
c.builder.SetInsertPointAtEnd(entryBlock)
|
||||||
var callParams []llvm.Value
|
var callParams []llvm.Value
|
||||||
if fnType.ReturnType() == int64Type {
|
if fnType.ReturnType() == int64Type {
|
||||||
|
|||||||
+10
-13
@@ -29,7 +29,7 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
|||||||
frame.deferClosureFuncs = make(map[*ir.Function]int)
|
frame.deferClosureFuncs = make(map[*ir.Function]int)
|
||||||
|
|
||||||
// Create defer list pointer.
|
// Create defer list pointer.
|
||||||
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
|
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
|
||||||
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
||||||
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
|||||||
|
|
||||||
// Make a struct out of the collected values to put in the defer frame.
|
// Make a struct out of the collected values to put in the defer frame.
|
||||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFrame := llvm.ConstNull(deferFrameType)
|
deferFrame := c.getZeroValue(deferFrameType)
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||||
}
|
}
|
||||||
@@ -130,9 +130,6 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
|||||||
// Put this struct in an alloca.
|
// Put this struct in an alloca.
|
||||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||||
c.builder.CreateStore(deferFrame, alloca)
|
c.builder.CreateStore(deferFrame, alloca)
|
||||||
if c.needsStackObjects() {
|
|
||||||
c.trackPointer(alloca)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push it on top of the linked list by replacing deferPtr.
|
// Push it on top of the linked list by replacing deferPtr.
|
||||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||||
@@ -157,10 +154,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Create loop.
|
// Create loop.
|
||||||
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||||
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||||
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||||
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||||
c.builder.CreateBr(loophead)
|
c.builder.CreateBr(loophead)
|
||||||
|
|
||||||
// Create loop head:
|
// Create loop head:
|
||||||
@@ -192,7 +189,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
|||||||
// Create switch case, for example:
|
// Create switch case, for example:
|
||||||
// case 0:
|
// case 0:
|
||||||
// // run first deferred call
|
// // run first deferred call
|
||||||
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
||||||
c.builder.SetInsertPointAtEnd(block)
|
c.builder.SetInsertPointAtEnd(block)
|
||||||
switch callback := callback.(type) {
|
switch callback := callback.(type) {
|
||||||
@@ -203,7 +200,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||||
}
|
}
|
||||||
@@ -234,7 +231,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
|||||||
// Direct call.
|
// Direct call.
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
for _, param := range callback.Params {
|
for _, param := range callback.Params {
|
||||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||||
}
|
}
|
||||||
@@ -263,7 +260,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
|||||||
case *ssa.MakeClosure:
|
case *ssa.MakeClosure:
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||||
|
|||||||
+82
-98
@@ -51,7 +51,7 @@ func (c *Compiler) LowerFuncValues() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Find all func values used in the program with their signatures.
|
// Find all func values used in the program with their signatures.
|
||||||
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
|
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||||
signatures := map[string]*funcSignatureInfo{}
|
signatures := map[string]*funcSignatureInfo{}
|
||||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
if global.Type() != funcValueWithSignaturePtr {
|
if global.Type() != funcValueWithSignaturePtr {
|
||||||
@@ -152,19 +152,19 @@ func (c *Compiler) LowerFuncValues() {
|
|||||||
// There are multiple functions used in a func value that
|
// There are multiple functions used in a func value that
|
||||||
// implement this signature.
|
// implement this signature.
|
||||||
// What we'll do is transform the following:
|
// What we'll do is transform the following:
|
||||||
// rawPtr := runtime.getFuncPtr(func.ptr)
|
// rawPtr := runtime.getFuncPtr(fn)
|
||||||
// if rawPtr == nil {
|
// if func.rawPtr == nil {
|
||||||
// runtime.nilPanic()
|
// runtime.nilpanic()
|
||||||
// }
|
// }
|
||||||
// result := rawPtr(...args, func.context)
|
// result := func.rawPtr(...args, func.context)
|
||||||
// into this:
|
// into this:
|
||||||
// if false {
|
// if false {
|
||||||
// runtime.nilPanic()
|
// runtime.nilpanic()
|
||||||
// }
|
// }
|
||||||
// var result // Phi
|
// var result // Phi
|
||||||
// switch fn.id {
|
// switch fn.id {
|
||||||
// case 0:
|
// case 0:
|
||||||
// runtime.nilPanic()
|
// runtime.nilpanic()
|
||||||
// case 1:
|
// case 1:
|
||||||
// result = call first implementation...
|
// result = call first implementation...
|
||||||
// case 2:
|
// case 2:
|
||||||
@@ -175,111 +175,95 @@ func (c *Compiler) LowerFuncValues() {
|
|||||||
|
|
||||||
// Remove some casts, checks, and the old call which we're going
|
// Remove some casts, checks, and the old call which we're going
|
||||||
// to replace.
|
// to replace.
|
||||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
var funcCall llvm.Value
|
||||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
for _, inttoptr := range getUses(callIntPtr) {
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
|
||||||
panic("expected a inttoptr")
|
|
||||||
}
|
|
||||||
for _, use := range getUses(inttoptr) {
|
|
||||||
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
|
|
||||||
use.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
inttoptr.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
callIntPtr.EraseFromParentAsInstruction()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if callIntPtr.IsAIntToPtrInst().IsNil() {
|
|
||||||
panic("expected inttoptr")
|
panic("expected inttoptr")
|
||||||
}
|
}
|
||||||
for _, ptrUse := range getUses(callIntPtr) {
|
for _, ptrUse := range getUses(inttoptr) {
|
||||||
if !ptrUse.IsABitCastInst().IsNil() {
|
if !ptrUse.IsABitCastInst().IsNil() {
|
||||||
for _, bitcastUse := range getUses(ptrUse) {
|
for _, bitcastUse := range getUses(ptrUse) {
|
||||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() {
|
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
|
||||||
panic("expected a call instruction")
|
|
||||||
}
|
|
||||||
switch bitcastUse.CalledValue().Name() {
|
|
||||||
case "runtime.isnil":
|
|
||||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
|
||||||
bitcastUse.EraseFromParentAsInstruction()
|
|
||||||
default:
|
|
||||||
panic("expected a call to runtime.isnil")
|
panic("expected a call to runtime.isnil")
|
||||||
}
|
}
|
||||||
|
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||||
|
bitcastUse.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
ptrUse.EraseFromParentAsInstruction()
|
||||||
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
|
||||||
return c.builder.CreateCall(funcPtr, params, "")
|
if !funcCall.IsNil() {
|
||||||
}, functions)
|
panic("multiple calls on a single runtime.getFuncPtr")
|
||||||
|
}
|
||||||
|
funcCall = ptrUse
|
||||||
} else {
|
} else {
|
||||||
panic("unexpected getFuncPtrCall")
|
panic("unexpected getFuncPtrCall")
|
||||||
}
|
}
|
||||||
ptrUse.EraseFromParentAsInstruction()
|
|
||||||
}
|
}
|
||||||
callIntPtr.EraseFromParentAsInstruction()
|
}
|
||||||
|
if funcCall.IsNil() {
|
||||||
|
panic("expected exactly one call use of a runtime.getFuncPtr")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block that cannot be reached with correct funcValues (to
|
||||||
|
// help the optimizer).
|
||||||
|
c.builder.SetInsertPointBefore(funcCall)
|
||||||
|
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
|
||||||
|
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Create the switch.
|
||||||
|
c.builder.SetInsertPointBefore(funcCall)
|
||||||
|
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||||
|
|
||||||
|
// Split right after the switch. We will need to insert a few
|
||||||
|
// basic blocks in this gap.
|
||||||
|
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||||
|
|
||||||
|
// The 0 case, which is actually a nil check.
|
||||||
|
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
|
||||||
|
c.builder.SetInsertPointAtEnd(nilBlock)
|
||||||
|
c.createRuntimeCall("nilpanic", nil, "")
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
||||||
|
|
||||||
|
// Gather the list of parameters for every call we're going to
|
||||||
|
// make.
|
||||||
|
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
|
||||||
|
for i := range callParams {
|
||||||
|
callParams[i] = funcCall.Operand(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the call produces a value, we need to get it using a PHI
|
||||||
|
// node.
|
||||||
|
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
||||||
|
phiValues := make([]llvm.Value, len(functions))
|
||||||
|
for i, fn := range functions {
|
||||||
|
// Insert a switch case.
|
||||||
|
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||||
|
c.builder.SetInsertPointAtEnd(bb)
|
||||||
|
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
|
||||||
|
c.builder.CreateBr(nextBlock)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||||
|
phiBlocks[i] = bb
|
||||||
|
phiValues[i] = result
|
||||||
|
}
|
||||||
|
// Create the PHI node so that the call result flows into the
|
||||||
|
// next block (after the split). This is only necessary when the
|
||||||
|
// call produced a value.
|
||||||
|
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||||
|
phi := c.builder.CreatePHI(funcCall.Type(), "")
|
||||||
|
phi.AddIncoming(phiValues, phiBlocks)
|
||||||
|
funcCall.ReplaceAllUsesWith(phi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, remove the old instructions.
|
||||||
|
funcCall.EraseFromParentAsInstruction()
|
||||||
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFuncLoweringSwitch creates a new switch on a function ID and inserts calls
|
|
||||||
// to the newly created direct calls. The funcID is the number to switch on,
|
|
||||||
// call is the call instruction to replace, and createCall is the callback that
|
|
||||||
// actually creates the new call. By changing createCall to something other than
|
|
||||||
// c.builder.CreateCall, instead of calling a function it can start a new
|
|
||||||
// goroutine for example.
|
|
||||||
func (c *Compiler) addFuncLoweringSwitch(funcID, call llvm.Value, createCall func(funcPtr llvm.Value, params []llvm.Value) llvm.Value, functions funcWithUsesList) {
|
|
||||||
// The block that cannot be reached with correct funcValues (to help the
|
|
||||||
// optimizer).
|
|
||||||
c.builder.SetInsertPointBefore(call)
|
|
||||||
defaultBlock := c.ctx.AddBasicBlock(call.InstructionParent().Parent(), "func.default")
|
|
||||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
|
||||||
c.builder.CreateUnreachable()
|
|
||||||
|
|
||||||
// Create the switch.
|
|
||||||
c.builder.SetInsertPointBefore(call)
|
|
||||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
|
||||||
|
|
||||||
// Split right after the switch. We will need to insert a few basic blocks
|
|
||||||
// in this gap.
|
|
||||||
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
|
||||||
|
|
||||||
// The 0 case, which is actually a nil check.
|
|
||||||
nilBlock := c.ctx.InsertBasicBlock(nextBlock, "func.nil")
|
|
||||||
c.builder.SetInsertPointAtEnd(nilBlock)
|
|
||||||
c.createRuntimeCall("nilPanic", nil, "")
|
|
||||||
c.builder.CreateUnreachable()
|
|
||||||
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
|
||||||
|
|
||||||
// Gather the list of parameters for every call we're going to make.
|
|
||||||
callParams := make([]llvm.Value, call.OperandsCount()-1)
|
|
||||||
for i := range callParams {
|
|
||||||
callParams[i] = call.Operand(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the call produces a value, we need to get it using a PHI
|
|
||||||
// node.
|
|
||||||
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
|
||||||
phiValues := make([]llvm.Value, len(functions))
|
|
||||||
for i, fn := range functions {
|
|
||||||
// Insert a switch case.
|
|
||||||
bb := c.ctx.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
|
||||||
c.builder.SetInsertPointAtEnd(bb)
|
|
||||||
result := createCall(fn.funcPtr, callParams)
|
|
||||||
c.builder.CreateBr(nextBlock)
|
|
||||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
|
||||||
phiBlocks[i] = bb
|
|
||||||
phiValues[i] = result
|
|
||||||
}
|
|
||||||
// Create the PHI node so that the call result flows into the
|
|
||||||
// next block (after the split). This is only necessary when the
|
|
||||||
// call produced a value.
|
|
||||||
if call.Type().TypeKind() != llvm.VoidTypeKind {
|
|
||||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
|
||||||
phi := c.builder.CreatePHI(call.Type(), "")
|
|
||||||
phi.AddIncoming(phiValues, phiBlocks)
|
|
||||||
call.ReplaceAllUsesWith(phi)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+23
-12
@@ -32,15 +32,10 @@ const (
|
|||||||
// funcImplementation picks an appropriate func value implementation for the
|
// funcImplementation picks an appropriate func value implementation for the
|
||||||
// target.
|
// target.
|
||||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||||
// Always pick the switch implementation, as it allows the use of blocking
|
if c.GOARCH == "wasm" {
|
||||||
// inside a function that is used as a func value.
|
|
||||||
switch c.selectScheduler() {
|
|
||||||
case "coroutines":
|
|
||||||
return funcValueSwitch
|
return funcValueSwitch
|
||||||
case "tasks":
|
} else {
|
||||||
return funcValueDoubleword
|
return funcValueDoubleword
|
||||||
default:
|
|
||||||
panic("unknown scheduler type")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,11 +48,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
|||||||
// Closure is: {context, function pointer}
|
// Closure is: {context, function pointer}
|
||||||
funcValueScalar = funcPtr
|
funcValueScalar = funcPtr
|
||||||
case funcValueSwitch:
|
case funcValueSwitch:
|
||||||
sigGlobal := c.getTypeCode(sig)
|
sigGlobal := c.getFuncSignature(sig)
|
||||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||||
if funcValueWithSignatureGlobal.IsNil() {
|
if funcValueWithSignatureGlobal.IsNil() {
|
||||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
|
||||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||||
sigGlobal,
|
sigGlobal,
|
||||||
@@ -78,6 +73,22 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
|||||||
return funcValue
|
return funcValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getFuncSignature returns a global for identification of a particular function
|
||||||
|
// signature. It is used in runtime.funcValueWithSignature and in calls to
|
||||||
|
// getFuncPtr.
|
||||||
|
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
|
||||||
|
typeCodeName := getTypeCodeName(sig)
|
||||||
|
sigGlobalName := "reflect/types.type:" + typeCodeName
|
||||||
|
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||||
|
if sigGlobal.IsNil() {
|
||||||
|
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||||
|
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
|
||||||
|
sigGlobal.SetGlobalConstant(true)
|
||||||
|
sigGlobal.SetLinkage(llvm.InternalLinkage)
|
||||||
|
}
|
||||||
|
return sigGlobal
|
||||||
|
}
|
||||||
|
|
||||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||||
// a cheap operation.
|
// a cheap operation.
|
||||||
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||||
@@ -92,14 +103,14 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
|||||||
|
|
||||||
// decodeFuncValue extracts the context and the function pointer from this func
|
// decodeFuncValue extracts the context and the function pointer from this func
|
||||||
// value. This may be an expensive operation.
|
// value. This may be an expensive operation.
|
||||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
|
||||||
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||||
switch c.funcImplementation() {
|
switch c.funcImplementation() {
|
||||||
case funcValueDoubleword:
|
case funcValueDoubleword:
|
||||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||||
case funcValueSwitch:
|
case funcValueSwitch:
|
||||||
llvmSig := c.getRawFuncType(sig)
|
llvmSig := c.getRawFuncType(sig)
|
||||||
sigGlobal := c.getTypeCode(sig)
|
sigGlobal := c.getFuncSignature(sig)
|
||||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||||
default:
|
default:
|
||||||
@@ -115,7 +126,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
|||||||
rawPtr := c.getRawFuncType(typ)
|
rawPtr := c.getRawFuncType(typ)
|
||||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||||
case funcValueSwitch:
|
case funcValueSwitch:
|
||||||
return c.getLLVMRuntimeType("funcValue")
|
return c.mod.GetTypeByName("runtime.funcValue")
|
||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -1,16 +1,5 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
// This file implements lowering for the goroutine scheduler. There are two
|
|
||||||
// scheduler implementations, one based on tasks (like RTOSes and the main Go
|
|
||||||
// runtime) and one based on a coroutine compiler transformation. The task based
|
|
||||||
// implementation requires very little work from the compiler but is not very
|
|
||||||
// portable (in particular, it is very hard if not impossible to support on
|
|
||||||
// WebAssembly). The coroutine based one requires a lot of work by the compiler
|
|
||||||
// to implement, but can run virtually anywhere with a single scheduler
|
|
||||||
// implementation.
|
|
||||||
//
|
|
||||||
// The below description is for the coroutine based scheduler.
|
|
||||||
//
|
|
||||||
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
||||||
// scheduler at runtime. It uses coroutine support in LLVM for this
|
// scheduler at runtime. It uses coroutine support in LLVM for this
|
||||||
// transformation: https://llvm.org/docs/Coroutines.html
|
// transformation: https://llvm.org/docs/Coroutines.html
|
||||||
@@ -73,7 +62,7 @@ package compiler
|
|||||||
// llvm.suspend(hdl) // suspend point
|
// llvm.suspend(hdl) // suspend point
|
||||||
// println("some other operation")
|
// println("some other operation")
|
||||||
// var i *int // allocate space on the stack for the return value
|
// var i *int // allocate space on the stack for the return value
|
||||||
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
|
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||||
// println("done", *i)
|
// println("done", *i)
|
||||||
@@ -117,69 +106,10 @@ type asyncFunc struct {
|
|||||||
unreachableBlock llvm.BasicBlock
|
unreachableBlock llvm.BasicBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// LowerGoroutines performs some IR transformations necessary to support
|
// LowerGoroutines is a pass called during optimization that transforms the IR
|
||||||
// goroutines. It does something different based on whether it uses the
|
// into one where all blocking functions are turned into goroutines and blocking
|
||||||
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
// calls into await calls.
|
||||||
// are necessary at all.
|
|
||||||
func (c *Compiler) LowerGoroutines() error {
|
func (c *Compiler) LowerGoroutines() error {
|
||||||
switch c.selectScheduler() {
|
|
||||||
case "coroutines":
|
|
||||||
return c.lowerCoroutines()
|
|
||||||
case "tasks":
|
|
||||||
return c.lowerTasks()
|
|
||||||
default:
|
|
||||||
panic("unknown scheduler type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowerTasks starts the main goroutine and then runs the scheduler.
|
|
||||||
// This is enough compiler-level transformation for the task-based scheduler.
|
|
||||||
func (c *Compiler) lowerTasks() error {
|
|
||||||
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
|
||||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
|
||||||
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
|
||||||
}
|
|
||||||
mainCall := uses[0]
|
|
||||||
|
|
||||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
|
||||||
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
|
|
||||||
// Program needs a scheduler. Start main.main as a goroutine and start
|
|
||||||
// the scheduler.
|
|
||||||
realMainWrapper := c.createGoroutineStartWrapper(realMain)
|
|
||||||
c.builder.SetInsertPointBefore(mainCall)
|
|
||||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
|
||||||
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
|
|
||||||
c.createRuntimeCall("scheduler", nil, "")
|
|
||||||
sleep := c.mod.NamedFunction("time.Sleep")
|
|
||||||
if !sleep.IsNil() {
|
|
||||||
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Program doesn't need a scheduler. Call main.main directly.
|
|
||||||
c.builder.SetInsertPointBefore(mainCall)
|
|
||||||
params := []llvm.Value{
|
|
||||||
llvm.Undef(c.i8ptrType), // unused context parameter
|
|
||||||
llvm.Undef(c.i8ptrType), // unused coroutine handle
|
|
||||||
}
|
|
||||||
c.createCall(realMain, params, "")
|
|
||||||
// runtime.Goexit isn't needed so let it be optimized away by
|
|
||||||
// globalopt.
|
|
||||||
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
|
|
||||||
}
|
|
||||||
mainCall.EraseFromParentAsInstruction()
|
|
||||||
|
|
||||||
// main.main was set to external linkage during IR construction. Set it to
|
|
||||||
// internal linkage to enable interprocedural optimizations.
|
|
||||||
realMain.SetLinkage(llvm.InternalLinkage)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lowerCoroutines transforms the IR into one where all blocking functions are
|
|
||||||
// turned into goroutines and blocking calls into await calls. It also makes
|
|
||||||
// sure that the first coroutine is started and the coroutine scheduler will be
|
|
||||||
// run.
|
|
||||||
func (c *Compiler) lowerCoroutines() error {
|
|
||||||
needsScheduler, err := c.markAsyncFunctions()
|
needsScheduler, err := c.markAsyncFunctions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -214,6 +144,12 @@ func (c *Compiler) lowerCoroutines() error {
|
|||||||
// main.main was set to external linkage during IR construction. Set it to
|
// main.main was set to external linkage during IR construction. Set it to
|
||||||
// internal linkage to enable interprocedural optimizations.
|
// internal linkage to enable interprocedural optimizations.
|
||||||
realMain.SetLinkage(llvm.InternalLinkage)
|
realMain.SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -237,9 +173,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
if !sleep.IsNil() {
|
if !sleep.IsNil() {
|
||||||
worklist = append(worklist, sleep)
|
worklist = append(worklist, sleep)
|
||||||
}
|
}
|
||||||
deadlock := c.mod.NamedFunction("runtime.deadlock")
|
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||||
if !deadlock.IsNil() {
|
if !deadlockStub.IsNil() {
|
||||||
worklist = append(worklist, deadlock)
|
worklist = append(worklist, deadlockStub)
|
||||||
}
|
}
|
||||||
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||||
if !chanSend.IsNil() {
|
if !chanSend.IsNil() {
|
||||||
@@ -275,25 +211,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
// Add all callees to the worklist.
|
// Add all callees to the worklist.
|
||||||
for _, use := range getUses(f) {
|
for _, use := range getUses(f) {
|
||||||
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||||
for _, call := range getUses(use) {
|
bitcastUses := getUses(use)
|
||||||
|
for _, call := range bitcastUses {
|
||||||
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
||||||
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine")
|
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// This is a go statement. Do not mark the parent as async, as
|
// This is a go statement. Do not mark the parent as async, as
|
||||||
// starting a goroutine is not a blocking operation.
|
// starting a goroutine is not a blocking operation.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
|
||||||
// Not sure why this const bitcast is here but as long as it
|
|
||||||
// has no uses it can be ignored, I guess?
|
|
||||||
// I think it was created for the runtime.isnil check but
|
|
||||||
// somehow wasn't removed when all these checks are removed.
|
|
||||||
if len(getUses(use)) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if use.IsACallInst().IsNil() {
|
if use.IsACallInst().IsNil() {
|
||||||
// Not a call instruction. Maybe a store to a global? In any
|
// Not a call instruction. Maybe a store to a global? In any
|
||||||
// case, this requires support for async calls across function
|
// case, this requires support for async calls across function
|
||||||
@@ -322,12 +250,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
// goroutine is not async (does not do any blocking operation), no
|
// goroutine is not async (does not do any blocking operation), no
|
||||||
// scheduler is necessary as it can be called directly.
|
// scheduler is necessary as it can be called directly.
|
||||||
for _, use := range getUses(makeGoroutine) {
|
for _, use := range getUses(makeGoroutine) {
|
||||||
// Input param must be const ptrtoint of function.
|
// Input param must be const bitcast of function.
|
||||||
ptrtoint := use.Operand(0)
|
bitcast := use.Operand(0)
|
||||||
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
|
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
||||||
panic("expected const ptrtoint operand of runtime.makeGoroutine")
|
panic("expected const bitcast operand of runtime.makeGoroutine")
|
||||||
}
|
}
|
||||||
goroutine := ptrtoint.Operand(0)
|
goroutine := bitcast.Operand(0)
|
||||||
if _, ok := asyncFuncs[goroutine]; ok {
|
if _, ok := asyncFuncs[goroutine]; ok {
|
||||||
needsScheduler = true
|
needsScheduler = true
|
||||||
break
|
break
|
||||||
@@ -364,7 +292,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
// Transform all async functions into coroutines.
|
// Transform all async functions into coroutines.
|
||||||
for _, f := range asyncList {
|
for _, f := range asyncList {
|
||||||
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
|
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,7 +309,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if !inst.IsACallInst().IsNil() {
|
if !inst.IsACallInst().IsNil() {
|
||||||
callee := inst.CalledValue()
|
callee := inst.CalledValue()
|
||||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
|
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
asyncCalls = append(asyncCalls, inst)
|
asyncCalls = append(asyncCalls, inst)
|
||||||
@@ -393,7 +321,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
// Coroutine setup.
|
// Coroutine setup.
|
||||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||||
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
|
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
||||||
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||||
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
@@ -408,9 +336,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||||
}
|
}
|
||||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||||
if c.needsStackObjects() {
|
|
||||||
c.trackPointer(data)
|
|
||||||
}
|
|
||||||
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||||
|
|
||||||
// Modify async calls so this function suspends right after the child
|
// Modify async calls so this function suspends right after the child
|
||||||
@@ -429,7 +354,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||||
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
|
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suspend.
|
// Suspend.
|
||||||
@@ -464,20 +389,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
|
|
||||||
c.builder.SetInsertPointBefore(inst)
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
|
||||||
var parentHandle llvm.Value
|
parentHandle := f.LastParam()
|
||||||
if f.Linkage() == llvm.ExternalLinkage {
|
|
||||||
// Exported function.
|
|
||||||
// Note that getTaskStatePtr will panic if it is called with
|
|
||||||
// a nil pointer, so blocking exported functions that try to
|
|
||||||
// return anything will not work.
|
|
||||||
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
|
|
||||||
} else {
|
|
||||||
parentHandle = f.LastParam()
|
|
||||||
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
|
|
||||||
// sanity check
|
|
||||||
panic("trying to make exported function async: " + f.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store return values.
|
// Store return values.
|
||||||
switch inst.OperandsCount() {
|
switch inst.OperandsCount() {
|
||||||
@@ -487,7 +399,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
// Return this value by writing to the pointer stored in the
|
// Return this value by writing to the pointer stored in the
|
||||||
// parent handle. The parent coroutine has made an alloca that
|
// parent handle. The parent coroutine has made an alloca that
|
||||||
// we can write to to store our return value.
|
// we can write to to store our return value.
|
||||||
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||||
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||||
c.builder.CreateStore(inst.Operand(0), alloca)
|
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||||
default:
|
default:
|
||||||
@@ -505,7 +417,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
// behavior somehow (with the unreachable instruction).
|
// behavior somehow (with the unreachable instruction).
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||||
}, "ret")
|
}, "ret")
|
||||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||||
@@ -566,9 +478,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
sleepCall.EraseFromParentAsInstruction()
|
sleepCall.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform calls to runtime.deadlock into coroutine suspends (without
|
// Transform calls to runtime.deadlockStub into coroutine suspends (without
|
||||||
// resume).
|
// resume).
|
||||||
for _, deadlockCall := range getUses(deadlock) {
|
for _, deadlockCall := range getUses(deadlockStub) {
|
||||||
// deadlockCall must be a call instruction.
|
// deadlockCall must be a call instruction.
|
||||||
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
||||||
|
|
||||||
@@ -576,7 +488,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
|||||||
c.builder.SetInsertPointBefore(deadlockCall)
|
c.builder.SetInsertPointBefore(deadlockCall)
|
||||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
llvm.ConstNull(c.ctx.TokenType()),
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
|
||||||
}, "")
|
}, "")
|
||||||
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
|
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
|
||||||
c.builder.SetInsertPointBefore(deadlockCall)
|
c.builder.SetInsertPointBefore(deadlockCall)
|
||||||
@@ -643,14 +555,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
|||||||
|
|
||||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||||
for _, goroutine := range getUses(makeGoroutine) {
|
for _, goroutine := range getUses(makeGoroutine) {
|
||||||
ptrtointIn := goroutine.Operand(0)
|
bitcastIn := goroutine.Operand(0)
|
||||||
origFunc := ptrtointIn.Operand(0)
|
origFunc := bitcastIn.Operand(0)
|
||||||
uses := getUses(goroutine)
|
uses := getUses(goroutine)
|
||||||
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
|
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
||||||
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
||||||
}
|
}
|
||||||
inttoptrOut := uses[0]
|
bitcastOut := uses[0]
|
||||||
uses = getUses(inttoptrOut)
|
uses = getUses(bitcastOut)
|
||||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||||
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||||
}
|
}
|
||||||
@@ -665,7 +577,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
|||||||
c.builder.SetInsertPointBefore(realCall)
|
c.builder.SetInsertPointBefore(realCall)
|
||||||
c.builder.CreateCall(origFunc, params, "")
|
c.builder.CreateCall(origFunc, params, "")
|
||||||
realCall.EraseFromParentAsInstruction()
|
realCall.EraseFromParentAsInstruction()
|
||||||
inttoptrOut.EraseFromParentAsInstruction()
|
bitcastOut.EraseFromParentAsInstruction()
|
||||||
goroutine.EraseFromParentAsInstruction()
|
goroutine.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -18,19 +18,10 @@ import (
|
|||||||
// func ReadRegister(name string) uintptr
|
// func ReadRegister(name string) uintptr
|
||||||
//
|
//
|
||||||
// The register name must be a constant, for example "sp".
|
// The register name must be a constant, for example "sp".
|
||||||
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
|
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
|
||||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||||
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||||
var asm string
|
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||||
switch name {
|
|
||||||
case "device/arm.ReadRegister":
|
|
||||||
asm = "mov $0, " + regname
|
|
||||||
case "device/riscv.ReadRegister":
|
|
||||||
asm = "mv $0, " + regname
|
|
||||||
default:
|
|
||||||
panic("unknown architecture")
|
|
||||||
}
|
|
||||||
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
|
|
||||||
return c.builder.CreateCall(target, nil, ""), nil
|
return c.builder.CreateCall(target, nil, ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -163,8 +163,8 @@ func (c *Compiler) LowerInterfaces() {
|
|||||||
// run runs the pass itself.
|
// run runs the pass itself.
|
||||||
func (p *lowerInterfacesPass) run() {
|
func (p *lowerInterfacesPass) run() {
|
||||||
// Collect all type codes.
|
// Collect all type codes.
|
||||||
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
|
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
|
||||||
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
|
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
|
||||||
var typesInInterfaces []llvm.Value
|
var typesInInterfaces []llvm.Value
|
||||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
switch global.Type() {
|
switch global.Type() {
|
||||||
@@ -603,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
|
|||||||
// TODO: debug info
|
// TODO: debug info
|
||||||
|
|
||||||
// Create all used basic blocks.
|
// Create all used basic blocks.
|
||||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
thenBlock := llvm.AddBasicBlock(fn, "then")
|
||||||
elseBlock := p.ctx.AddBasicBlock(fn, "else")
|
elseBlock := llvm.AddBasicBlock(fn, "else")
|
||||||
|
|
||||||
// Add all possible types as cases.
|
// Add all possible types as cases.
|
||||||
p.builder.SetInsertPointAtEnd(entry)
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
@@ -661,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
|||||||
// TODO: debug info
|
// TODO: debug info
|
||||||
|
|
||||||
// Create entry block.
|
// Create entry block.
|
||||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
|
|
||||||
// Create default block and make it unreachable (which it is, because all
|
// Create default block and make it unreachable (which it is, because all
|
||||||
// possible types are checked).
|
// possible types are checked).
|
||||||
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
|
defaultBlock := llvm.AddBasicBlock(fn, "default")
|
||||||
p.builder.SetInsertPointAtEnd(defaultBlock)
|
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
p.builder.CreateUnreachable()
|
p.builder.CreateUnreachable()
|
||||||
|
|
||||||
@@ -684,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
|||||||
|
|
||||||
// Define all possible functions that can be called.
|
// Define all possible functions that can be called.
|
||||||
for _, typ := range itf.types {
|
for _, typ := range itf.types {
|
||||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||||
|
|
||||||
// The function we will redirect to when the interface has this type.
|
// The function we will redirect to when the interface has this type.
|
||||||
|
|||||||
+22
-102
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
|||||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||||
if itfConcreteTypeGlobal.IsNil() {
|
if itfConcreteTypeGlobal.IsNil() {
|
||||||
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
|
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||||
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||||
}
|
}
|
||||||
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||||
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
|
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf
|
return itf
|
||||||
@@ -45,100 +45,27 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
|||||||
// It returns a pointer to an external global which should be replaced with the
|
// It returns a pointer to an external global which should be replaced with the
|
||||||
// real type in the interface lowering pass.
|
// real type in the interface lowering pass.
|
||||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
globalName := "type:" + getTypeCodeName(typ)
|
||||||
global := c.mod.NamedGlobal(globalName)
|
global := c.mod.NamedGlobal(globalName)
|
||||||
if global.IsNil() {
|
if global.IsNil() {
|
||||||
// Create a new typecode global.
|
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
|
||||||
// Some type classes contain more information for underlying types or
|
|
||||||
// element types. Store it directly in the typecode global to make
|
|
||||||
// reflect lowering simpler.
|
|
||||||
var references llvm.Value
|
|
||||||
var length int64
|
|
||||||
switch typ := typ.(type) {
|
|
||||||
case *types.Named:
|
|
||||||
references = c.getTypeCode(typ.Underlying())
|
|
||||||
case *types.Chan:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Pointer:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Slice:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Array:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
length = typ.Len()
|
|
||||||
case *types.Struct:
|
|
||||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
|
||||||
structGlobal := c.makeStructTypeFields(typ)
|
|
||||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
|
||||||
}
|
|
||||||
if !references.IsNil() {
|
|
||||||
// Set the 'references' field of the runtime.typecodeID struct.
|
|
||||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
|
||||||
if length != 0 {
|
|
||||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
|
||||||
}
|
|
||||||
global.SetInitializer(globalValue)
|
|
||||||
global.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
}
|
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
}
|
}
|
||||||
return global
|
return global
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeStructTypeFields creates a new global that stores all type information
|
|
||||||
// related to this struct type, and returns the resulting global. This global is
|
|
||||||
// actually an array of all the fields in the structs.
|
|
||||||
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
|
||||||
// The global is an array of runtime.structField structs.
|
|
||||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
|
||||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
|
||||||
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
|
|
||||||
structGlobalValue := llvm.ConstNull(structGlobalType)
|
|
||||||
for i := 0; i < typ.NumFields(); i++ {
|
|
||||||
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
|
|
||||||
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
|
|
||||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
fieldName.SetUnnamedAddr(true)
|
|
||||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
|
||||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
|
||||||
})
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
|
||||||
if typ.Tag(i) != "" {
|
|
||||||
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
|
|
||||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
fieldTag.SetUnnamedAddr(true)
|
|
||||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
|
||||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
|
||||||
})
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
|
||||||
}
|
|
||||||
if typ.Field(i).Embedded() {
|
|
||||||
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
|
|
||||||
}
|
|
||||||
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
|
|
||||||
}
|
|
||||||
structGlobal.SetInitializer(structGlobalValue)
|
|
||||||
structGlobal.SetUnnamedAddr(true)
|
|
||||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
return structGlobal
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTypeCodeName returns a name for this type that can be used in the
|
// getTypeCodeName returns a name for this type that can be used in the
|
||||||
// interface lowering pass to assign type codes as expected by the reflect
|
// interface lowering pass to assign type codes as expected by the reflect
|
||||||
// package. See getTypeCodeNum.
|
// package. See getTypeCodeNum.
|
||||||
func getTypeCodeName(t types.Type) string {
|
func getTypeCodeName(t types.Type) string {
|
||||||
|
name := ""
|
||||||
|
if named, ok := t.(*types.Named); ok {
|
||||||
|
name = "~" + named.String() + ":"
|
||||||
|
t = t.Underlying()
|
||||||
|
}
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
case *types.Named:
|
|
||||||
return "named:" + t.String()
|
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
var kind string
|
var kind string
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
@@ -181,21 +108,21 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
default:
|
default:
|
||||||
panic("unknown basic type: " + t.Name())
|
panic("unknown basic type: " + t.Name())
|
||||||
}
|
}
|
||||||
return "basic:" + kind
|
return "basic:" + name + kind
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
return "chan:" + getTypeCodeName(t.Elem())
|
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
methods := make([]string, t.NumMethods())
|
methods := make([]string, t.NumMethods())
|
||||||
for i := 0; i < t.NumMethods(); i++ {
|
for i := 0; i < t.NumMethods(); i++ {
|
||||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||||
}
|
}
|
||||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
keyType := getTypeCodeName(t.Key())
|
keyType := getTypeCodeName(t.Key())
|
||||||
elemType := getTypeCodeName(t.Elem())
|
elemType := getTypeCodeName(t.Elem())
|
||||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
return "pointer:" + getTypeCodeName(t.Elem())
|
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
params := make([]string, t.Params().Len())
|
params := make([]string, t.Params().Len())
|
||||||
for i := 0; i < t.Params().Len(); i++ {
|
for i := 0; i < t.Params().Len(); i++ {
|
||||||
@@ -205,9 +132,9 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
for i := 0; i < t.Results().Len(); i++ {
|
for i := 0; i < t.Results().Len(); i++ {
|
||||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||||
}
|
}
|
||||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
return "slice:" + getTypeCodeName(t.Elem())
|
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
elems := make([]string, t.NumFields())
|
elems := make([]string, t.NumFields())
|
||||||
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
||||||
@@ -215,16 +142,9 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
panic("cgo unions are not allowed in interfaces")
|
panic("cgo unions are not allowed in interfaces")
|
||||||
}
|
}
|
||||||
for i := 0; i < t.NumFields(); i++ {
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
embedded := ""
|
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||||
if t.Field(i).Embedded() {
|
|
||||||
embedded = "#"
|
|
||||||
}
|
|
||||||
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
|
|
||||||
if t.Tag(i) != "" {
|
|
||||||
elems[i] += "`" + t.Tag(i) + "`"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return "struct:" + "{" + strings.Join(elems, ",") + "}"
|
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||||
default:
|
default:
|
||||||
panic("unknown type: " + t.String())
|
panic("unknown type: " + t.String())
|
||||||
}
|
}
|
||||||
@@ -243,11 +163,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
|||||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||||
if ms.Len() == 0 {
|
if ms.Len() == 0 {
|
||||||
// no methods, so can leave that one out
|
// no methods, so can leave that one out
|
||||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
methods := make([]llvm.Value, ms.Len())
|
methods := make([]llvm.Value, ms.Len())
|
||||||
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
|
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
|
||||||
for i := 0; i < ms.Len(); i++ {
|
for i := 0; i < ms.Len(); i++ {
|
||||||
method := ms.At(i)
|
method := ms.At(i)
|
||||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||||
@@ -380,7 +300,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
|||||||
// Continue after the if statement.
|
// Continue after the if statement.
|
||||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||||
|
|
||||||
if expr.CommaOk {
|
if expr.CommaOk {
|
||||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||||
|
|||||||
+9
-74
@@ -1,8 +1,6 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,41 +24,23 @@ func getUses(value llvm.Value) []llvm.Value {
|
|||||||
|
|
||||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||||
// at the end of the current block.
|
// after the last instruction in the current block. Also, it adds lifetime
|
||||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
// information to the IR signalling that the alloca won't be used before this
|
||||||
currentBlock := c.builder.GetInsertBlock()
|
// point.
|
||||||
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
|
||||||
if entryBlock.FirstInstruction().IsNil() {
|
|
||||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
|
||||||
} else {
|
|
||||||
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
|
||||||
}
|
|
||||||
alloca := c.builder.CreateAlloca(t, name)
|
|
||||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
|
||||||
return alloca
|
|
||||||
}
|
|
||||||
|
|
||||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
|
||||||
// lifetime start infromation in the IR signalling that the alloca won't be used
|
|
||||||
// before this point.
|
|
||||||
//
|
//
|
||||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
// end the lifetime after you're done with it.
|
||||||
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||||
alloca = c.createEntryBlockAlloca(t, name)
|
currentBlock := c.builder.GetInsertBlock()
|
||||||
|
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
alloca = c.builder.CreateAlloca(t, name)
|
||||||
|
c.builder.SetInsertPointAtEnd(currentBlock)
|
||||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
|
||||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
|
||||||
// createTemporaryAlloca.
|
|
||||||
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
|
||||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||||
// first if it doesn't exist yet.
|
// first if it doesn't exist yet.
|
||||||
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
||||||
@@ -154,48 +134,3 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
|
|||||||
|
|
||||||
return newBlock
|
return newBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
|
||||||
// contents, and returns the global.
|
|
||||||
// Note that it is left with the default linkage etc., you should set
|
|
||||||
// linkage/constant/etc properties yourself.
|
|
||||||
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
|
|
||||||
buf := reflect.ValueOf(bufItf)
|
|
||||||
globalType := llvm.ArrayType(elementType, buf.Len())
|
|
||||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
|
||||||
value := llvm.Undef(globalType)
|
|
||||||
for i := 0; i < buf.Len(); i++ {
|
|
||||||
ch := buf.Index(i).Uint()
|
|
||||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
|
||||||
}
|
|
||||||
global.SetInitializer(value)
|
|
||||||
return global
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGlobalBytes returns the slice contained in the array of the provided
|
|
||||||
// global. It can recover the bytes originally created using makeGlobalArray, if
|
|
||||||
// makeGlobalArray was given a byte slice.
|
|
||||||
func getGlobalBytes(global llvm.Value) []byte {
|
|
||||||
value := global.Initializer()
|
|
||||||
buf := make([]byte, value.Type().ArrayLength())
|
|
||||||
for i := range buf {
|
|
||||||
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaceGlobalByteWithArray replaces a global integer type in the module with
|
|
||||||
// an integer array, using a GEP to make the types match. It is a convenience
|
|
||||||
// function used for creating reflection sidetables, for example.
|
|
||||||
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
|
|
||||||
oldGlobal := c.mod.NamedGlobal(name)
|
|
||||||
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
|
|
||||||
gep := llvm.ConstGEP(global, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
})
|
|
||||||
oldGlobal.ReplaceAllUsesWith(gep)
|
|
||||||
oldGlobal.EraseFromParentAsGlobal()
|
|
||||||
global.SetName(name)
|
|
||||||
return global
|
|
||||||
}
|
|
||||||
|
|||||||
+13
-15
@@ -15,7 +15,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
|||||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||||
// will be zeroed by the hashmap get implementation if the key is not
|
// will be zeroed by the hashmap get implementation if the key is not
|
||||||
// present in the map.
|
// present in the map.
|
||||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
|
||||||
|
|
||||||
// Do the lookup. How it is done depends on the key type.
|
// Do the lookup. How it is done depends on the key type.
|
||||||
var commaOkValue llvm.Value
|
var commaOkValue llvm.Value
|
||||||
@@ -27,21 +27,21 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
|||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||||
// growth.
|
// growth.
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, mapKeyAlloca)
|
c.builder.CreateStore(key, mapKeyAlloca)
|
||||||
// Fetch the value from the hashmap.
|
// Fetch the value from the hashmap.
|
||||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||||
} else {
|
} else {
|
||||||
// Not trivially comparable using memcmp.
|
// Not trivially comparable using memcmp.
|
||||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the resulting value from the hashmap. The value is set to the zero
|
// Load the resulting value from the hashmap. The value is set to the zero
|
||||||
// value if the key doesn't exist in the hashmap.
|
// value if the key doesn't exist in the hashmap.
|
||||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||||
@@ -54,7 +54,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||||
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
|
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||||
c.builder.CreateStore(value, valueAlloca)
|
c.builder.CreateStore(value, valueAlloca)
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
@@ -63,15 +63,15 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
|
|||||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
c.emitLifetimeEnd(keyPtr, keySize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
} else {
|
} else {
|
||||||
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
}
|
}
|
||||||
c.emitLifetimeEnd(valuePtr, valueSize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||||
@@ -82,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
|||||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||||
return nil
|
return nil
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||||
c.builder.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
c.emitLifetimeEnd(keyPtr, keySize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,8 +121,6 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
|||||||
switch keyType := keyType.(type) {
|
switch keyType := keyType.(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
||||||
case *types.Pointer:
|
|
||||||
return true
|
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
for i := 0; i < keyType.NumFields(); i++ {
|
for i := 0; i < keyType.NumFields(); i++ {
|
||||||
fieldType := keyType.Field(i).Type().Underlying()
|
fieldType := keyType.Field(i).Type().Underlying()
|
||||||
|
|||||||
+143
-21
@@ -3,7 +3,6 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/transform"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,11 +22,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
c.replacePanicsWithTrap() // -panic=trap
|
c.replacePanicsWithTrap() // -panic=trap
|
||||||
}
|
}
|
||||||
|
|
||||||
// run a check of all of our code
|
|
||||||
if c.VerifyIR {
|
|
||||||
c.checkModule()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run function passes for each function.
|
// Run function passes for each function.
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||||
defer funcPasses.Dispose()
|
defer funcPasses.Dispose()
|
||||||
@@ -43,15 +37,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
goPasses := llvm.NewPassManager()
|
goPasses := llvm.NewPassManager()
|
||||||
defer goPasses.Dispose()
|
defer goPasses.Dispose()
|
||||||
goPasses.AddGlobalOptimizerPass()
|
goPasses.AddGlobalOptimizerPass()
|
||||||
|
goPasses.AddGlobalDCEPass()
|
||||||
goPasses.AddConstantPropagationPass()
|
goPasses.AddConstantPropagationPass()
|
||||||
goPasses.AddAggressiveDCEPass()
|
goPasses.AddAggressiveDCEPass()
|
||||||
goPasses.AddFunctionAttrsPass()
|
goPasses.AddFunctionAttrsPass()
|
||||||
goPasses.Run(c.mod)
|
goPasses.Run(c.mod)
|
||||||
|
|
||||||
// Run Go-specific optimization passes.
|
// Run Go-specific optimization passes.
|
||||||
transform.OptimizeMaps(c.mod)
|
c.OptimizeMaps()
|
||||||
c.OptimizeStringToBytes()
|
c.OptimizeStringToBytes()
|
||||||
transform.OptimizeAllocs(c.mod)
|
c.OptimizeAllocs()
|
||||||
c.LowerInterfaces()
|
c.LowerInterfaces()
|
||||||
c.LowerFuncValues()
|
c.LowerFuncValues()
|
||||||
|
|
||||||
@@ -61,7 +56,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
goPasses.Run(c.mod)
|
goPasses.Run(c.mod)
|
||||||
|
|
||||||
// Run TinyGo-specific interprocedural optimizations.
|
// Run TinyGo-specific interprocedural optimizations.
|
||||||
transform.OptimizeAllocs(c.mod)
|
c.OptimizeAllocs()
|
||||||
c.OptimizeStringToBytes()
|
c.OptimizeStringToBytes()
|
||||||
|
|
||||||
// Lower runtime.isnil calls to regular nil comparisons.
|
// Lower runtime.isnil calls to regular nil comparisons.
|
||||||
@@ -107,15 +102,6 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
|
||||||
for _, name := range functionsUsedInTransforms {
|
|
||||||
fn := c.mod.NamedFunction(name)
|
|
||||||
if fn.IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fn.SetLinkage(llvm.InternalLinkage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||||
// doesn't get lowered.
|
// doesn't get lowered.
|
||||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
@@ -129,9 +115,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
|||||||
builder.Populate(modPasses)
|
builder.Populate(modPasses)
|
||||||
modPasses.Run(c.mod)
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
hasGCPass := c.addGlobalsBitmap()
|
if c.gcIsPrecise() {
|
||||||
hasGCPass = c.makeGCStackSlots() || hasGCPass
|
c.addGlobalsBitmap()
|
||||||
if hasGCPass {
|
|
||||||
if err := c.Verify(); err != nil {
|
if err := c.Verify(); err != nil {
|
||||||
return errors.New("GC pass caused a verification failure")
|
return errors.New("GC pass caused a verification failure")
|
||||||
}
|
}
|
||||||
@@ -159,6 +144,48 @@ func (c *Compiler) replacePanicsWithTrap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eliminate created but not used maps.
|
||||||
|
//
|
||||||
|
// In the future, this should statically allocate created but never modified
|
||||||
|
// maps. This has not yet been implemented, however.
|
||||||
|
func (c *Compiler) OptimizeMaps() {
|
||||||
|
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
|
||||||
|
if hashmapMake.IsNil() {
|
||||||
|
// nothing to optimize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
|
||||||
|
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
|
||||||
|
|
||||||
|
for _, makeInst := range getUses(hashmapMake) {
|
||||||
|
updateInsts := []llvm.Value{}
|
||||||
|
unknownUses := false // are there any uses other than setting a value?
|
||||||
|
|
||||||
|
for _, use := range getUses(makeInst) {
|
||||||
|
if use := use.IsACallInst(); !use.IsNil() {
|
||||||
|
switch use.CalledValue() {
|
||||||
|
case hashmapBinarySet, hashmapStringSet:
|
||||||
|
updateInsts = append(updateInsts, use)
|
||||||
|
default:
|
||||||
|
unknownUses = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unknownUses = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unknownUses {
|
||||||
|
// This map can be entirely removed, as it is only created but never
|
||||||
|
// used.
|
||||||
|
for _, inst := range updateInsts {
|
||||||
|
inst.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
makeInst.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
||||||
// possible. This optimizes the following pattern:
|
// possible. This optimizes the following pattern:
|
||||||
// w.Write([]byte("foo"))
|
// w.Write([]byte("foo"))
|
||||||
@@ -209,6 +236,101 @@ func (c *Compiler) OptimizeStringToBytes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Basic escape analysis: translate runtime.alloc calls into alloca
|
||||||
|
// instructions.
|
||||||
|
func (c *Compiler) OptimizeAllocs() {
|
||||||
|
allocator := c.mod.NamedFunction("runtime.alloc")
|
||||||
|
if allocator.IsNil() {
|
||||||
|
// nothing to optimize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
heapallocs := getUses(allocator)
|
||||||
|
for _, heapalloc := range heapallocs {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if heapalloc.Operand(0).IsAConstant() == nilValue {
|
||||||
|
// Do not allocate variable length arrays on the stack.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size := heapalloc.Operand(0).ZExtValue()
|
||||||
|
if size > 256 {
|
||||||
|
// The maximum value for a stack allocation.
|
||||||
|
// TODO: tune this, this is just a random value.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// In general the pattern is:
|
||||||
|
// %0 = call i8* @runtime.alloc(i32 %size)
|
||||||
|
// %1 = bitcast i8* %0 to type*
|
||||||
|
// (use %1 only)
|
||||||
|
// But the bitcast might sometimes be dropped when allocating an *i8.
|
||||||
|
// The 'bitcast' variable below is thus usually a bitcast of the
|
||||||
|
// heapalloc but not always.
|
||||||
|
bitcast := heapalloc // instruction that creates the value
|
||||||
|
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
|
||||||
|
// getting only bitcast use
|
||||||
|
bitcast = uses[0]
|
||||||
|
}
|
||||||
|
if !c.doesEscape(bitcast) {
|
||||||
|
// Insert alloca in the entry block. Do it here so that mem2reg can
|
||||||
|
// promote it to a SSA value.
|
||||||
|
fn := bitcast.InstructionParent().Parent()
|
||||||
|
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||||
|
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
|
||||||
|
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||||
|
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||||
|
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||||
|
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||||
|
c.builder.CreateStore(zero, alloca)
|
||||||
|
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||||
|
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||||
|
if heapalloc != bitcast {
|
||||||
|
bitcast.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
heapalloc.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very basic escape analysis.
|
||||||
|
func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||||
|
uses := getUses(value)
|
||||||
|
for _, use := range uses {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if use.IsAGetElementPtrInst() != nilValue {
|
||||||
|
if c.doesEscape(use) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsABitCastInst() != nilValue {
|
||||||
|
// A bitcast escapes if the casted-to value escapes.
|
||||||
|
if c.doesEscape(use) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsALoadInst() != nilValue {
|
||||||
|
// Load does not escape.
|
||||||
|
} else if use.IsAStoreInst() != nilValue {
|
||||||
|
// Store only escapes when the value is stored to, not when the
|
||||||
|
// value is stored into another value.
|
||||||
|
if use.Operand(0) == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsACallInst() != nilValue {
|
||||||
|
if !c.hasFlag(use, value, "nocapture") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsAICmpInst() != nilValue {
|
||||||
|
// Comparing pointers don't let the pointer escape.
|
||||||
|
// This is often a compiler-inserted nil check.
|
||||||
|
} else {
|
||||||
|
// Unknown instruction, might escape.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not escape
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether the given value (which is of pointer type) is never stored to.
|
// Check whether the given value (which is of pointer type) is never stored to.
|
||||||
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||||
uses := getUses(value)
|
uses := getUses(value)
|
||||||
|
|||||||
+73
-406
@@ -1,43 +1,10 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
// This file has some compiler support for run-time reflection using the reflect
|
|
||||||
// package. In particular, it encodes type information in type codes in such a
|
|
||||||
// way that the reflect package can decode the type from this information.
|
|
||||||
// Where needed, it also adds some side tables for looking up more information
|
|
||||||
// about a type, when that information cannot be stored directly in the type
|
|
||||||
// code.
|
|
||||||
//
|
|
||||||
// Go has 26 different type kinds.
|
|
||||||
//
|
|
||||||
// Type kinds are subdivided in basic types (see the list of basicTypes below)
|
|
||||||
// that are mostly numeric literals and non-basic (or "complex") types that are
|
|
||||||
// more difficult to encode. These non-basic types come in two forms:
|
|
||||||
// * Prefix types (pointer, slice, interface, channel): these just add
|
|
||||||
// something to an existing type. For example, a pointer like *int just adds
|
|
||||||
// the fact that it's a pointer to an existing type (int).
|
|
||||||
// These are encoded efficiently by adding a prefix to a type code.
|
|
||||||
// * Types with multiple fields (struct, array, func, map). All of these have
|
|
||||||
// multiple fields contained within. Most obviously structs can contain many
|
|
||||||
// types as fields. Also arrays contain not just the element type but also
|
|
||||||
// the length parameter which can be any arbitrary number and thus may not
|
|
||||||
// fit in a type code.
|
|
||||||
// These types are encoded using side tables.
|
|
||||||
//
|
|
||||||
// This distinction is also important for how named types are encoded. At the
|
|
||||||
// moment, named basic type just get a unique number assigned while named
|
|
||||||
// non-basic types have their underlying type stored in a sidetable.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"go/ast"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A list of basic types and their numbers. This list should be kept in sync
|
|
||||||
// with the list of Kind constants of type.go in the reflect package.
|
|
||||||
var basicTypes = map[string]int64{
|
var basicTypes = map[string]int64{
|
||||||
"bool": 1,
|
"bool": 1,
|
||||||
"int": 2,
|
"int": 2,
|
||||||
@@ -59,72 +26,6 @@ var basicTypes = map[string]int64{
|
|||||||
"unsafeptr": 18,
|
"unsafeptr": 18,
|
||||||
}
|
}
|
||||||
|
|
||||||
// A list of non-basic types. Adding 19 to this number will give the Kind as
|
|
||||||
// used in src/reflect/types.go, and it must be kept in sync with that list.
|
|
||||||
var nonBasicTypes = map[string]int64{
|
|
||||||
"chan": 0,
|
|
||||||
"interface": 1,
|
|
||||||
"pointer": 2,
|
|
||||||
"slice": 3,
|
|
||||||
"array": 4,
|
|
||||||
"func": 5,
|
|
||||||
"map": 6,
|
|
||||||
"struct": 7,
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeCodeAssignmentState keeps some global state around for type code
|
|
||||||
// assignments, used to assign one unique type code to each Go type.
|
|
||||||
type typeCodeAssignmentState struct {
|
|
||||||
// An integer that's incremented each time it's used to give unique IDs to
|
|
||||||
// type codes that are not yet fully supported otherwise by the reflect
|
|
||||||
// package (or are simply unused in the compiled program).
|
|
||||||
fallbackIndex int
|
|
||||||
|
|
||||||
// This is the length of an uintptr. Only used occasionally to know whether
|
|
||||||
// a given number can be encoded as a varint.
|
|
||||||
uintptrLen int
|
|
||||||
|
|
||||||
// Map of named types to their type code. It is important that named types
|
|
||||||
// get unique IDs for each type.
|
|
||||||
namedBasicTypes map[string]int
|
|
||||||
namedNonBasicTypes map[string]int
|
|
||||||
|
|
||||||
// Map of array types to their type code.
|
|
||||||
arrayTypes map[string]int
|
|
||||||
arrayTypesSidetable []byte
|
|
||||||
needsArrayTypesSidetable bool
|
|
||||||
|
|
||||||
// Map of struct types to their type code.
|
|
||||||
structTypes map[string]int
|
|
||||||
structTypesSidetable []byte
|
|
||||||
needsStructNamesSidetable bool
|
|
||||||
|
|
||||||
// Map of struct names and tags to their name string.
|
|
||||||
structNames map[string]int
|
|
||||||
structNamesSidetable []byte
|
|
||||||
needsStructTypesSidetable bool
|
|
||||||
|
|
||||||
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
|
|
||||||
// used at runtime to get details about a named non-basic type.
|
|
||||||
// Entries are varints (see makeVarint below and readVarint in
|
|
||||||
// reflect/sidetables.go for the encoding): one varint per entry. The
|
|
||||||
// integers in namedNonBasicTypes are indices into this array. Because these
|
|
||||||
// are varints, most type codes are really small (just one byte).
|
|
||||||
//
|
|
||||||
// Note that this byte buffer is not created when it is not needed
|
|
||||||
// (reflect.namedNonBasicTypesSidetable has no uses), see
|
|
||||||
// needsNamedTypesSidetable.
|
|
||||||
namedNonBasicTypesSidetable []uint64
|
|
||||||
|
|
||||||
// This indicates whether namedNonBasicTypesSidetable needs to be created at
|
|
||||||
// all. If it is false, namedNonBasicTypesSidetable will contain simple
|
|
||||||
// monotonically increasing numbers.
|
|
||||||
needsNamedNonBasicTypesSidetable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// assignTypeCodes is used to assign a type code to each type in the program
|
|
||||||
// that is ever stored in an interface. It tries to use the smallest possible
|
|
||||||
// numbers to make the code that works with interfaces as small as possible.
|
|
||||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||||
if fn.IsNil() {
|
if fn.IsNil() {
|
||||||
@@ -137,21 +38,13 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assign typecodes the way the reflect package expects.
|
// Assign typecodes the way the reflect package expects.
|
||||||
state := typeCodeAssignmentState{
|
fallbackIndex := 1
|
||||||
fallbackIndex: 1,
|
namedTypes := make(map[string]int)
|
||||||
uintptrLen: c.uintptrType.IntTypeWidth(),
|
|
||||||
namedBasicTypes: make(map[string]int),
|
|
||||||
namedNonBasicTypes: make(map[string]int),
|
|
||||||
arrayTypes: make(map[string]int),
|
|
||||||
structTypes: make(map[string]int),
|
|
||||||
structNames: make(map[string]int),
|
|
||||||
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
|
||||||
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
|
||||||
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
|
||||||
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
|
||||||
}
|
|
||||||
for _, t := range typeSlice {
|
for _, t := range typeSlice {
|
||||||
num := state.getTypeCodeNum(t.typecode)
|
if t.name[:5] != "type:" {
|
||||||
|
panic("expected type name to start with 'type:'")
|
||||||
|
}
|
||||||
|
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||||
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||||
// TODO: support this in some way, using a side table for example.
|
// TODO: support this in some way, using a side table for example.
|
||||||
// That's less efficient but better than not working at all.
|
// That's less efficient but better than not working at all.
|
||||||
@@ -161,41 +54,25 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
|||||||
}
|
}
|
||||||
t.num = num.Uint64()
|
t.num = num.Uint64()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only create this sidetable when it is necessary.
|
|
||||||
if state.needsNamedNonBasicTypesSidetable {
|
|
||||||
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
|
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
}
|
|
||||||
if state.needsArrayTypesSidetable {
|
|
||||||
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
}
|
|
||||||
if state.needsStructTypesSidetable {
|
|
||||||
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
}
|
|
||||||
if state.needsStructNamesSidetable {
|
|
||||||
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
|
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||||
// based on a types.Type value for this function.
|
// based on a types.Type value for this function.
|
||||||
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
|
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||||
// Note: see src/reflect/type.go for bit allocations.
|
// Note: see src/reflect/type.go for bit allocations.
|
||||||
class, value := getClassAndValueFromTypeCode(typecode)
|
// A type can be named or unnamed. Example of both:
|
||||||
|
// basic:~foo:uint64
|
||||||
|
// basic:uint64
|
||||||
|
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||||
|
// contents of this type ID string. Allocate bits based on that, as
|
||||||
|
// src/runtime/types.go expects.
|
||||||
|
class := id[:strings.IndexByte(id, ':')]
|
||||||
|
value := id[len(class)+1:]
|
||||||
name := ""
|
name := ""
|
||||||
if class == "named" {
|
if value[0] == '~' {
|
||||||
name = value
|
name = value[1:strings.IndexByte(value, ':')]
|
||||||
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
value = value[len(name)+2:]
|
||||||
class, value = getClassAndValueFromTypeCode(typecode)
|
|
||||||
}
|
}
|
||||||
if class == "basic" {
|
if class == "basic" {
|
||||||
// Basic types follow the following bit pattern:
|
// Basic types follow the following bit pattern:
|
||||||
@@ -204,286 +81,76 @@ func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.I
|
|||||||
// upper bits are used to indicate the named type.
|
// upper bits are used to indicate the named type.
|
||||||
num, ok := basicTypes[value]
|
num, ok := basicTypes[value]
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("invalid basic type: " + value)
|
panic("invalid basic type: " + id)
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
// This type is named, set the upper bits to the name ID.
|
// This type is named, set the upper bits to the name ID.
|
||||||
num |= int64(state.getBasicNamedTypeNum(name)) << 5
|
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||||
}
|
}
|
||||||
return big.NewInt(num << 1)
|
return big.NewInt(num << 1)
|
||||||
} else {
|
} else {
|
||||||
// Non-baisc types use the following bit pattern:
|
// Complex types use the following bit pattern:
|
||||||
// ...nxxx1
|
// ...nxxx1
|
||||||
// where xxx indicates the non-basic type. The upper bits contain
|
// where xxx indicates the complex type (any non-basic type). The upper
|
||||||
// whatever the type contains. Types that wrap a single other type
|
// bits contain whatever the type contains. Types that wrap a single
|
||||||
// (channel, interface, pointer, slice) just contain the bits of the
|
// other type (channel, interface, pointer, slice) just contain the bits
|
||||||
// wrapped type. Other types (like struct) need more fields and thus
|
// of the wrapped type. Other types (like struct) have a different
|
||||||
// cannot be encoded as a simple prefix.
|
// method of encoding the contents of the type.
|
||||||
var classNumber int64
|
|
||||||
if n, ok := nonBasicTypes[class]; ok {
|
|
||||||
classNumber = n
|
|
||||||
} else {
|
|
||||||
panic("unknown type kind: " + class)
|
|
||||||
}
|
|
||||||
var num *big.Int
|
var num *big.Int
|
||||||
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
|
var classNumber int64
|
||||||
|
switch class {
|
||||||
|
case "chan":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 0
|
||||||
|
case "interface":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 1
|
||||||
|
case "pointer":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 2
|
||||||
|
case "slice":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 3
|
||||||
|
case "array":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 4
|
||||||
|
case "func":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 5
|
||||||
|
case "map":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 6
|
||||||
|
case "struct":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 7
|
||||||
|
default:
|
||||||
|
panic("unknown type kind: " + id)
|
||||||
|
}
|
||||||
if name == "" {
|
if name == "" {
|
||||||
num = state.getNonBasicTypeCode(class, typecode)
|
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||||
} else {
|
} else {
|
||||||
// We must return a named type here. But first check whether it
|
// TODO: store num in a sidetable
|
||||||
// has already been defined.
|
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||||
if index, ok := state.namedNonBasicTypes[name]; ok {
|
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||||
num := big.NewInt(int64(index))
|
|
||||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
lowBits |= 1 << 4 // set the 'n' bit (see above)
|
|
||||||
if !state.needsNamedNonBasicTypesSidetable {
|
|
||||||
// Use simple small integers in this case, to make these numbers
|
|
||||||
// smaller.
|
|
||||||
index := len(state.namedNonBasicTypes) + 1
|
|
||||||
state.namedNonBasicTypes[name] = index
|
|
||||||
num = big.NewInt(int64(index))
|
|
||||||
} else {
|
|
||||||
// We need to store full type information.
|
|
||||||
// First allocate a number in the named non-basic type
|
|
||||||
// sidetable.
|
|
||||||
index := len(state.namedNonBasicTypesSidetable)
|
|
||||||
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
|
|
||||||
state.namedNonBasicTypes[name] = index
|
|
||||||
// Get the typecode of the underlying type (which could be the
|
|
||||||
// element type in the case of pointers, for example).
|
|
||||||
num = state.getNonBasicTypeCode(class, typecode)
|
|
||||||
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
|
||||||
panic("cannot store value in sidetable")
|
|
||||||
}
|
|
||||||
// Now update the side table with the number we just
|
|
||||||
// determined. We need this multi-step approach to avoid stack
|
|
||||||
// overflow due to adding types recursively in the case of
|
|
||||||
// linked lists (a pointer which points to a struct that
|
|
||||||
// contains that same pointer).
|
|
||||||
state.namedNonBasicTypesSidetable[index] = num.Uint64()
|
|
||||||
num = big.NewInt(int64(index))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Concatenate the 'num' and 'lowBits' bitstrings.
|
|
||||||
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
|
|
||||||
return num
|
return num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
|
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||||
// the type code used there in the type code.
|
// type. If the name already has a number that number is returned, else a new
|
||||||
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
|
// number is returned. The number is always non-zero.
|
||||||
switch class {
|
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||||
case "chan", "pointer", "slice":
|
if num, ok := namedTypes[name]; ok {
|
||||||
// Prefix-style type kinds. The upper bits contain the element type.
|
return num
|
||||||
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
} else {
|
||||||
return state.getTypeCodeNum(sub)
|
num = len(namedTypes) + 1
|
||||||
case "array":
|
namedTypes[name] = num
|
||||||
// An array is basically a pair of (typecode, length) stored in a
|
|
||||||
// sidetable.
|
|
||||||
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
|
|
||||||
case "struct":
|
|
||||||
// More complicated type kind. The upper bits contain the index to the
|
|
||||||
// struct type in the struct types sidetable.
|
|
||||||
return big.NewInt(int64(state.getStructTypeNum(typecode)))
|
|
||||||
default:
|
|
||||||
// Type has not yet been implemented, so fall back by using a unique
|
|
||||||
// number.
|
|
||||||
num := big.NewInt(int64(state.fallbackIndex))
|
|
||||||
state.fallbackIndex++
|
|
||||||
return num
|
return num
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type
|
|
||||||
// runtime.typecodeID), looks at the name, and extracts the typecode class and
|
|
||||||
// value from it. For example, for a typecode with the following name:
|
|
||||||
// reflect/types.type:pointer:named:reflect.ValueError
|
|
||||||
// It extracts:
|
|
||||||
// class = "pointer"
|
|
||||||
// value = "named:reflect.ValueError"
|
|
||||||
func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) {
|
|
||||||
typecodeName := typecode.Name()
|
|
||||||
const prefix = "reflect/types.type:"
|
|
||||||
if !strings.HasPrefix(typecodeName, prefix) {
|
|
||||||
panic("unexpected typecode name: " + typecodeName)
|
|
||||||
}
|
|
||||||
id := typecodeName[len(prefix):]
|
|
||||||
class = id[:strings.IndexByte(id, ':')]
|
|
||||||
value = id[len(class)+1:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getBasicNamedTypeNum returns an appropriate (unique) number for the given
|
|
||||||
// named type. If the name already has a number that number is returned, else a
|
|
||||||
// new number is returned. The number is always non-zero.
|
|
||||||
func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
|
|
||||||
if num, ok := state.namedBasicTypes[name]; ok {
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
num := len(state.namedBasicTypes) + 1
|
|
||||||
state.namedBasicTypes[name] = num
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
// getArrayTypeNum returns the array type number, which is an index into the
|
|
||||||
// reflect.arrayTypesSidetable or a unique number for this type if this table is
|
|
||||||
// not used.
|
|
||||||
func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int {
|
|
||||||
name := typecode.Name()
|
|
||||||
if num, ok := state.arrayTypes[name]; ok {
|
|
||||||
// This array type already has an entry in the sidetable. Don't store
|
|
||||||
// it twice.
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
if !state.needsArrayTypesSidetable {
|
|
||||||
// We don't need array sidetables, so we can just assign monotonically
|
|
||||||
// increasing numbers to each array type.
|
|
||||||
num := len(state.arrayTypes)
|
|
||||||
state.arrayTypes[name] = num
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
|
||||||
elemTypeNum := state.getTypeCodeNum(elemTypeCode)
|
|
||||||
if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() {
|
|
||||||
// TODO: make this a regular error
|
|
||||||
panic("array element type has a type code that is too big")
|
|
||||||
}
|
|
||||||
|
|
||||||
// The array side table is a sequence of {element type, array length}.
|
|
||||||
arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue()
|
|
||||||
buf := makeVarint(elemTypeNum.Uint64())
|
|
||||||
buf = append(buf, makeVarint(arrayLength)...)
|
|
||||||
|
|
||||||
index := len(state.arrayTypesSidetable)
|
|
||||||
state.arrayTypes[name] = index
|
|
||||||
state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...)
|
|
||||||
return index
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStructTypeNum returns the struct type number, which is an index into
|
|
||||||
// reflect.structTypesSidetable or an unique number for every struct if this
|
|
||||||
// sidetable is not needed in the to-be-compiled program.
|
|
||||||
func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int {
|
|
||||||
name := typecode.Name()
|
|
||||||
if num, ok := state.structTypes[name]; ok {
|
|
||||||
// This struct already has an assigned type code.
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
if !state.needsStructTypesSidetable {
|
|
||||||
// We don't need struct sidetables, so we can just assign monotonically
|
|
||||||
// increasing numbers to each struct type.
|
|
||||||
num := len(state.structTypes)
|
|
||||||
state.structTypes[name] = num
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the fields this struct type contains.
|
|
||||||
// The struct number will be the start index of
|
|
||||||
structTypeGlobal := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}).Operand(0).Initializer()
|
|
||||||
numFields := structTypeGlobal.Type().ArrayLength()
|
|
||||||
|
|
||||||
// The first data that is stored in the struct sidetable is the number of
|
|
||||||
// fields this struct contains. This is usually just a single byte because
|
|
||||||
// most structs don't contain that many fields, but make it a varint just
|
|
||||||
// to be sure.
|
|
||||||
buf := makeVarint(uint64(numFields))
|
|
||||||
|
|
||||||
// Iterate over every field in the struct.
|
|
||||||
// Every field is stored sequentially in the struct sidetable. Fields can
|
|
||||||
// be retrieved from this list of fields at runtime by iterating over all
|
|
||||||
// of them until the right field has been found.
|
|
||||||
// Perhaps adding some index would speed things up, but it would also make
|
|
||||||
// the sidetable bigger.
|
|
||||||
for i := 0; i < numFields; i++ {
|
|
||||||
// Collect some information about this field.
|
|
||||||
field := llvm.ConstExtractValue(structTypeGlobal, []uint32{uint32(i)})
|
|
||||||
|
|
||||||
nameGlobal := llvm.ConstExtractValue(field, []uint32{1})
|
|
||||||
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
|
|
||||||
panic("compiler: no name for this struct field")
|
|
||||||
}
|
|
||||||
fieldNameBytes := getGlobalBytes(nameGlobal.Operand(0))
|
|
||||||
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
|
|
||||||
|
|
||||||
// See whether this struct field has an associated tag, and if so,
|
|
||||||
// store that tag in the tags sidetable.
|
|
||||||
tagGlobal := llvm.ConstExtractValue(field, []uint32{2})
|
|
||||||
hasTag := false
|
|
||||||
tagNumber := 0
|
|
||||||
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
|
|
||||||
hasTag = true
|
|
||||||
tagBytes := getGlobalBytes(tagGlobal.Operand(0))
|
|
||||||
tagNumber = state.getStructNameNumber(tagBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The 'embedded' or 'anonymous' flag for this field.
|
|
||||||
embedded := llvm.ConstExtractValue(field, []uint32{3}).ZExtValue() != 0
|
|
||||||
|
|
||||||
// The first byte in the struct types sidetable is a flags byte with
|
|
||||||
// two bits in it.
|
|
||||||
flagsByte := byte(0)
|
|
||||||
if embedded {
|
|
||||||
flagsByte |= 1
|
|
||||||
}
|
|
||||||
if hasTag {
|
|
||||||
flagsByte |= 2
|
|
||||||
}
|
|
||||||
if ast.IsExported(string(fieldNameBytes)) {
|
|
||||||
flagsByte |= 4
|
|
||||||
}
|
|
||||||
buf = append(buf, flagsByte)
|
|
||||||
|
|
||||||
// Get the type number and add it to the buffer.
|
|
||||||
// All fields have a type, so include it directly here.
|
|
||||||
typeNum := state.getTypeCodeNum(llvm.ConstExtractValue(field, []uint32{0}))
|
|
||||||
if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() {
|
|
||||||
// TODO: make this a regular error
|
|
||||||
panic("struct field has a type code that is too big")
|
|
||||||
}
|
|
||||||
buf = append(buf, makeVarint(typeNum.Uint64())...)
|
|
||||||
|
|
||||||
// Add the name.
|
|
||||||
buf = append(buf, makeVarint(uint64(fieldNameNumber))...)
|
|
||||||
|
|
||||||
// Add the tag, if there is one.
|
|
||||||
if hasTag {
|
|
||||||
buf = append(buf, makeVarint(uint64(tagNumber))...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
num := len(state.structTypesSidetable)
|
|
||||||
state.structTypes[name] = num
|
|
||||||
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
|
|
||||||
return num
|
|
||||||
}
|
|
||||||
|
|
||||||
// getStructNameNumber stores this string (name or tag) onto the struct names
|
|
||||||
// sidetable. The format is a varint of the length of the struct, followed by
|
|
||||||
// the raw bytes of the name. Multiple identical strings are stored under the
|
|
||||||
// same name for space efficiency.
|
|
||||||
func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int {
|
|
||||||
name := string(nameBytes)
|
|
||||||
if n, ok := state.structNames[name]; ok {
|
|
||||||
// This name was used before, re-use it now (for space efficiency).
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
// This name is not yet in the names sidetable. Add it now.
|
|
||||||
n := len(state.structNamesSidetable)
|
|
||||||
state.structNames[name] = n
|
|
||||||
state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...)
|
|
||||||
state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeVarint is a small helper function that returns the bytes of the number in
|
|
||||||
// varint encoding.
|
|
||||||
func makeVarint(n uint64) []byte {
|
|
||||||
buf := make([]byte, binary.MaxVarintLen64)
|
|
||||||
return buf[:binary.PutUvarint(buf, n)]
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
@@ -4,6 +4,7 @@ package compiler
|
|||||||
// compiler builtins.
|
// compiler builtins.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go/constant"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
// emitSyscall emits an inline system call instruction, depending on the target
|
// emitSyscall emits an inline system call instruction, depending on the target
|
||||||
// OS/arch.
|
// OS/arch.
|
||||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||||
num := c.getValue(frame, call.Args[0])
|
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
||||||
var syscallResult llvm.Value
|
var syscallResult llvm.Value
|
||||||
switch {
|
switch {
|
||||||
case c.GOARCH == "amd64":
|
case c.GOARCH == "amd64":
|
||||||
@@ -28,12 +29,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
// > All system classes enter the kernel via the syscall instruction.
|
// > All system classes enter the kernel via the syscall instruction.
|
||||||
//
|
//
|
||||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
num += 0x2000000
|
||||||
}
|
}
|
||||||
// Sources:
|
// Sources:
|
||||||
// https://stackoverflow.com/a/2538212
|
// https://stackoverflow.com/a/2538212
|
||||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||||
args := []llvm.Value{num}
|
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
||||||
argTypes := []llvm.Type{c.uintptrType}
|
argTypes := []llvm.Type{c.uintptrType}
|
||||||
// Constraints will look something like:
|
// Constraints will look something like:
|
||||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||||
@@ -58,32 +59,6 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
syscallResult = c.builder.CreateCall(target, args, "")
|
||||||
case c.GOARCH == "386" && c.GOOS == "linux":
|
|
||||||
// Sources:
|
|
||||||
// syscall(2) man page
|
|
||||||
// https://stackoverflow.com/a/2538212
|
|
||||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
|
|
||||||
args := []llvm.Value{num}
|
|
||||||
argTypes := []llvm.Type{c.uintptrType}
|
|
||||||
// Constraints will look something like:
|
|
||||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
|
||||||
constraints := "={eax},0"
|
|
||||||
for i, arg := range call.Args[1:] {
|
|
||||||
constraints += "," + [...]string{
|
|
||||||
"{ebx}",
|
|
||||||
"{ecx}",
|
|
||||||
"{edx}",
|
|
||||||
"{esi}",
|
|
||||||
"{edi}",
|
|
||||||
"{ebp}",
|
|
||||||
}[i]
|
|
||||||
llvmValue := c.getValue(frame, arg)
|
|
||||||
args = append(args, llvmValue)
|
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
|
||||||
}
|
|
||||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
|
||||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
|
||||||
syscallResult = c.builder.CreateCall(target, args, "")
|
|
||||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||||
// Implement the EABI system call convention for Linux.
|
// Implement the EABI system call convention for Linux.
|
||||||
// Source: syscall(2) man page.
|
// Source: syscall(2) man page.
|
||||||
@@ -106,7 +81,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
args = append(args, num)
|
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||||
argTypes = append(argTypes, c.uintptrType)
|
argTypes = append(argTypes, c.uintptrType)
|
||||||
constraints += ",{r7}" // syscall number
|
constraints += ",{r7}" // syscall number
|
||||||
for i := len(call.Args) - 1; i < 4; i++ {
|
for i := len(call.Args) - 1; i < 4; i++ {
|
||||||
@@ -136,7 +111,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
args = append(args, llvmValue)
|
args = append(args, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
args = append(args, num)
|
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||||
argTypes = append(argTypes, c.uintptrType)
|
argTypes = append(argTypes, c.uintptrType)
|
||||||
constraints += ",{x8}" // syscall number
|
constraints += ",{x8}" // syscall number
|
||||||
for i := len(call.Args) - 1; i < 8; i++ {
|
for i := len(call.Args) - 1; i < 8; i++ {
|
||||||
@@ -165,7 +140,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||||
@@ -181,7 +156,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
|||||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||||
|
|||||||
+8
-20
@@ -25,7 +25,7 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
|||||||
return llvm.ConstPointerNull(c.i8ptrType)
|
return llvm.ConstPointerNull(c.i8ptrType)
|
||||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||||
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
||||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||||
// Packed data fits in a pointer, so store it directly inside the
|
// Packed data fits in a pointer, so store it directly inside the
|
||||||
// pointer.
|
// pointer.
|
||||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||||
@@ -34,14 +34,11 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
|||||||
}
|
}
|
||||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||||
// it in an alloca first for bitcasting (store+bitcast+load).
|
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||||
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
|
packedAlloc = c.builder.CreateAlloca(packedType, "")
|
||||||
} else {
|
} else {
|
||||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||||
if c.needsStackObjects() {
|
|
||||||
c.trackPointer(packedHeapAlloc)
|
|
||||||
}
|
|
||||||
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||||
}
|
}
|
||||||
// Store all values in the alloca or heap pointer.
|
// Store all values in the alloca or heap pointer.
|
||||||
@@ -57,14 +54,10 @@ func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
|||||||
if packedHeapAlloc.IsNil() {
|
if packedHeapAlloc.IsNil() {
|
||||||
// Load value (as *i8) from the alloca.
|
// Load value (as *i8) from the alloca.
|
||||||
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||||
result := c.builder.CreateLoad(packedAlloc, "")
|
return c.builder.CreateLoad(packedAlloc, "")
|
||||||
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
|
||||||
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
|
|
||||||
c.emitLifetimeEnd(packedPtr, packedSize)
|
|
||||||
return result
|
|
||||||
} else {
|
} else {
|
||||||
// Get the original heap allocation pointer, which already is an *i8.
|
// Get the original heap allocation pointer, which already is an *i8.
|
||||||
return packedHeapAlloc
|
return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,21 +66,21 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
|||||||
packedType := c.ctx.StructType(valueTypes, false)
|
packedType := c.ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
// Get a correctly-typed pointer to the packed data.
|
// Get a correctly-typed pointer to the packed data.
|
||||||
var packedAlloc, packedRawAlloc llvm.Value
|
var packedAlloc llvm.Value
|
||||||
size := c.targetData.TypeAllocSize(packedType)
|
size := c.targetData.TypeAllocSize(packedType)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
// No data to unpack.
|
// No data to unpack.
|
||||||
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
||||||
// A single pointer is always stored directly.
|
// A single pointer is always stored directly.
|
||||||
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) && !c.gcIsPrecise() {
|
||||||
// Packed data stored directly in pointer.
|
// Packed data stored directly in pointer.
|
||||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||||
// Keep this cast in SSA form.
|
// Keep this cast in SSA form.
|
||||||
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||||
}
|
}
|
||||||
// Fallback: load it using an alloca.
|
// Fallback: load it using an alloca.
|
||||||
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
||||||
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||||
@@ -101,7 +94,7 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
|||||||
for i, valueType := range valueTypes {
|
for i, valueType := range valueTypes {
|
||||||
if c.targetData.TypeAllocSize(valueType) == 0 {
|
if c.targetData.TypeAllocSize(valueType) == 0 {
|
||||||
// This value has length zero, so there's nothing to load.
|
// This value has length zero, so there's nothing to load.
|
||||||
values[i] = llvm.ConstNull(valueType)
|
values[i] = c.getZeroValue(valueType)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
indices := []llvm.Value{
|
indices := []llvm.Value{
|
||||||
@@ -111,10 +104,5 @@ func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []l
|
|||||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
values[i] = c.builder.CreateLoad(gep, "")
|
values[i] = c.builder.CreateLoad(gep, "")
|
||||||
}
|
}
|
||||||
if !packedRawAlloc.IsNil() {
|
|
||||||
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
|
|
||||||
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
|
|
||||||
c.emitLifetimeEnd(allocPtr, allocSize)
|
|
||||||
}
|
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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
@@ -86,7 +86,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
case !inst.IsAAllocaInst().IsNil():
|
case !inst.IsAAllocaInst().IsNil():
|
||||||
allocType := inst.Type().ElementType()
|
allocType := inst.Type().ElementType()
|
||||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
alloca.SetInitializer(getZeroValue(allocType))
|
||||||
alloca.SetLinkage(llvm.InternalLinkage)
|
alloca.SetLinkage(llvm.InternalLinkage)
|
||||||
fr.locals[inst] = &LocalValue{
|
fr.locals[inst] = &LocalValue{
|
||||||
Underlying: alloca,
|
Underlying: alloca,
|
||||||
@@ -176,20 +176,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
continue // special case: bitcast of alloc
|
continue // special case: bitcast of alloc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
|
||||||
// Special case for runtime.trackPointer calls.
|
|
||||||
// Note: this might not be entirely sound in some rare cases
|
|
||||||
// where the map is stored in a dirty global.
|
|
||||||
uses := getUses(inst)
|
|
||||||
if len(uses) == 1 {
|
|
||||||
use := uses[0]
|
|
||||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// It is not possible in Go to bitcast a map value to a pointer.
|
|
||||||
panic("unimplemented: bitcast of map")
|
|
||||||
}
|
|
||||||
value := fr.getLocal(operand).(*LocalValue)
|
value := fr.getLocal(operand).(*LocalValue)
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||||
|
|
||||||
@@ -198,25 +184,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||||
predicate := inst.IntPredicate()
|
predicate := inst.IntPredicate()
|
||||||
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
|
|
||||||
// Unfortunately, the const propagation in the IR builder
|
|
||||||
// doesn't handle pointer compares of inttoptr values. So we
|
|
||||||
// implement it manually here.
|
|
||||||
lhsNil, ok1 := isPointerNil(lhs)
|
|
||||||
rhsNil, ok2 := isPointerNil(rhs)
|
|
||||||
if ok1 && ok2 {
|
|
||||||
if lhsNil && rhsNil {
|
|
||||||
// Both are nil, so this icmp is always evaluated to true.
|
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if lhsNil != rhsNil {
|
|
||||||
// Only one of them is nil, so this comparison must return false.
|
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
|
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
|
||||||
case !inst.IsAFCmpInst().IsNil():
|
case !inst.IsAFCmpInst().IsNil():
|
||||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||||
@@ -253,7 +220,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
allocType = llvm.ArrayType(allocType, elementCount)
|
allocType = llvm.ArrayType(allocType, elementCount)
|
||||||
}
|
}
|
||||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
||||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
alloc.SetInitializer(getZeroValue(allocType))
|
||||||
alloc.SetLinkage(llvm.InternalLinkage)
|
alloc.SetLinkage(llvm.InternalLinkage)
|
||||||
result := &LocalValue{
|
result := &LocalValue{
|
||||||
Underlying: alloc,
|
Underlying: alloc,
|
||||||
@@ -312,7 +279,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
stringType := fr.Mod.GetTypeByName("runtime._string")
|
stringType := fr.Mod.GetTypeByName("runtime._string")
|
||||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||||
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
||||||
ret := llvm.ConstNull(stringType)
|
ret := getZeroValue(stringType)
|
||||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
||||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||||
@@ -335,7 +302,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
sliceType := inst.Type()
|
sliceType := inst.Type()
|
||||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||||
ret := llvm.ConstNull(sliceType)
|
ret := getZeroValue(sliceType)
|
||||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||||
@@ -382,8 +349,6 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||||
case callee.Name() == "llvm.dbg.value":
|
case callee.Name() == "llvm.dbg.value":
|
||||||
// do nothing
|
// do nothing
|
||||||
case callee.Name() == "runtime.trackPointer":
|
|
||||||
// do nothing
|
|
||||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||||
// This are all print instructions, which necessarily have side
|
// This are all print instructions, which necessarily have side
|
||||||
// effects but no results.
|
// effects but no results.
|
||||||
@@ -496,21 +461,17 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
|||||||
}
|
}
|
||||||
thenBB := inst.Operand(1)
|
thenBB := inst.Operand(1)
|
||||||
elseBB := inst.Operand(2)
|
elseBB := inst.Operand(2)
|
||||||
if !cond.IsAInstruction().IsNil() {
|
if !cond.IsConstant() {
|
||||||
return nil, nil, errors.New("interp: branch on a non-constant")
|
return nil, nil, errors.New("interp: branch on a non-constant")
|
||||||
}
|
} else {
|
||||||
if !cond.IsAConstantExpr().IsNil() {
|
switch cond.ZExtValue() {
|
||||||
// This may happen when the instruction builder could not
|
case 0: // false
|
||||||
// const-fold some instructions.
|
return nil, []llvm.Value{thenBB}, nil // then
|
||||||
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
|
case 1: // true
|
||||||
}
|
return nil, []llvm.Value{elseBB}, nil // else
|
||||||
switch cond {
|
default:
|
||||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
panic("branch was not true or false")
|
||||||
return nil, []llvm.Value{thenBB}, nil // then
|
}
|
||||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
|
||||||
return nil, []llvm.Value{elseBB}, nil // else
|
|
||||||
default:
|
|
||||||
panic("branch was not true or false")
|
|
||||||
}
|
}
|
||||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||||
// unconditional branch (goto)
|
// unconditional branch (goto)
|
||||||
|
|||||||
+1
-12
@@ -35,8 +35,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
return &sideEffectResult{severity: sideEffectLimited}
|
return &sideEffectResult{severity: sideEffectLimited}
|
||||||
case "runtime.interfaceImplements":
|
case "runtime.interfaceImplements":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
case "runtime.trackPointer":
|
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
|
||||||
case "llvm.dbg.value":
|
case "llvm.dbg.value":
|
||||||
return &sideEffectResult{severity: sideEffectNone}
|
return &sideEffectResult{severity: sideEffectNone}
|
||||||
}
|
}
|
||||||
@@ -111,16 +109,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
|||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
case llvm.Load:
|
case llvm.Load, llvm.Store:
|
||||||
if inst.IsVolatile() {
|
|
||||||
result.updateSeverity(sideEffectLimited)
|
|
||||||
}
|
|
||||||
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
|
|
||||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
|
||||||
result.updateSeverity(sideEffectLimited)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case llvm.Store:
|
|
||||||
if inst.IsVolatile() {
|
if inst.IsVolatile() {
|
||||||
result.updateSeverity(sideEffectLimited)
|
result.updateSeverity(sideEffectLimited)
|
||||||
}
|
}
|
||||||
|
|||||||
+44
-26
@@ -16,6 +16,50 @@ func getUses(value llvm.Value) []llvm.Value {
|
|||||||
return uses
|
return uses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return a zero LLVM value for any LLVM type. Setting this value as an
|
||||||
|
// initializer has the same effect as setting 'zeroinitializer' on a value.
|
||||||
|
// Sadly, I haven't found a way to do it directly with the Go API but this works
|
||||||
|
// just fine.
|
||||||
|
func getZeroValue(typ llvm.Type) llvm.Value {
|
||||||
|
switch typ.TypeKind() {
|
||||||
|
case llvm.ArrayTypeKind:
|
||||||
|
subTyp := typ.ElementType()
|
||||||
|
subVal := getZeroValue(subTyp)
|
||||||
|
vals := make([]llvm.Value, typ.ArrayLength())
|
||||||
|
for i := range vals {
|
||||||
|
vals[i] = subVal
|
||||||
|
}
|
||||||
|
return llvm.ConstArray(subTyp, vals)
|
||||||
|
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||||
|
return llvm.ConstFloat(typ, 0.0)
|
||||||
|
case llvm.IntegerTypeKind:
|
||||||
|
return llvm.ConstInt(typ, 0, false)
|
||||||
|
case llvm.PointerTypeKind:
|
||||||
|
return llvm.ConstPointerNull(typ)
|
||||||
|
case llvm.StructTypeKind:
|
||||||
|
types := typ.StructElementTypes()
|
||||||
|
vals := make([]llvm.Value, len(types))
|
||||||
|
for i, subTyp := range types {
|
||||||
|
val := getZeroValue(subTyp)
|
||||||
|
vals[i] = val
|
||||||
|
}
|
||||||
|
if typ.StructName() != "" {
|
||||||
|
return llvm.ConstNamedStruct(typ, vals)
|
||||||
|
} else {
|
||||||
|
return typ.Context().ConstStruct(vals, false)
|
||||||
|
}
|
||||||
|
case llvm.VectorTypeKind:
|
||||||
|
zero := getZeroValue(typ.ElementType())
|
||||||
|
vals := make([]llvm.Value, typ.VectorSize())
|
||||||
|
for i := range vals {
|
||||||
|
vals[i] = zero
|
||||||
|
}
|
||||||
|
return llvm.ConstVector(vals, false)
|
||||||
|
default:
|
||||||
|
panic("interp: unknown LLVM type: " + typ.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// getStringBytes loads the byte slice of a Go string represented as a
|
// getStringBytes loads the byte slice of a Go string represented as a
|
||||||
// {ptr, len} pair.
|
// {ptr, len} pair.
|
||||||
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
||||||
@@ -50,29 +94,3 @@ func isScalar(t llvm.Type) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPointerNil returns whether this is a nil pointer or not. The ok value
|
|
||||||
// indicates whether the result is certain: if it is false the result boolean is
|
|
||||||
// not valid.
|
|
||||||
func isPointerNil(v llvm.Value) (result bool, ok bool) {
|
|
||||||
if !v.IsAConstantExpr().IsNil() {
|
|
||||||
switch v.Opcode() {
|
|
||||||
case llvm.IntToPtr:
|
|
||||||
// Whether a constant inttoptr is nil is easy to
|
|
||||||
// determine.
|
|
||||||
operand := v.Operand(0)
|
|
||||||
if operand.IsConstant() {
|
|
||||||
return operand.ZExtValue() == 0, true
|
|
||||||
}
|
|
||||||
case llvm.BitCast, llvm.GetElementPtr:
|
|
||||||
// These const instructions are just a kind of wrappers for the
|
|
||||||
// underlying pointer.
|
|
||||||
return isPointerNil(v.Operand(0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !v.IsAConstantPointerNull().IsNil() {
|
|
||||||
// A constant pointer null is always null, of course.
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
return false, false // not valid
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-2
@@ -162,7 +162,7 @@ func (v *MapValue) newBucket() llvm.Value {
|
|||||||
llvm.ArrayType(v.KeyType, 8), // key type
|
llvm.ArrayType(v.KeyType, 8), // key type
|
||||||
llvm.ArrayType(v.ValueType, 8), // value type
|
llvm.ArrayType(v.ValueType, 8), // value type
|
||||||
}, false)
|
}, false)
|
||||||
bucketValue := llvm.ConstNull(bucketType)
|
bucketValue := getZeroValue(bucketType)
|
||||||
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
|
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
|
||||||
bucket.SetInitializer(bucketValue)
|
bucket.SetInitializer(bucketValue)
|
||||||
bucket.SetLinkage(llvm.InternalLinkage)
|
bucket.SetLinkage(llvm.InternalLinkage)
|
||||||
@@ -311,7 +311,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
|||||||
|
|
||||||
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
||||||
v.KeyType = keyType
|
v.KeyType = keyType
|
||||||
key := llvm.ConstNull(keyType)
|
key := getZeroValue(keyType)
|
||||||
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
|
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
|
||||||
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
|
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package ir
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -22,13 +23,16 @@ type Program struct {
|
|||||||
mainPkg *ssa.Package
|
mainPkg *ssa.Package
|
||||||
Functions []*Function
|
Functions []*Function
|
||||||
functionMap map[*ssa.Function]*Function
|
functionMap map[*ssa.Function]*Function
|
||||||
|
Globals []*Global
|
||||||
|
globalMap map[*ssa.Global]*Global
|
||||||
|
comments map[string]*ast.CommentGroup
|
||||||
|
NamedTypes []*NamedType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function or method.
|
// Function or method.
|
||||||
type Function struct {
|
type Function struct {
|
||||||
*ssa.Function
|
*ssa.Function
|
||||||
LLVMFn llvm.Value
|
LLVMFn llvm.Value
|
||||||
module string // go:wasm-module
|
|
||||||
linkName string // go:linkname, go:export, go:interrupt
|
linkName string // go:linkname, go:export, go:interrupt
|
||||||
exported bool // go:export
|
exported bool // go:export
|
||||||
nobounds bool // go:nobounds
|
nobounds bool // go:nobounds
|
||||||
@@ -37,6 +41,28 @@ type Function struct {
|
|||||||
inline InlineType // go:inline
|
inline InlineType // go:inline
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Global variable, possibly constant.
|
||||||
|
type Global struct {
|
||||||
|
*ssa.Global
|
||||||
|
program *Program
|
||||||
|
LLVMGlobal llvm.Value
|
||||||
|
linkName string // go:extern
|
||||||
|
extern bool // go:extern
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type with a name and possibly methods.
|
||||||
|
type NamedType struct {
|
||||||
|
*ssa.Type
|
||||||
|
LLVMType llvm.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type that is at some point put in an interface.
|
||||||
|
type TypeWithMethods struct {
|
||||||
|
t types.Type
|
||||||
|
Num int
|
||||||
|
Methods map[string]*types.Selection
|
||||||
|
}
|
||||||
|
|
||||||
// Interface type that is at some point used in a type assert (to check whether
|
// Interface type that is at some point used in a type assert (to check whether
|
||||||
// it implements another interface).
|
// it implements another interface).
|
||||||
type Interface struct {
|
type Interface struct {
|
||||||
@@ -57,14 +83,39 @@ const (
|
|||||||
// //go:inline). The compiler will be more likely to inline this function,
|
// //go:inline). The compiler will be more likely to inline this function,
|
||||||
// but it is not a guarantee.
|
// but it is not a guarantee.
|
||||||
InlineHint
|
InlineHint
|
||||||
|
|
||||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
|
||||||
// //go:noinline.
|
|
||||||
InlineNone
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create and initialize a new *Program from a *ssa.Program.
|
// Create and initialize a new *Program from a *ssa.Program.
|
||||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||||
|
comments := map[string]*ast.CommentGroup{}
|
||||||
|
for _, pkgInfo := range lprogram.Sorted() {
|
||||||
|
for _, file := range pkgInfo.Files {
|
||||||
|
for _, decl := range file.Decls {
|
||||||
|
switch decl := decl.(type) {
|
||||||
|
case *ast.GenDecl:
|
||||||
|
switch decl.Tok {
|
||||||
|
case token.TYPE, token.VAR:
|
||||||
|
if len(decl.Specs) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, spec := range decl.Specs {
|
||||||
|
switch spec := spec.(type) {
|
||||||
|
case *ast.TypeSpec: // decl.Tok == token.TYPE
|
||||||
|
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
|
||||||
|
comments[id] = decl.Doc
|
||||||
|
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||||
|
for _, name := range spec.Names {
|
||||||
|
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||||
|
comments[id] = decl.Doc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
program := lprogram.LoadSSA()
|
program := lprogram.LoadSSA()
|
||||||
program.Build()
|
program.Build()
|
||||||
|
|
||||||
@@ -136,6 +187,8 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
|||||||
LoaderProgram: lprogram,
|
LoaderProgram: lprogram,
|
||||||
mainPkg: mainPkg,
|
mainPkg: mainPkg,
|
||||||
functionMap: make(map[*ssa.Function]*Function),
|
functionMap: make(map[*ssa.Function]*Function),
|
||||||
|
globalMap: make(map[*ssa.Global]*Global),
|
||||||
|
comments: comments,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pkg := range packageList {
|
for _, pkg := range packageList {
|
||||||
@@ -160,6 +213,8 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
|||||||
case *ssa.Function:
|
case *ssa.Function:
|
||||||
p.addFunction(member)
|
p.addFunction(member)
|
||||||
case *ssa.Type:
|
case *ssa.Type:
|
||||||
|
t := &NamedType{Type: member}
|
||||||
|
p.NamedTypes = append(p.NamedTypes, t)
|
||||||
methods := getAllMethods(pkg.Prog, member.Type())
|
methods := getAllMethods(pkg.Prog, member.Type())
|
||||||
if !types.IsInterface(member.Type()) {
|
if !types.IsInterface(member.Type()) {
|
||||||
// named type
|
// named type
|
||||||
@@ -168,7 +223,13 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *ssa.Global:
|
case *ssa.Global:
|
||||||
// Ignore. Globals are not handled here.
|
g := &Global{program: p, Global: member}
|
||||||
|
doc := p.comments[g.RelString(nil)]
|
||||||
|
if doc != nil {
|
||||||
|
g.parsePragmas(doc)
|
||||||
|
}
|
||||||
|
p.Globals = append(p.Globals, g)
|
||||||
|
p.globalMap[member] = g
|
||||||
case *ssa.NamedConst:
|
case *ssa.NamedConst:
|
||||||
// Ignore: these are already resolved.
|
// Ignore: these are already resolved.
|
||||||
default:
|
default:
|
||||||
@@ -202,6 +263,10 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
|
|||||||
return p.functionMap[ssaFn]
|
return p.functionMap[ssaFn]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
|
||||||
|
return p.globalMap[ssaGlobal]
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Program) MainPkg() *ssa.Package {
|
func (p *Program) MainPkg() *ssa.Package {
|
||||||
return p.mainPkg
|
return p.mainPkg
|
||||||
}
|
}
|
||||||
@@ -230,16 +295,8 @@ func (f *Function) parsePragmas() {
|
|||||||
}
|
}
|
||||||
f.linkName = parts[1]
|
f.linkName = parts[1]
|
||||||
f.exported = true
|
f.exported = true
|
||||||
case "//go:wasm-module":
|
|
||||||
// Alternative comment for setting the import module.
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f.module = parts[1]
|
|
||||||
case "//go:inline":
|
case "//go:inline":
|
||||||
f.inline = InlineHint
|
f.inline = InlineHint
|
||||||
case "//go:noinline":
|
|
||||||
f.inline = InlineNone
|
|
||||||
case "//go:interrupt":
|
case "//go:interrupt":
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 {
|
||||||
continue
|
continue
|
||||||
@@ -298,11 +355,6 @@ func (f *Function) Inline() InlineType {
|
|||||||
return f.inline
|
return f.inline
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the module name if not the default.
|
|
||||||
func (f *Function) Module() string {
|
|
||||||
return f.module
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the link name for this function.
|
// Return the link name for this function.
|
||||||
func (f *Function) LinkName() string {
|
func (f *Function) LinkName() string {
|
||||||
if f.linkName != "" {
|
if f.linkName != "" {
|
||||||
@@ -337,6 +389,72 @@ func (f *Function) CName() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse //go: pragma comments from the source.
|
||||||
|
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
|
||||||
|
for _, comment := range doc.List {
|
||||||
|
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.Fields(comment.Text)
|
||||||
|
switch parts[0] {
|
||||||
|
case "//go:extern":
|
||||||
|
g.extern = true
|
||||||
|
if len(parts) == 2 {
|
||||||
|
g.linkName = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the link name for this global.
|
||||||
|
func (g *Global) LinkName() string {
|
||||||
|
if g.linkName != "" {
|
||||||
|
return g.linkName
|
||||||
|
}
|
||||||
|
if name := g.CName(); name != "" {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return g.RelString(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Global) IsExtern() bool {
|
||||||
|
return g.extern || g.CName() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
|
||||||
|
// zero-length string.
|
||||||
|
func (g *Global) CName() string {
|
||||||
|
name := g.Name()
|
||||||
|
if strings.HasPrefix(name, "C.") {
|
||||||
|
// created by ../loader/cgo.go
|
||||||
|
return name[2:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if this named type is annotated with the //go:volatile pragma,
|
||||||
|
// for volatile loads and stores.
|
||||||
|
func (p *Program) IsVolatile(t types.Type) bool {
|
||||||
|
if t, ok := t.(*types.Named); !ok {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
if t.Obj().Pkg() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
|
||||||
|
doc := p.comments[id]
|
||||||
|
if doc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, line := range doc.List {
|
||||||
|
if strings.TrimSpace(line.Text) == "//go:volatile" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get all methods of a type.
|
// Get all methods of a type.
|
||||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||||
ms := prog.MethodSets.MethodSet(typ)
|
ms := prog.MethodSets.MethodSet(typ)
|
||||||
|
|||||||
+1
-1
Submodule lib/cmsis-svd updated: 5910f3d115...b6f0a65ac3
@@ -63,7 +63,6 @@ func Link(linker string, flags ...string) error {
|
|||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Dir = sourceDir()
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,5 @@ func Link(linker string, flags ...string) error {
|
|||||||
cmd := exec.Command(linker, flags...)
|
cmd := exec.Command(linker, flags...)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
cmd.Dir = sourceDir()
|
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-144
@@ -1,7 +1,6 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
@@ -11,26 +10,22 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/cgo"
|
"github.com/tinygo-org/tinygo/cgo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program holds all packages and some metadata about the program as a whole.
|
// Program holds all packages and some metadata about the program as a whole.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
mainPkg string
|
Build *build.Context
|
||||||
Build *build.Context
|
OverlayBuild *build.Context
|
||||||
OverlayBuild *build.Context
|
ShouldOverlay func(path string) bool
|
||||||
OverlayPath func(path string) string
|
Packages map[string]*Package
|
||||||
Packages map[string]*Package
|
sorted []*Package
|
||||||
sorted []*Package
|
fset *token.FileSet
|
||||||
fset *token.FileSet
|
TypeChecker types.Config
|
||||||
TypeChecker types.Config
|
Dir string // current working directory (for error reporting)
|
||||||
Dir string // current working directory (for error reporting)
|
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||||
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
CFlags []string
|
||||||
CFlags []string
|
|
||||||
ClangHeaders string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package holds a loaded package, its imports, and its parsed files.
|
// Package holds a loaded package, its imports, and its parsed files.
|
||||||
@@ -53,9 +48,8 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
|||||||
|
|
||||||
// Load this package.
|
// Load this package.
|
||||||
ctx := p.Build
|
ctx := p.Build
|
||||||
if newPath := p.OverlayPath(path); newPath != "" {
|
if p.ShouldOverlay(path) {
|
||||||
ctx = p.OverlayBuild
|
ctx = p.OverlayBuild
|
||||||
path = newPath
|
|
||||||
}
|
}
|
||||||
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -68,11 +62,6 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
|||||||
p.sorted = nil // invalidate the sorted order of packages
|
p.sorted = nil // invalidate the sorted order of packages
|
||||||
pkg := p.newPackage(buildPkg)
|
pkg := p.newPackage(buildPkg)
|
||||||
p.Packages[buildPkg.ImportPath] = pkg
|
p.Packages[buildPkg.ImportPath] = pkg
|
||||||
|
|
||||||
if p.mainPkg == "" {
|
|
||||||
p.mainPkg = buildPkg.ImportPath
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg, nil
|
return pkg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,11 +91,6 @@ func (p *Program) ImportFile(path string) (*Package, error) {
|
|||||||
p.sorted = nil // invalidate the sorted order of packages
|
p.sorted = nil // invalidate the sorted order of packages
|
||||||
pkg := p.newPackage(buildPkg)
|
pkg := p.newPackage(buildPkg)
|
||||||
p.Packages[buildPkg.ImportPath] = pkg
|
p.Packages[buildPkg.ImportPath] = pkg
|
||||||
|
|
||||||
if p.mainPkg == "" {
|
|
||||||
p.mainPkg = buildPkg.ImportPath
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkg, nil
|
return pkg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,12 +169,10 @@ func (p *Program) sort() {
|
|||||||
// The returned error may be an Errors error, which contains a list of errors.
|
// The returned error may be an Errors error, which contains a list of errors.
|
||||||
//
|
//
|
||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Program) Parse(compileTestBinary bool) error {
|
func (p *Program) Parse() error {
|
||||||
includeTests := compileTestBinary
|
|
||||||
|
|
||||||
// Load all imports
|
// Load all imports
|
||||||
for _, pkg := range p.Sorted() {
|
for _, pkg := range p.Sorted() {
|
||||||
err := pkg.importRecursively(includeTests)
|
err := pkg.importRecursively()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(*ImportCycleError); ok {
|
if err, ok := err.(*ImportCycleError); ok {
|
||||||
if pkg.ImportPath != err.Packages[0] {
|
if pkg.ImportPath != err.Packages[0] {
|
||||||
@@ -203,14 +185,7 @@ func (p *Program) Parse(compileTestBinary bool) error {
|
|||||||
|
|
||||||
// Parse all packages.
|
// Parse all packages.
|
||||||
for _, pkg := range p.Sorted() {
|
for _, pkg := range p.Sorted() {
|
||||||
err := pkg.Parse(includeTests)
|
err := pkg.Parse()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if compileTestBinary {
|
|
||||||
err := p.SwapTestMain()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -227,83 +202,6 @@ func (p *Program) Parse(compileTestBinary bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Program) SwapTestMain() error {
|
|
||||||
var tests []string
|
|
||||||
|
|
||||||
isTestFunc := func(f *ast.FuncDecl) bool {
|
|
||||||
// TODO: improve signature check
|
|
||||||
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
mainPkg := p.Packages[p.mainPkg]
|
|
||||||
for _, f := range mainPkg.Files {
|
|
||||||
for i, d := range f.Decls {
|
|
||||||
switch v := d.(type) {
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
if isTestFunc(v) {
|
|
||||||
tests = append(tests, v.Name.Name)
|
|
||||||
}
|
|
||||||
if v.Name.Name == "main" {
|
|
||||||
// Remove main
|
|
||||||
if len(f.Decls) == 1 {
|
|
||||||
f.Decls = make([]ast.Decl, 0)
|
|
||||||
} else {
|
|
||||||
f.Decls[i] = f.Decls[len(f.Decls)-1]
|
|
||||||
f.Decls = f.Decls[:len(f.Decls)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
|
|
||||||
const mainBody = `package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main () {
|
|
||||||
m := &testing.M{
|
|
||||||
Tests: []testing.TestToCall{
|
|
||||||
{{range .TestFunctions}}
|
|
||||||
{Name: "{{.}}", Func: {{.}}},
|
|
||||||
{{end}}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
testing.TestMain(m)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
tmpl := template.Must(template.New("testmain").Parse(mainBody))
|
|
||||||
b := bytes.Buffer{}
|
|
||||||
tmplData := struct {
|
|
||||||
TestFunctions []string
|
|
||||||
}{
|
|
||||||
TestFunctions: tests,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := tmpl.Execute(&b, tmplData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path := filepath.Join(p.mainPkg, "$testmain.go")
|
|
||||||
|
|
||||||
if p.fset == nil {
|
|
||||||
p.fset = token.NewFileSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
mainPkg.Files = append(mainPkg.Files, newMain)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseFile is a wrapper around parser.ParseFile.
|
// parseFile is a wrapper around parser.ParseFile.
|
||||||
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||||
if p.fset == nil {
|
if p.fset == nil {
|
||||||
@@ -328,7 +226,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
|||||||
// Parse parses and typechecks this package.
|
// Parse parses and typechecks this package.
|
||||||
//
|
//
|
||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Package) Parse(includeTests bool) error {
|
func (p *Package) Parse() error {
|
||||||
if len(p.Files) != 0 {
|
if len(p.Files) != 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -342,7 +240,7 @@ func (p *Package) Parse(includeTests bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := p.parseFiles(includeTests)
|
files, err := p.parseFiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -381,21 +279,11 @@ func (p *Package) Check() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// parseFiles parses the loaded list of files and returns this list.
|
// parseFiles parses the loaded list of files and returns this list.
|
||||||
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||||
// TODO: do this concurrently.
|
// TODO: do this concurrently.
|
||||||
var files []*ast.File
|
var files []*ast.File
|
||||||
var fileErrs []error
|
var fileErrs []error
|
||||||
|
for _, file := range p.GoFiles {
|
||||||
var gofiles []string
|
|
||||||
if includeTests {
|
|
||||||
gofiles = make([]string, 0, len(p.GoFiles)+len(p.TestGoFiles))
|
|
||||||
gofiles = append(gofiles, p.GoFiles...)
|
|
||||||
gofiles = append(gofiles, p.TestGoFiles...)
|
|
||||||
} else {
|
|
||||||
gofiles = p.GoFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, file := range gofiles {
|
|
||||||
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
f, err := p.parseFile(filepath.Join(p.Package.Dir, file), parser.ParseComments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
@@ -417,11 +305,15 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
|||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
if len(p.CgoFiles) != 0 {
|
if len(p.CgoFiles) != 0 {
|
||||||
cflags := append(p.CFlags, "-I"+p.Package.Dir)
|
clangIncludes := ""
|
||||||
if p.ClangHeaders != "" {
|
if _, err := os.Stat(filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")); !os.IsNotExist(err) {
|
||||||
cflags = append(cflags, "-I"+p.ClangHeaders)
|
// Running from the source directory.
|
||||||
|
clangIncludes = filepath.Join(p.TINYGOROOT, "llvm", "tools", "clang", "lib", "Headers")
|
||||||
|
} else {
|
||||||
|
// Running from the installation directory.
|
||||||
|
clangIncludes = filepath.Join(p.TINYGOROOT, "lib", "clang", "include")
|
||||||
}
|
}
|
||||||
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
|
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, append(p.CFlags, "-I"+p.Package.Dir, "-I"+clangIncludes))
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
fileErrs = append(fileErrs, errs...)
|
fileErrs = append(fileErrs, errs...)
|
||||||
}
|
}
|
||||||
@@ -430,7 +322,6 @@ func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
|||||||
if len(fileErrs) != 0 {
|
if len(fileErrs) != 0 {
|
||||||
return nil, Errors{p, fileErrs}
|
return nil, Errors{p, fileErrs}
|
||||||
}
|
}
|
||||||
|
|
||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,15 +342,9 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
|||||||
// importRecursively() on the imported packages as well.
|
// importRecursively() on the imported packages as well.
|
||||||
//
|
//
|
||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Package) importRecursively(includeTests bool) error {
|
func (p *Package) importRecursively() error {
|
||||||
p.Importing = true
|
p.Importing = true
|
||||||
|
for _, to := range p.Package.Imports {
|
||||||
imports := p.Package.Imports
|
|
||||||
if includeTests {
|
|
||||||
imports = append(imports, p.Package.TestImports...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, to := range imports {
|
|
||||||
if to == "C" {
|
if to == "C" {
|
||||||
// Do CGo processing in a later stage.
|
// Do CGo processing in a later stage.
|
||||||
continue
|
continue
|
||||||
@@ -477,7 +362,7 @@ func (p *Package) importRecursively(includeTests bool) error {
|
|||||||
if importedPkg.Importing {
|
if importedPkg.Importing {
|
||||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
||||||
}
|
}
|
||||||
err = importedPkg.importRecursively(false)
|
err = importedPkg.importRecursively()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(*ImportCycleError); ok {
|
if err, ok := err.(*ImportCycleError); ok {
|
||||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
||||||
|
|||||||
@@ -47,18 +47,13 @@ type BuildConfig struct {
|
|||||||
opt string
|
opt string
|
||||||
gc string
|
gc string
|
||||||
panicStrategy string
|
panicStrategy string
|
||||||
scheduler string
|
|
||||||
printIR bool
|
printIR bool
|
||||||
dumpSSA bool
|
dumpSSA bool
|
||||||
verifyIR bool
|
|
||||||
debug bool
|
debug bool
|
||||||
printSizes string
|
printSizes string
|
||||||
cFlags []string
|
cFlags []string
|
||||||
ldFlags []string
|
ldFlags []string
|
||||||
tags string
|
|
||||||
wasmAbi string
|
wasmAbi string
|
||||||
heapSize int64
|
|
||||||
testConfig compiler.TestConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function for Compiler object.
|
// Helper function for Compiler object.
|
||||||
@@ -85,44 +80,21 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
if goroot == "" {
|
if goroot == "" {
|
||||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||||
}
|
}
|
||||||
tags := spec.BuildTags
|
|
||||||
major, minor, err := getGorootVersion(goroot)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
|
||||||
}
|
|
||||||
if major != 1 || (minor != 11 && minor != 12) {
|
|
||||||
return fmt.Errorf("requires go version 1.11 or 1.12, got go%d.%d", major, minor)
|
|
||||||
}
|
|
||||||
for i := 1; i <= minor; i++ {
|
|
||||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
|
||||||
}
|
|
||||||
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
|
|
||||||
tags = append(tags, extraTags...)
|
|
||||||
}
|
|
||||||
scheduler := spec.Scheduler
|
|
||||||
if config.scheduler != "" {
|
|
||||||
scheduler = config.scheduler
|
|
||||||
}
|
|
||||||
compilerConfig := compiler.Config{
|
compilerConfig := compiler.Config{
|
||||||
Triple: spec.Triple,
|
Triple: spec.Triple,
|
||||||
CPU: spec.CPU,
|
CPU: spec.CPU,
|
||||||
Features: spec.Features,
|
|
||||||
GOOS: spec.GOOS,
|
GOOS: spec.GOOS,
|
||||||
GOARCH: spec.GOARCH,
|
GOARCH: spec.GOARCH,
|
||||||
GC: config.gc,
|
GC: config.gc,
|
||||||
PanicStrategy: config.panicStrategy,
|
PanicStrategy: config.panicStrategy,
|
||||||
Scheduler: scheduler,
|
|
||||||
CFlags: cflags,
|
CFlags: cflags,
|
||||||
LDFlags: ldflags,
|
LDFlags: ldflags,
|
||||||
ClangHeaders: getClangHeaderPath(root),
|
|
||||||
Debug: config.debug,
|
Debug: config.debug,
|
||||||
DumpSSA: config.dumpSSA,
|
DumpSSA: config.dumpSSA,
|
||||||
VerifyIR: config.verifyIR,
|
|
||||||
TINYGOROOT: root,
|
TINYGOROOT: root,
|
||||||
GOROOT: goroot,
|
GOROOT: goroot,
|
||||||
GOPATH: getGopath(),
|
GOPATH: getGopath(),
|
||||||
BuildTags: tags,
|
BuildTags: spec.BuildTags,
|
||||||
TestConfig: config.testConfig,
|
|
||||||
}
|
}
|
||||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -156,6 +128,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
if spec.GOOS != "darwin" {
|
if spec.GOOS != "darwin" {
|
||||||
c.ApplyFunctionSections() // -ffunction-sections
|
c.ApplyFunctionSections() // -ffunction-sections
|
||||||
}
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification error after applying function sections")
|
||||||
|
}
|
||||||
|
|
||||||
// Browsers cannot handle external functions that have type i64 because it
|
// Browsers cannot handle external functions that have type i64 because it
|
||||||
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
||||||
@@ -167,6 +142,9 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("verification error after running the wasm i64 hack")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization levels here are roughly the same as Clang, but probably not
|
// Optimization levels here are roughly the same as Clang, but probably not
|
||||||
@@ -243,16 +221,10 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
|||||||
// Prepare link command.
|
// Prepare link command.
|
||||||
executable := filepath.Join(dir, "main")
|
executable := filepath.Join(dir, "main")
|
||||||
tmppath := executable // final file
|
tmppath := executable // final file
|
||||||
ldflags = append(ldflags, "-o", executable, objfile, "-L", root)
|
ldflags := append(ldflags, "-o", executable, objfile, "-L", root)
|
||||||
if spec.RTLib == "compiler-rt" {
|
if spec.RTLib == "compiler-rt" {
|
||||||
ldflags = append(ldflags, librt)
|
ldflags = append(ldflags, librt)
|
||||||
}
|
}
|
||||||
if spec.GOARCH == "wasm" {
|
|
||||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
|
||||||
// size).
|
|
||||||
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
|
|
||||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile extra files.
|
// Compile extra files.
|
||||||
for i, path := range spec.ExtraFiles {
|
for i, path := range spec.ExtraFiles {
|
||||||
@@ -364,33 +336,6 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test(pkgName, target string, config *BuildConfig) error {
|
|
||||||
spec, err := LoadTarget(target)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
spec.BuildTags = append(spec.BuildTags, "test")
|
|
||||||
config.testConfig.CompileTestBinary = true
|
|
||||||
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
|
|
||||||
cmd := exec.Command(tmppath)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
// Propagate the exit code
|
|
||||||
if err, ok := err.(*exec.ExitError); ok {
|
|
||||||
if status, ok := err.Sys().(syscall.WaitStatus); ok {
|
|
||||||
os.Exit(status.ExitStatus())
|
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return &commandError{"failed to run compiled binary", tmppath, err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func Flash(pkgName, target, port string, config *BuildConfig) error {
|
func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||||
spec, err := LoadTarget(target)
|
spec, err := LoadTarget(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -549,30 +494,6 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
|
||||||
// number.
|
|
||||||
func parseSize(s string) (int64, error) {
|
|
||||||
s = strings.ToLower(strings.TrimSpace(s))
|
|
||||||
if len(s) == 0 {
|
|
||||||
return 0, errors.New("no size provided")
|
|
||||||
}
|
|
||||||
multiply := int64(1)
|
|
||||||
switch s[len(s)-1] {
|
|
||||||
case 'k':
|
|
||||||
multiply = 1 << 10
|
|
||||||
case 'm':
|
|
||||||
multiply = 1 << 20
|
|
||||||
case 'g':
|
|
||||||
multiply = 1 << 30
|
|
||||||
}
|
|
||||||
if multiply != 1 {
|
|
||||||
s = s[:len(s)-1]
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseInt(s, 0, 64)
|
|
||||||
n *= multiply
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||||
fmt.Fprintln(os.Stderr, "version:", version)
|
fmt.Fprintln(os.Stderr, "version:", version)
|
||||||
@@ -580,7 +501,6 @@ func usage() {
|
|||||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||||
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
|
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
|
||||||
fmt.Fprintln(os.Stderr, " test: test packages")
|
|
||||||
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
||||||
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
||||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
|
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
|
||||||
@@ -591,24 +511,23 @@ func usage() {
|
|||||||
|
|
||||||
func handleCompilerError(err error) {
|
func handleCompilerError(err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err := err.(type) {
|
if errUnsupported, ok := err.(*interp.Unsupported); ok {
|
||||||
case *interp.Unsupported:
|
|
||||||
// hit an unknown/unsupported instruction
|
// hit an unknown/unsupported instruction
|
||||||
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
|
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
|
||||||
err.Inst.Dump()
|
errUnsupported.Inst.Dump()
|
||||||
fmt.Fprintln(os.Stderr)
|
fmt.Fprintln(os.Stderr)
|
||||||
case types.Error:
|
} else if errCompiler, ok := err.(types.Error); ok {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, errCompiler)
|
||||||
case loader.Errors:
|
} else if errLoader, ok := err.(loader.Errors); ok {
|
||||||
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
|
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
|
||||||
for _, err := range err.Errs {
|
for _, err := range errLoader.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
case *multiError:
|
} else if errMulti, ok := err.(*multiError); ok {
|
||||||
for _, err := range err.Errs {
|
for _, err := range errMulti.Errs {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
}
|
}
|
||||||
default:
|
} else {
|
||||||
fmt.Fprintln(os.Stderr, "error:", err)
|
fmt.Fprintln(os.Stderr, "error:", err)
|
||||||
}
|
}
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -619,13 +538,10 @@ func main() {
|
|||||||
outpath := flag.String("o", "", "output filename")
|
outpath := flag.String("o", "", "output filename")
|
||||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||||
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
panicStrategy := flag.String("panic", "print", "panic strategy (abort, trap)")
|
||||||
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
|
|
||||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||||
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
|
target := flag.String("target", "", "LLVM target")
|
||||||
tags := flag.String("tags", "", "a space-separated list of extra build tags")
|
|
||||||
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
|
|
||||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||||
@@ -633,7 +549,6 @@ func main() {
|
|||||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||||
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||||
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
|
||||||
|
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
||||||
@@ -647,13 +562,10 @@ func main() {
|
|||||||
opt: *opt,
|
opt: *opt,
|
||||||
gc: *gc,
|
gc: *gc,
|
||||||
panicStrategy: *panicStrategy,
|
panicStrategy: *panicStrategy,
|
||||||
scheduler: *scheduler,
|
|
||||||
printIR: *printIR,
|
printIR: *printIR,
|
||||||
dumpSSA: *dumpSSA,
|
dumpSSA: *dumpSSA,
|
||||||
verifyIR: *verifyIR,
|
|
||||||
debug: !*nodebug,
|
debug: !*nodebug,
|
||||||
printSizes: *printSize,
|
printSizes: *printSize,
|
||||||
tags: *tags,
|
|
||||||
wasmAbi: *wasmAbi,
|
wasmAbi: *wasmAbi,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,13 +583,6 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
|
||||||
if config.heapSize, err = parseSize(*heapSize); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
|
||||||
usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Setenv("CC", "clang -target="+*target)
|
os.Setenv("CC", "clang -target="+*target)
|
||||||
|
|
||||||
switch command {
|
switch command {
|
||||||
@@ -687,11 +592,8 @@ func main() {
|
|||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
pkgName := "."
|
if flag.NArg() != 1 {
|
||||||
if flag.NArg() == 1 {
|
fmt.Fprintln(os.Stderr, "No package specified.")
|
||||||
pkgName = flag.Arg(0)
|
|
||||||
} else if flag.NArg() > 1 {
|
|
||||||
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
|
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
@@ -699,7 +601,7 @@ func main() {
|
|||||||
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
||||||
target = "wasm"
|
target = "wasm"
|
||||||
}
|
}
|
||||||
err := Build(pkgName, *outpath, target, config)
|
err := Build(flag.Arg(0), *outpath, target, config)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
case "build-builtins":
|
case "build-builtins":
|
||||||
// Note: this command is only meant to be used while making a release!
|
// Note: this command is only meant to be used while making a release!
|
||||||
@@ -741,17 +643,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
err := Run(flag.Arg(0), *target, config)
|
err := Run(flag.Arg(0), *target, config)
|
||||||
handleCompilerError(err)
|
handleCompilerError(err)
|
||||||
case "test":
|
|
||||||
pkgName := "."
|
|
||||||
if flag.NArg() == 1 {
|
|
||||||
pkgName = flag.Arg(0)
|
|
||||||
} else if flag.NArg() > 1 {
|
|
||||||
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
|
|
||||||
usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err := Test(pkgName, *target, config)
|
|
||||||
handleCompilerError(err)
|
|
||||||
case "clean":
|
case "clean":
|
||||||
// remove cache directory
|
// remove cache directory
|
||||||
dir := cacheDir()
|
dir := cacheDir()
|
||||||
|
|||||||
+3
-1
@@ -58,6 +58,9 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
t.Log("running tests for emulated cortex-m3...")
|
t.Log("running tests for emulated cortex-m3...")
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
|
if path == "testdata/reflect.go" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
runTest(path, tmpdir, "qemu", t)
|
runTest(path, tmpdir, "qemu", t)
|
||||||
})
|
})
|
||||||
@@ -116,7 +119,6 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
|||||||
opt: "z",
|
opt: "z",
|
||||||
printIR: false,
|
printIR: false,
|
||||||
dumpSSA: false,
|
dumpSSA: false,
|
||||||
verifyIR: true,
|
|
||||||
debug: false,
|
debug: false,
|
||||||
printSizes: "",
|
printSizes: "",
|
||||||
wasmAbi: "js",
|
wasmAbi: "js",
|
||||||
|
|||||||
+11
-44
@@ -30,7 +30,6 @@
|
|||||||
package arm
|
package arm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/volatile"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,58 +69,37 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
|||||||
// Run the following system call (SVCall) with 4 arguments.
|
// Run the following system call (SVCall) with 4 arguments.
|
||||||
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||||
|
|
||||||
|
//go:volatile
|
||||||
|
type RegValue uint32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SCS_BASE = 0xE000E000
|
SCS_BASE = 0xE000E000
|
||||||
NVIC_BASE = SCS_BASE + 0x0100
|
NVIC_BASE = SCS_BASE + 0x0100
|
||||||
SCB_BASE = SCS_BASE + 0x0D00
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
SCB_AIRCR_VECTKEY_Pos = 16
|
|
||||||
SCB_AIRCR_SYSRESETREQ_Pos = 2
|
|
||||||
SCB_AIRCR_SYSRESETREQ_Msk = 1 << SCB_AIRCR_SYSRESETREQ_Pos
|
|
||||||
)
|
|
||||||
|
|
||||||
// System Control Block (SCB)
|
|
||||||
//
|
|
||||||
// SCB_Type provides the definitions for the System Control Block Registers.
|
|
||||||
type SCB_Type struct {
|
|
||||||
CPUID volatile.Register32 // CPUID Base Register
|
|
||||||
ICSR volatile.Register32 // Interrupt Control and State Register
|
|
||||||
VTOR volatile.Register32 // Vector Table Offset Register
|
|
||||||
AIRCR volatile.Register32 // Application Interrupt and Reset Control Register
|
|
||||||
SCR volatile.Register32 // System Control Register
|
|
||||||
CCR volatile.Register32 // Configuration Control Register
|
|
||||||
_ volatile.Register32 // RESERVED1;
|
|
||||||
SHP [2]volatile.Register32 // System Handlers Priority Registers. [0] is RESERVED
|
|
||||||
SHCSR volatile.Register32 // System Handler Control and State Register
|
|
||||||
}
|
|
||||||
|
|
||||||
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
|
|
||||||
|
|
||||||
// Nested Vectored Interrupt Controller (NVIC).
|
// Nested Vectored Interrupt Controller (NVIC).
|
||||||
//
|
//
|
||||||
// Source:
|
// Source:
|
||||||
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
|
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
|
||||||
type NVIC_Type struct {
|
type NVIC_Type struct {
|
||||||
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
|
ISER [8]RegValue // Interrupt Set-enable Registers
|
||||||
_ [24]uint32
|
_ [24]uint32
|
||||||
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
|
ICER [8]RegValue // Interrupt Clear-enable Registers
|
||||||
_ [24]uint32
|
_ [24]uint32
|
||||||
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
|
ISPR [8]RegValue // Interrupt Set-pending Registers
|
||||||
_ [24]uint32
|
_ [24]uint32
|
||||||
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
|
ICPR [8]RegValue // Interrupt Clear-pending Registers
|
||||||
_ [24]uint32
|
_ [24]uint32
|
||||||
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
|
IABR [8]RegValue // Interrupt Active Bit Registers
|
||||||
_ [56]uint32
|
_ [56]uint32
|
||||||
IPR [60]volatile.Register32 // Interrupt Priority Registers
|
IPR [60]RegValue // Interrupt Priority Registers
|
||||||
}
|
}
|
||||||
|
|
||||||
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
||||||
|
|
||||||
// Enable the given interrupt number.
|
// Enable the given interrupt number.
|
||||||
func EnableIRQ(irq uint32) {
|
func EnableIRQ(irq uint32) {
|
||||||
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
|
NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the priority of the given interrupt number.
|
// Set the priority of the given interrupt number.
|
||||||
@@ -138,7 +116,7 @@ func SetPriority(irq uint32, priority uint32) {
|
|||||||
regpos := irq % 4
|
regpos := irq % 4
|
||||||
mask := uint32(0xff) << (regpos * 8) // bits to clear
|
mask := uint32(0xff) << (regpos * 8) // bits to clear
|
||||||
priority = priority << (regpos * 8) // bits to set
|
priority = priority << (regpos * 8) // bits to set
|
||||||
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
|
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableInterrupts disables all interrupts, and returns the old state.
|
// DisableInterrupts disables all interrupts, and returns the old state.
|
||||||
@@ -158,14 +136,3 @@ func DisableInterrupts() uintptr {
|
|||||||
func EnableInterrupts(mask uintptr) {
|
func EnableInterrupts(mask uintptr) {
|
||||||
Asm("cpsie if")
|
Asm("cpsie if")
|
||||||
}
|
}
|
||||||
|
|
||||||
// SystemReset performs a hard system reset.
|
|
||||||
func SystemReset() {
|
|
||||||
// SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
|
|
||||||
// SCB_AIRCR_SYSRESETREQ_Msk);
|
|
||||||
SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk)
|
|
||||||
|
|
||||||
for {
|
|
||||||
Asm("wfi")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
machine.InitADC()
|
machine.InitADC()
|
||||||
|
|
||||||
led := machine.LED
|
led := machine.GPIO{machine.LED}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
sensor := machine.ADC{machine.ADC2}
|
sensor := machine.ADC{machine.ADC2}
|
||||||
sensor.Configure()
|
sensor.Configure()
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
led := machine.LED
|
led := machine.GPIO{machine.LED}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
for {
|
for {
|
||||||
led.Low()
|
led.Low()
|
||||||
time.Sleep(time.Millisecond * 500)
|
time.Sleep(time.Millisecond * 500)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func led1() {
|
func led1() {
|
||||||
led := machine.LED1
|
led := machine.GPIO{machine.LED}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
for {
|
for {
|
||||||
println("+")
|
println("+")
|
||||||
led.Low()
|
led.Low()
|
||||||
@@ -30,8 +30,8 @@ func led1() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func led2() {
|
func led2() {
|
||||||
led := machine.LED2
|
led := machine.GPIO{machine.LED2}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
for {
|
for {
|
||||||
println(" +")
|
println(" +")
|
||||||
led.Low()
|
led.Low()
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import (
|
|||||||
|
|
||||||
// This example assumes that the button is connected to pin 8. Change the value
|
// This example assumes that the button is connected to pin 8. Change the value
|
||||||
// below to use a different pin.
|
// below to use a different pin.
|
||||||
const (
|
const buttonPin = 8
|
||||||
led = machine.LED
|
|
||||||
button = machine.Pin(8)
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led := machine.GPIO{machine.LED}
|
||||||
button.Configure(machine.PinConfig{Mode: machine.PinInput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
|
button := machine.GPIO{buttonPin}
|
||||||
|
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if button.Get() {
|
if button.Get() {
|
||||||
|
|||||||
@@ -8,29 +8,29 @@ import (
|
|||||||
// This example assumes that you are using the pca10040 board
|
// This example assumes that you are using the pca10040 board
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
led1 := machine.LED1
|
led1 := machine.GPIO{machine.LED1}
|
||||||
led1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led1.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
led2 := machine.LED2
|
led2 := machine.GPIO{machine.LED2}
|
||||||
led2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led2.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
led3 := machine.LED3
|
led3 := machine.GPIO{machine.LED3}
|
||||||
led3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led3.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
led4 := machine.LED4
|
led4 := machine.GPIO{machine.LED4}
|
||||||
led4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led4.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
button1 := machine.BUTTON1
|
button1 := machine.GPIO{machine.BUTTON1}
|
||||||
button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
button1.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||||
|
|
||||||
button2 := machine.BUTTON2
|
button2 := machine.GPIO{machine.BUTTON2}
|
||||||
button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
button2.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||||
|
|
||||||
button3 := machine.BUTTON3
|
button3 := machine.GPIO{machine.BUTTON3}
|
||||||
button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
button3.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||||
|
|
||||||
button4 := machine.BUTTON4
|
button4 := machine.GPIO{machine.BUTTON4}
|
||||||
button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
button4.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||||
|
|
||||||
for {
|
for {
|
||||||
led1.Set(button1.Get())
|
led1.Set(button1.Get())
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
// change these to test a different UART or pins if available
|
// change these to test a different UART or pins if available
|
||||||
var (
|
var (
|
||||||
uart = machine.UART0
|
uart = machine.UART0
|
||||||
tx = machine.UART_TX_PIN
|
tx uint8 = machine.UART_TX_PIN
|
||||||
rx = machine.UART_RX_PIN
|
rx uint8 = machine.UART_RX_PIN
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -8,17 +8,19 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// cs is the pin used for Chip Select (CS). Change to whatever is in use on your board.
|
// CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board.
|
||||||
const cs = machine.Pin(3)
|
const CS_PIN = 3
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tx []byte
|
tx []byte
|
||||||
rx []byte
|
rx []byte
|
||||||
val, result uint16
|
val, result uint16
|
||||||
|
cs machine.GPIO
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
cs = machine.GPIO{CS_PIN}
|
||||||
|
cs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
|
|
||||||
machine.SPI0.Configure(machine.SPIConfig{
|
machine.SPI0.Configure(machine.SPIConfig{
|
||||||
Frequency: 4000000,
|
Frequency: 4000000,
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ import (
|
|||||||
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
|
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
|
||||||
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
|
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
|
||||||
func main() {
|
func main() {
|
||||||
ledrow := machine.LED_ROW_1
|
ledrow := machine.GPIO{machine.LED_ROW_1}
|
||||||
ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
ledrow.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
ledcol := machine.LED_COL_1
|
ledcol := machine.GPIO{machine.LED_COL_1}
|
||||||
ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
ledcol.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
ledcol.Low()
|
ledcol.Low()
|
||||||
for {
|
for {
|
||||||
ledrow.Low()
|
ledrow.Low()
|
||||||
|
|||||||
@@ -1,26 +1,10 @@
|
|||||||
invoke: clean wasm_exec
|
|
||||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go
|
|
||||||
cp ./invoke/wasm.js ./html/
|
|
||||||
cp ./invoke/index.html ./html/
|
|
||||||
|
|
||||||
export: clean wasm_exec
|
export: clean wasm_exec
|
||||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go
|
tinygo build -o ./html/wasm.wasm -target wasm ./export/wasm.go
|
||||||
cp ./export/wasm.js ./html/
|
cp ./export/wasm.js ./html/
|
||||||
cp ./export/index.html ./html/
|
cp ./export/index.html ./html/
|
||||||
|
|
||||||
callback: clean wasm_exec
|
|
||||||
tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go
|
|
||||||
cp ./callback/wasm.js ./html/
|
|
||||||
cp ./callback/index.html ./html/
|
|
||||||
|
|
||||||
slices: clean wasm_exec
|
|
||||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go
|
|
||||||
cp ./slices/wasm.js ./html/
|
|
||||||
cp ./slices/index.html ./html/
|
|
||||||
|
|
||||||
|
|
||||||
main: clean wasm_exec
|
main: clean wasm_exec
|
||||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
|
tinygo build -o ./html/wasm.wasm -target wasm ./main/main.go
|
||||||
cp ./main/index.html ./html/
|
cp ./main/index.html ./html/
|
||||||
|
|
||||||
wasm_exec:
|
wasm_exec:
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
The examples here show two different ways of using WebAssembly with TinyGo:
|
The examples here show two different ways of using WebAssembly with TinyGo:
|
||||||
|
|
||||||
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
||||||
[the export folder](./export) for an example of this. Additionally, the Wasm
|
[the export folder](./export) for an example of this.
|
||||||
module (which has a default value of `env`) can be specified using
|
|
||||||
`//go:wasm-module <module>`.
|
|
||||||
1. Defining and executing a `func main()`. This is similar to how the Go
|
1. Defining and executing a `func main()`. This is similar to how the Go
|
||||||
standard library implementation works. See [the main folder](./main) for an
|
standard library implementation works. See [the main folder](./main) for an
|
||||||
example of this.
|
example of this.
|
||||||
@@ -36,7 +34,7 @@ $ make main
|
|||||||
Start the local web server:
|
Start the local web server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go run server.go
|
$ go run main.go
|
||||||
Serving ./html on http://localhost:8080
|
Serving ./html on http://localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
@@ -1,24 +1,24 @@
|
|||||||
// +build arduino
|
// +build avr,arduino
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
const CPU_FREQUENCY = 16000000
|
const CPU_FREQUENCY = 16000000
|
||||||
|
|
||||||
// LED on the Arduino
|
// LED on the Arduino
|
||||||
const LED Pin = 13
|
const LED = 13
|
||||||
|
|
||||||
// ADC on the Arduino
|
// ADC on the Arduino
|
||||||
const (
|
const (
|
||||||
ADC0 Pin = 0
|
ADC0 = 0
|
||||||
ADC1 Pin = 1
|
ADC1 = 1
|
||||||
ADC2 Pin = 2
|
ADC2 = 2
|
||||||
ADC3 Pin = 3
|
ADC3 = 3
|
||||||
ADC4 Pin = 4 // Used by TWI for SDA
|
ADC4 = 4 // Used by TWI for SDA
|
||||||
ADC5 Pin = 5 // Used by TWI for SCL
|
ADC5 = 5 // Used by TWI for SCL
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 1
|
UART_TX_PIN = 1
|
||||||
UART_RX_PIN Pin = 0
|
UART_RX_PIN = 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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}
|
|
||||||
)
|
|
||||||
@@ -1,9 +1,7 @@
|
|||||||
// +build bluepill
|
// +build stm32,bluepill
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
import "device/stm32"
|
|
||||||
|
|
||||||
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
|
// https://wiki.stm32duino.com/index.php?title=File:Bluepillpinout.gif
|
||||||
const (
|
const (
|
||||||
PA0 = portA + 0
|
PA0 = portA + 0
|
||||||
@@ -49,28 +47,10 @@ const (
|
|||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN = PA9
|
UART_TX_PIN = PA9
|
||||||
UART_RX_PIN = PA10
|
UART_RX_PIN = PA10
|
||||||
UART_ALT_TX_PIN = PB6
|
|
||||||
UART_ALT_RX_PIN = PB7
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// USART1 is the first hardware serial port on the STM32.
|
|
||||||
// Both UART0 and UART1 refer to USART1.
|
|
||||||
UART0 = UART{
|
|
||||||
Buffer: NewRingBuffer(),
|
|
||||||
Bus: stm32.USART1,
|
|
||||||
IRQVal: stm32.IRQ_USART1,
|
|
||||||
}
|
|
||||||
UART1 = &UART0
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:export USART1_IRQHandler
|
|
||||||
func handleUART1() {
|
|
||||||
UART1.Receive(byte((UART1.Bus.DR.Get() & 0xFF)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN = PA5
|
SPI0_SCK_PIN = PA5
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ package machine
|
|||||||
|
|
||||||
import "device/sam"
|
import "device/sam"
|
||||||
|
|
||||||
// used to reset into bootloader
|
|
||||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
|
||||||
|
|
||||||
// GPIO Pins
|
// GPIO Pins
|
||||||
const (
|
const (
|
||||||
D0 = PB09
|
D0 = PB09
|
||||||
@@ -20,7 +17,7 @@ const (
|
|||||||
D8 = PB23
|
D8 = PB23
|
||||||
D9 = PA06
|
D9 = PA06
|
||||||
D10 = PA07
|
D10 = PA07
|
||||||
D11 = NoPin // does not seem to exist
|
D11 = 0xff // does not seem to exist
|
||||||
D12 = PA02
|
D12 = PA02
|
||||||
D13 = PA17 // PWM available
|
D13 = PA17 // PWM available
|
||||||
)
|
)
|
||||||
@@ -68,20 +65,6 @@ const (
|
|||||||
UART_RX_PIN = PB09 // PORTB
|
UART_RX_PIN = PB09 // PORTB
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART1 on the Circuit Playground Express.
|
|
||||||
var (
|
|
||||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
|
||||||
Buffer: NewRingBuffer(),
|
|
||||||
Mode: PinSERCOM,
|
|
||||||
IRQVal: sam.IRQ_SERCOM1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:export SERCOM1_IRQHandler
|
|
||||||
func handleUART1() {
|
|
||||||
defaultUART1Handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN = PB02 // I2C0 external
|
SDA_PIN = PB02 // I2C0 external
|
||||||
@@ -97,12 +80,12 @@ var (
|
|||||||
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
|
I2C0 = I2C{Bus: sam.SERCOM5_I2CM,
|
||||||
SDA: SDA_PIN,
|
SDA: SDA_PIN,
|
||||||
SCL: SCL_PIN,
|
SCL: SCL_PIN,
|
||||||
PinMode: PinSERCOM}
|
PinMode: GPIO_SERCOM}
|
||||||
// internal device
|
// internal device
|
||||||
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
|
I2C1 = I2C{Bus: sam.SERCOM1_I2CM,
|
||||||
SDA: SDA1_PIN,
|
SDA: SDA1_PIN,
|
||||||
SCL: SCL1_PIN,
|
SCL: SCL1_PIN,
|
||||||
PinMode: PinSERCOMAlt}
|
PinMode: GPIO_SERCOM_ALT}
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins (internal flash)
|
// SPI pins (internal flash)
|
||||||
@@ -114,20 +97,14 @@ const (
|
|||||||
|
|
||||||
// SPI on the Circuit Playground Express.
|
// SPI on the Circuit Playground Express.
|
||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM3_SPI,
|
SPI0 = SPI{Bus: sam.SERCOM3_SPI}
|
||||||
SCK: SPI0_SCK_PIN,
|
|
||||||
MOSI: SPI0_MOSI_PIN,
|
|
||||||
MISO: SPI0_MISO_PIN,
|
|
||||||
DOpad: spiTXPad2SCK3,
|
|
||||||
DIpad: sercomRXPad0,
|
|
||||||
PinMode: PinSERCOMAlt}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2S pins
|
// I2S pins
|
||||||
const (
|
const (
|
||||||
I2S_SCK_PIN = PA10
|
I2S_SCK_PIN = PA10
|
||||||
I2S_SD_PIN = PA08
|
I2S_SD_PIN = PA08
|
||||||
I2S_WS_PIN = NoPin // no WS, instead uses SCK to sync
|
I2S_WS_PIN = 0xff // no WS, instead uses SCK to sync
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2S on the Circuit Playground Express.
|
// I2S on the Circuit Playground Express.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// +build digispark
|
// +build attiny85,digispark
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
const (
|
const (
|
||||||
LED Pin = 1
|
LED = 1
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -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.
|
|
||||||
)
|
|
||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -2,12 +2,7 @@
|
|||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
import "device/sam"
|
||||||
"device/sam"
|
|
||||||
)
|
|
||||||
|
|
||||||
// used to reset into bootloader
|
|
||||||
const RESET_MAGIC_VALUE = 0xf01669ef
|
|
||||||
|
|
||||||
// GPIO Pins
|
// GPIO Pins
|
||||||
const (
|
const (
|
||||||
@@ -53,20 +48,6 @@ const (
|
|||||||
UART_RX_PIN = D11
|
UART_RX_PIN = D11
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART1 on the ItsyBitsy M0.
|
|
||||||
var (
|
|
||||||
UART1 = UART{Bus: sam.SERCOM1_USART,
|
|
||||||
Buffer: NewRingBuffer(),
|
|
||||||
Mode: PinSERCOM,
|
|
||||||
IRQVal: sam.IRQ_SERCOM1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:export SERCOM1_IRQHandler
|
|
||||||
func handleUART1() {
|
|
||||||
defaultUART1Handler()
|
|
||||||
}
|
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
|
SDA_PIN = PA22 // SDA: SERCOM3/PAD[0]
|
||||||
@@ -78,7 +59,7 @@ var (
|
|||||||
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
I2C0 = I2C{Bus: sam.SERCOM3_I2CM,
|
||||||
SDA: SDA_PIN,
|
SDA: SDA_PIN,
|
||||||
SCL: SCL_PIN,
|
SCL: SCL_PIN,
|
||||||
PinMode: PinSERCOM}
|
PinMode: GPIO_SERCOM}
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
@@ -90,39 +71,14 @@ const (
|
|||||||
|
|
||||||
// SPI on the ItsyBitsy M0.
|
// SPI on the ItsyBitsy M0.
|
||||||
var (
|
var (
|
||||||
SPI0 = SPI{Bus: sam.SERCOM4_SPI,
|
SPI0 = SPI{Bus: sam.SERCOM4_SPI}
|
||||||
SCK: SPI0_SCK_PIN,
|
|
||||||
MOSI: SPI0_MOSI_PIN,
|
|
||||||
MISO: SPI0_MISO_PIN,
|
|
||||||
DOpad: spiTXPad2SCK3,
|
|
||||||
DIpad: sercomRXPad0,
|
|
||||||
PinMode: PinSERCOMAlt}
|
|
||||||
)
|
|
||||||
|
|
||||||
// "Internal" SPI pins; SPI flash is attached to these on ItsyBitsy M0
|
|
||||||
const (
|
|
||||||
SPI1_CS_PIN = PA27
|
|
||||||
SPI1_SCK_PIN = PB23
|
|
||||||
SPI1_MOSI_PIN = PB22
|
|
||||||
SPI1_MISO_PIN = PB03
|
|
||||||
)
|
|
||||||
|
|
||||||
// "Internal" SPI on Sercom 5
|
|
||||||
var (
|
|
||||||
SPI1 = SPI{Bus: sam.SERCOM5_SPI,
|
|
||||||
SCK: SPI1_SCK_PIN,
|
|
||||||
MOSI: SPI1_MOSI_PIN,
|
|
||||||
MISO: SPI1_MISO_PIN,
|
|
||||||
DOpad: spiTXPad2SCK3,
|
|
||||||
DIpad: sercomRXPad1,
|
|
||||||
PinMode: PinSERCOMAlt}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2S pins
|
// I2S pins
|
||||||
const (
|
const (
|
||||||
I2S_SCK_PIN = PA10
|
I2S_SCK_PIN = PA10
|
||||||
I2S_SD_PIN = PA08
|
I2S_SD_PIN = PA08
|
||||||
I2S_WS_PIN = NoPin // TODO: figure out what this is on ItsyBitsy M0.
|
I2S_WS_PIN = 0xff // TODO: figure out what this is on ItsyBitsy M0.
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2S on the ItsyBitsy M0.
|
// I2S on the ItsyBitsy M0.
|
||||||
|
|||||||
@@ -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}
|
|
||||||
)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build microbit
|
// +build nrf51,microbit
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
@@ -7,70 +7,70 @@ const HasLowFrequencyCrystal = false
|
|||||||
|
|
||||||
// Buttons on the micro:bit (A and B)
|
// Buttons on the micro:bit (A and B)
|
||||||
const (
|
const (
|
||||||
BUTTON Pin = BUTTONA
|
BUTTON = BUTTONA
|
||||||
BUTTONA Pin = 17
|
BUTTONA = 17
|
||||||
BUTTONB Pin = 26
|
BUTTONB = 26
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 24
|
UART_TX_PIN = 24
|
||||||
UART_RX_PIN Pin = 25
|
UART_RX_PIN = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADC pins
|
// ADC pins
|
||||||
const (
|
const (
|
||||||
ADC0 Pin = 3 // P0 on the board
|
ADC0 = 3 // P0 on the board
|
||||||
ADC1 Pin = 2 // P1 on the board
|
ADC1 = 2 // P1 on the board
|
||||||
ADC2 Pin = 1 // P2 on the board
|
ADC2 = 1 // P2 on the board
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN Pin = 30 // P20 on the board
|
SDA_PIN = 30 // P20 on the board
|
||||||
SCL_PIN Pin = 0 // P19 on the board
|
SCL_PIN = 0 // P19 on the board
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN Pin = 23 // P13 on the board
|
SPI0_SCK_PIN = 23 // P13 on the board
|
||||||
SPI0_MOSI_PIN Pin = 21 // P15 on the board
|
SPI0_MOSI_PIN = 21 // P15 on the board
|
||||||
SPI0_MISO_PIN Pin = 22 // P14 on the board
|
SPI0_MISO_PIN = 22 // P14 on the board
|
||||||
)
|
)
|
||||||
|
|
||||||
// GPIO/Analog pins
|
// GPIO/Analog pins
|
||||||
const (
|
const (
|
||||||
P0 Pin = 3
|
P0 = 3
|
||||||
P1 Pin = 2
|
P1 = 2
|
||||||
P2 Pin = 1
|
P2 = 1
|
||||||
P3 Pin = 4
|
P3 = 4
|
||||||
P4 Pin = 5
|
P4 = 5
|
||||||
P5 Pin = 17
|
P5 = 17
|
||||||
P6 Pin = 12
|
P6 = 12
|
||||||
P7 Pin = 11
|
P7 = 11
|
||||||
P8 Pin = 18
|
P8 = 18
|
||||||
P9 Pin = 10
|
P9 = 10
|
||||||
P10 Pin = 6
|
P10 = 6
|
||||||
P11 Pin = 26
|
P11 = 26
|
||||||
P12 Pin = 20
|
P12 = 20
|
||||||
P13 Pin = 23
|
P13 = 23
|
||||||
P14 Pin = 22
|
P14 = 22
|
||||||
P15 Pin = 21
|
P15 = 21
|
||||||
P16 Pin = 16
|
P16 = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
// LED matrix pins
|
// LED matrix pins
|
||||||
const (
|
const (
|
||||||
LED_COL_1 Pin = 4
|
LED_COL_1 = 4
|
||||||
LED_COL_2 Pin = 5
|
LED_COL_2 = 5
|
||||||
LED_COL_3 Pin = 6
|
LED_COL_3 = 6
|
||||||
LED_COL_4 Pin = 7
|
LED_COL_4 = 7
|
||||||
LED_COL_5 Pin = 8
|
LED_COL_5 = 8
|
||||||
LED_COL_6 Pin = 9
|
LED_COL_6 = 9
|
||||||
LED_COL_7 Pin = 10
|
LED_COL_7 = 10
|
||||||
LED_COL_8 Pin = 11
|
LED_COL_8 = 11
|
||||||
LED_COL_9 Pin = 12
|
LED_COL_9 = 12
|
||||||
LED_ROW_1 Pin = 13
|
LED_ROW_1 = 13
|
||||||
LED_ROW_2 Pin = 14
|
LED_ROW_2 = 14
|
||||||
LED_ROW_3 Pin = 15
|
LED_ROW_3 = 15
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,27 +6,27 @@ const HasLowFrequencyCrystal = true
|
|||||||
|
|
||||||
// LEDs on the nrf52840-mdk (nRF52840 dev board)
|
// LEDs on the nrf52840-mdk (nRF52840 dev board)
|
||||||
const (
|
const (
|
||||||
LED Pin = LED_GREEN
|
LED = LED_GREEN
|
||||||
LED_GREEN Pin = 22
|
LED_GREEN = 22
|
||||||
LED_RED Pin = 23
|
LED_RED = 23
|
||||||
LED_BLUE Pin = 24
|
LED_BLUE = 24
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 20
|
UART_TX_PIN = 20
|
||||||
UART_RX_PIN Pin = 19
|
UART_RX_PIN = 19
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins (unused)
|
// I2C pins (unused)
|
||||||
const (
|
const (
|
||||||
SDA_PIN = NoPin
|
SDA_PIN = 0xff
|
||||||
SCL_PIN = NoPin
|
SCL_PIN = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins (unused)
|
// SPI pins (unused)
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN = NoPin
|
SPI0_SCK_PIN = 0
|
||||||
SPI0_MOSI_PIN = NoPin
|
SPI0_MOSI_PIN = 0
|
||||||
SPI0_MISO_PIN = NoPin
|
SPI0_MISO_PIN = 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build pca10031
|
// +build nrf51,pca10031
|
||||||
|
|
||||||
// pca10031 is a nrf51 based dongle, intended for use in wireless applications.
|
// pca10031 is a nrf51 based dongle, intended for use in wireless applications.
|
||||||
//
|
//
|
||||||
@@ -10,30 +10,30 @@ const HasLowFrequencyCrystal = true
|
|||||||
|
|
||||||
// LED on the pca10031
|
// LED on the pca10031
|
||||||
const (
|
const (
|
||||||
LED Pin = LED_RED
|
LED = LED_RED
|
||||||
LED1 Pin = LED_RED
|
LED1 = LED_RED
|
||||||
LED2 Pin = LED_GREEN
|
LED2 = LED_GREEN
|
||||||
LED3 Pin = LED_BLUE
|
LED3 = LED_BLUE
|
||||||
LED_RED Pin = 21
|
LED_RED = 21
|
||||||
LED_GREEN Pin = 22
|
LED_GREEN = 22
|
||||||
LED_BLUE Pin = 23
|
LED_BLUE = 23
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 9
|
UART_TX_PIN = 9
|
||||||
UART_RX_PIN Pin = 11
|
UART_RX_PIN = 11
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins (disabled)
|
// I2C pins (disabled)
|
||||||
const (
|
const (
|
||||||
SDA_PIN = NoPin
|
SDA_PIN = 0xff
|
||||||
SCL_PIN = NoPin
|
SCL_PIN = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins (unused)
|
// SPI pins (unused)
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN = NoPin
|
SPI0_SCK_PIN = 0
|
||||||
SPI0_MOSI_PIN = NoPin
|
SPI0_MOSI_PIN = 0
|
||||||
SPI0_MISO_PIN = NoPin
|
SPI0_MISO_PIN = 0
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// +build pca10040
|
// +build nrf,pca10040
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
@@ -7,47 +7,47 @@ const HasLowFrequencyCrystal = true
|
|||||||
|
|
||||||
// LEDs on the PCA10040 (nRF52832 dev board)
|
// LEDs on the PCA10040 (nRF52832 dev board)
|
||||||
const (
|
const (
|
||||||
LED Pin = LED1
|
LED = LED1
|
||||||
LED1 Pin = 17
|
LED1 = 17
|
||||||
LED2 Pin = 18
|
LED2 = 18
|
||||||
LED3 Pin = 19
|
LED3 = 19
|
||||||
LED4 Pin = 20
|
LED4 = 20
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buttons on the PCA10040 (nRF52832 dev board)
|
// Buttons on the PCA10040 (nRF52832 dev board)
|
||||||
const (
|
const (
|
||||||
BUTTON Pin = BUTTON1
|
BUTTON = BUTTON1
|
||||||
BUTTON1 Pin = 13
|
BUTTON1 = 13
|
||||||
BUTTON2 Pin = 14
|
BUTTON2 = 14
|
||||||
BUTTON3 Pin = 15
|
BUTTON3 = 15
|
||||||
BUTTON4 Pin = 16
|
BUTTON4 = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins for NRF52840-DK
|
// UART pins for NRF52840-DK
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 6
|
UART_TX_PIN = 6
|
||||||
UART_RX_PIN Pin = 8
|
UART_RX_PIN = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADC pins
|
// ADC pins
|
||||||
const (
|
const (
|
||||||
ADC0 Pin = 3
|
ADC0 = 3
|
||||||
ADC1 Pin = 4
|
ADC1 = 4
|
||||||
ADC2 Pin = 28
|
ADC2 = 28
|
||||||
ADC3 Pin = 29
|
ADC3 = 29
|
||||||
ADC4 Pin = 30
|
ADC4 = 30
|
||||||
ADC5 Pin = 31
|
ADC5 = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN Pin = 26
|
SDA_PIN = 26
|
||||||
SCL_PIN Pin = 27
|
SCL_PIN = 27
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN Pin = 25
|
SPI0_SCK_PIN = 25
|
||||||
SPI0_MOSI_PIN Pin = 23
|
SPI0_MOSI_PIN = 23
|
||||||
SPI0_MISO_PIN Pin = 24
|
SPI0_MISO_PIN = 24
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,47 +6,47 @@ const HasLowFrequencyCrystal = true
|
|||||||
|
|
||||||
// LEDs on the pca10056
|
// LEDs on the pca10056
|
||||||
const (
|
const (
|
||||||
LED Pin = LED1
|
LED = LED1
|
||||||
LED1 Pin = 13
|
LED1 = 13
|
||||||
LED2 Pin = 14
|
LED2 = 14
|
||||||
LED3 Pin = 15
|
LED3 = 15
|
||||||
LED4 Pin = 16
|
LED4 = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
// Buttons on the pca10056
|
// Buttons on the pca10056
|
||||||
const (
|
const (
|
||||||
BUTTON Pin = BUTTON1
|
BUTTON = BUTTON1
|
||||||
BUTTON1 Pin = 11
|
BUTTON1 = 11
|
||||||
BUTTON2 Pin = 12
|
BUTTON2 = 12
|
||||||
BUTTON3 Pin = 24
|
BUTTON3 = 24
|
||||||
BUTTON4 Pin = 25
|
BUTTON4 = 25
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 6
|
UART_TX_PIN = 6
|
||||||
UART_RX_PIN Pin = 8
|
UART_RX_PIN = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// ADC pins
|
// ADC pins
|
||||||
const (
|
const (
|
||||||
ADC0 Pin = 3
|
ADC0 = 3
|
||||||
ADC1 Pin = 4
|
ADC1 = 4
|
||||||
ADC2 Pin = 28
|
ADC2 = 28
|
||||||
ADC3 Pin = 29
|
ADC3 = 29
|
||||||
ADC4 Pin = 30
|
ADC4 = 30
|
||||||
ADC5 Pin = 31
|
ADC5 = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN Pin = 26 // P0.26
|
SDA_PIN = 26 // P0.26
|
||||||
SCL_PIN Pin = 27 // P0.27
|
SCL_PIN = 27 // P0.27
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN Pin = 47 // P1.15
|
SPI0_SCK_PIN = 47 // P1.15
|
||||||
SPI0_MOSI_PIN Pin = 45 // P1.13
|
SPI0_MOSI_PIN = 45 // P1.13
|
||||||
SPI0_MISO_PIN Pin = 46 // P1.14
|
SPI0_MISO_PIN = 46 // P1.14
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,58 +4,39 @@ package machine
|
|||||||
|
|
||||||
const HasLowFrequencyCrystal = true
|
const HasLowFrequencyCrystal = true
|
||||||
|
|
||||||
// Pins on the reel board
|
// LEDs on the reel board
|
||||||
const (
|
const (
|
||||||
LED Pin = LED1
|
LED = LED1
|
||||||
LED1 Pin = LED_YELLOW
|
LED1 = LED_YELLOW
|
||||||
LED2 Pin = LED_RED
|
LED2 = LED_RED
|
||||||
LED3 Pin = LED_GREEN
|
LED3 = LED_GREEN
|
||||||
LED4 Pin = LED_BLUE
|
LED4 = LED_BLUE
|
||||||
LED_RED Pin = 11
|
LED_RED = 11
|
||||||
LED_GREEN Pin = 12
|
LED_GREEN = 12
|
||||||
LED_BLUE Pin = 41
|
LED_BLUE = 41
|
||||||
LED_YELLOW Pin = 13
|
LED_YELLOW = 13
|
||||||
EPD_BUSY_PIN Pin = 14
|
|
||||||
EPD_RESET_PIN Pin = 15
|
|
||||||
EPD_DC_PIN Pin = 16
|
|
||||||
EPD_CS_PIN Pin = 17
|
|
||||||
EPD_SCK_PIN Pin = 19
|
|
||||||
EPD_MOSI_PIN Pin = 20
|
|
||||||
POWER_SUPPLY_PIN Pin = 32
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// User "a" button on the reel board
|
// User "a" button on the reel board
|
||||||
const (
|
const (
|
||||||
BUTTON Pin = 7
|
BUTTON = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
// UART pins
|
// UART pins
|
||||||
const (
|
const (
|
||||||
UART_TX_PIN Pin = 6
|
UART_TX_PIN = 6
|
||||||
UART_RX_PIN Pin = 8
|
UART_RX_PIN = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
// I2C pins
|
// I2C pins
|
||||||
const (
|
const (
|
||||||
SDA_PIN Pin = 26
|
SDA_PIN = 26
|
||||||
SCL_PIN Pin = 27
|
SCL_PIN = 27
|
||||||
)
|
)
|
||||||
|
|
||||||
// SPI pins
|
// SPI pins
|
||||||
const (
|
const (
|
||||||
SPI0_SCK_PIN Pin = 47
|
SPI0_SCK_PIN = 47
|
||||||
SPI0_MOSI_PIN Pin = 45
|
SPI0_MOSI_PIN = 45
|
||||||
SPI0_MISO_PIN Pin = 46
|
SPI0_MISO_PIN = 46
|
||||||
)
|
)
|
||||||
|
|
||||||
// PowerSupplyActive enables the supply voltages for nRF52840 and peripherals (true) or only for nRF52840 (false)
|
|
||||||
// This controls the TPS610981 boost converter. You must turn the power supply active in order to use the EPD and
|
|
||||||
// other onboard peripherals.
|
|
||||||
func PowerSupplyActive(active bool) {
|
|
||||||
POWER_SUPPLY_PIN.Configure(PinConfig{Mode: PinOutput})
|
|
||||||
if active {
|
|
||||||
POWER_SUPPLY_PIN.High()
|
|
||||||
} else {
|
|
||||||
POWER_SUPPLY_PIN.Low()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
// +build stm32f4disco
|
// +build stm32,stm32f4disco
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -1,11 +1,10 @@
|
|||||||
package machine
|
package machine
|
||||||
|
|
||||||
import (
|
|
||||||
"runtime/volatile"
|
|
||||||
)
|
|
||||||
|
|
||||||
const bufferSize = 128
|
const bufferSize = 128
|
||||||
|
|
||||||
|
//go:volatile
|
||||||
|
type volatileByte byte
|
||||||
|
|
||||||
// RingBuffer is ring buffer implementation inspired by post at
|
// RingBuffer is ring buffer implementation inspired by post at
|
||||||
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
// https://www.embeddedrelated.com/showthread/comp.arch.embedded/77084-1.php
|
||||||
//
|
//
|
||||||
@@ -13,9 +12,9 @@ const bufferSize = 128
|
|||||||
// members of a struct are not compiled correctly by TinyGo.
|
// members of a struct are not compiled correctly by TinyGo.
|
||||||
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
|
// See https://github.com/tinygo-org/tinygo/issues/151 for details.
|
||||||
type RingBuffer struct {
|
type RingBuffer struct {
|
||||||
rxbuffer [bufferSize]volatile.Register8
|
rxbuffer [bufferSize]volatileByte
|
||||||
head volatile.Register8
|
head volatileByte
|
||||||
tail volatile.Register8
|
tail volatileByte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRingBuffer returns a new ring buffer.
|
// NewRingBuffer returns a new ring buffer.
|
||||||
@@ -25,15 +24,15 @@ func NewRingBuffer() *RingBuffer {
|
|||||||
|
|
||||||
// Used returns how many bytes in buffer have been used.
|
// Used returns how many bytes in buffer have been used.
|
||||||
func (rb *RingBuffer) Used() uint8 {
|
func (rb *RingBuffer) Used() uint8 {
|
||||||
return uint8(rb.head.Get() - rb.tail.Get())
|
return uint8(rb.head - rb.tail)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put stores a byte in the buffer. If the buffer is already
|
// Put stores a byte in the buffer. If the buffer is already
|
||||||
// full, the method will return false.
|
// full, the method will return false.
|
||||||
func (rb *RingBuffer) Put(val byte) bool {
|
func (rb *RingBuffer) Put(val byte) bool {
|
||||||
if rb.Used() != bufferSize {
|
if rb.Used() != bufferSize {
|
||||||
rb.head.Set(rb.head.Get() + 1)
|
rb.head++
|
||||||
rb.rxbuffer[rb.head.Get()%bufferSize].Set(val)
|
rb.rxbuffer[rb.head%bufferSize] = volatileByte(val)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -43,8 +42,8 @@ func (rb *RingBuffer) Put(val byte) bool {
|
|||||||
// the method will return a false as the second value.
|
// the method will return a false as the second value.
|
||||||
func (rb *RingBuffer) Get() (byte, bool) {
|
func (rb *RingBuffer) Get() (byte, bool) {
|
||||||
if rb.Used() != 0 {
|
if rb.Used() != 0 {
|
||||||
rb.tail.Set(rb.tail.Get() + 1)
|
rb.tail++
|
||||||
return rb.rxbuffer[rb.tail.Get()%bufferSize].Get(), true
|
return byte(rb.rxbuffer[rb.tail%bufferSize]), true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// +build avr nrf sam stm32,!stm32f4disco
|
// +build avr nrf sam stm32f103xx
|
||||||
|
|
||||||
package machine
|
package machine
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -41,9 +41,9 @@ const (
|
|||||||
|
|
||||||
// All fields are optional and may not be required or used on a particular platform.
|
// All fields are optional and may not be required or used on a particular platform.
|
||||||
type I2SConfig struct {
|
type I2SConfig struct {
|
||||||
SCK Pin
|
SCK uint8
|
||||||
WS Pin
|
WS uint8
|
||||||
SD Pin
|
SD uint8
|
||||||
Mode I2SMode
|
Mode I2SMode
|
||||||
Standard I2SStandard
|
Standard I2SStandard
|
||||||
ClockSource I2SClockSource
|
ClockSource I2SClockSource
|
||||||
|
|||||||
+9
-20
@@ -1,36 +1,25 @@
|
|||||||
package machine
|
package machine
|
||||||
|
|
||||||
type PinConfig struct {
|
type GPIOConfig struct {
|
||||||
Mode PinMode
|
Mode GPIOMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pin is a single pin on a chip, which may be connected to other hardware
|
type GPIO struct {
|
||||||
// devices. It can either be used directly as GPIO pin or it can be used in
|
Pin uint8
|
||||||
// other peripherals like ADC, I2C, etc.
|
}
|
||||||
type Pin int8
|
|
||||||
|
|
||||||
// NoPin explicitly indicates "not a pin". Use this pin if you want to leave one
|
func (p GPIO) High() {
|
||||||
// of the pins in a peripheral unconfigured (if supported by the hardware).
|
|
||||||
const NoPin = Pin(-1)
|
|
||||||
|
|
||||||
// High sets this GPIO pin to high, assuming it has been configured as an output
|
|
||||||
// pin. It is hardware dependent (and often undefined) what happens if you set a
|
|
||||||
// pin to high that is not configured as an output pin.
|
|
||||||
func (p Pin) High() {
|
|
||||||
p.Set(true)
|
p.Set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Low sets this GPIO pin to low, assuming it has been configured as an output
|
func (p GPIO) Low() {
|
||||||
// pin. It is hardware dependent (and often undefined) what happens if you set a
|
|
||||||
// pin to low that is not configured as an output pin.
|
|
||||||
func (p Pin) Low() {
|
|
||||||
p.Set(false)
|
p.Set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PWM struct {
|
type PWM struct {
|
||||||
Pin Pin
|
Pin uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
type ADC struct {
|
type ADC struct {
|
||||||
Pin Pin
|
Pin uint8
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,42 +4,41 @@ package machine
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"device/avr"
|
"device/avr"
|
||||||
"runtime/volatile"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Configure sets the pin to input or output.
|
// Configure sets the pin to input or output.
|
||||||
func (p Pin) Configure(config PinConfig) {
|
func (p GPIO) Configure(config GPIOConfig) {
|
||||||
if config.Mode == PinOutput { // set output bit
|
if config.Mode == GPIO_OUTPUT { // set output bit
|
||||||
if p < 8 {
|
if p.Pin < 8 {
|
||||||
avr.DDRD.SetBits(1 << uint8(p))
|
avr.DDRD.SetBits(1 << p.Pin)
|
||||||
} else {
|
} else {
|
||||||
avr.DDRB.SetBits(1 << uint8(p-8))
|
avr.DDRB.SetBits(1 << (p.Pin - 8))
|
||||||
}
|
}
|
||||||
} else { // configure input: clear output bit
|
} else { // configure input: clear output bit
|
||||||
if p < 8 {
|
if p.Pin < 8 {
|
||||||
avr.DDRD.ClearBits(1 << uint8(p))
|
avr.DDRD.ClearBits(1 << p.Pin)
|
||||||
} else {
|
} else {
|
||||||
avr.DDRB.ClearBits(1 << uint8(p-8))
|
avr.DDRB.ClearBits(1 << (p.Pin - 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the current value of a GPIO pin.
|
// Get returns the current value of a GPIO pin.
|
||||||
func (p Pin) Get() bool {
|
func (p GPIO) Get() bool {
|
||||||
if p < 8 {
|
if p.Pin < 8 {
|
||||||
val := avr.PIND.Get() & (1 << uint8(p))
|
val := avr.PIND.Get() & (1 << p.Pin)
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
} else {
|
} else {
|
||||||
val := avr.PINB.Get() & (1 << uint8(p-8))
|
val := avr.PINB.Get() & (1 << (p.Pin - 8))
|
||||||
return (val > 0)
|
return (val > 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Pin) getPortMask() (*volatile.Register8, uint8) {
|
func (p GPIO) getPortMask() (*avr.Register8, uint8) {
|
||||||
if p < 8 {
|
if p.Pin < 8 {
|
||||||
return avr.PORTD, 1 << uint8(p)
|
return avr.PORTD, 1 << p.Pin
|
||||||
} else {
|
} else {
|
||||||
return avr.PORTB, 1 << uint8(p-8)
|
return avr.PORTB, 1 << (p.Pin - 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +66,9 @@ func InitPWM() {
|
|||||||
// Configure configures a PWM pin for output.
|
// Configure configures a PWM pin for output.
|
||||||
func (pwm PWM) Configure() {
|
func (pwm PWM) Configure() {
|
||||||
if pwm.Pin < 8 {
|
if pwm.Pin < 8 {
|
||||||
avr.DDRD.SetBits(1 << uint8(pwm.Pin))
|
avr.DDRD.SetBits(1 << pwm.Pin)
|
||||||
} else {
|
} else {
|
||||||
avr.DDRB.SetBits(1 << uint8(pwm.Pin-8))
|
avr.DDRB.SetBits(1 << (pwm.Pin - 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +165,7 @@ func (i2c I2C) start(address uint8, write bool) {
|
|||||||
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
|
avr.TWCR.Set((avr.TWCR_TWINT | avr.TWCR_TWSTA | avr.TWCR_TWEN))
|
||||||
|
|
||||||
// Wait till start condition is transmitted.
|
// Wait till start condition is transmitted.
|
||||||
for !avr.TWCR.HasBits(avr.TWCR_TWINT) {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write 7-bit shifted peripheral address.
|
// Write 7-bit shifted peripheral address.
|
||||||
@@ -183,7 +182,7 @@ func (i2c I2C) stop() {
|
|||||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWSTO)
|
||||||
|
|
||||||
// Wait for stop condition to be executed on bus.
|
// Wait for stop condition to be executed on bus.
|
||||||
for !avr.TWCR.HasBits(avr.TWCR_TWSTO) {
|
for (avr.TWCR.Get() & avr.TWCR_TWSTO) == 0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +195,7 @@ func (i2c I2C) writeByte(data byte) {
|
|||||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT)
|
||||||
|
|
||||||
// Wait till data is transmitted.
|
// Wait till data is transmitted.
|
||||||
for !avr.TWCR.HasBits(avr.TWCR_TWINT) {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +205,7 @@ func (i2c I2C) readByte() byte {
|
|||||||
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
avr.TWCR.Set(avr.TWCR_TWEN | avr.TWCR_TWINT | avr.TWCR_TWEA)
|
||||||
|
|
||||||
// Wait till read request is transmitted.
|
// Wait till read request is transmitted.
|
||||||
for !avr.TWCR.HasBits(avr.TWCR_TWINT) {
|
for (avr.TWCR.Get() & avr.TWCR_TWINT) == 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
return byte(avr.TWDR.Get())
|
return byte(avr.TWDR.Get())
|
||||||
@@ -240,7 +239,7 @@ func (uart UART) Configure(config UARTConfig) {
|
|||||||
// WriteByte writes a byte of data to the UART.
|
// WriteByte writes a byte of data to the UART.
|
||||||
func (uart UART) WriteByte(c byte) error {
|
func (uart UART) WriteByte(c byte) error {
|
||||||
// Wait until UART buffer is not busy.
|
// Wait until UART buffer is not busy.
|
||||||
for !avr.UCSR0A.HasBits(avr.UCSR0A_UDRE0) {
|
for (avr.UCSR0A.Get() & avr.UCSR0A_UDRE0) == 0 {
|
||||||
}
|
}
|
||||||
avr.UDR0.Set(c) // send char
|
avr.UDR0.Set(c) // send char
|
||||||
return nil
|
return nil
|
||||||
@@ -252,7 +251,7 @@ func handleUSART_RX() {
|
|||||||
data := avr.UDR0.Get()
|
data := avr.UDR0.Get()
|
||||||
|
|
||||||
// Ensure no error.
|
// Ensure no error.
|
||||||
if !avr.UCSR0A.HasBits(avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0) {
|
if (avr.UCSR0A.Get() & (avr.UCSR0A_FE0 | avr.UCSR0A_DOR0 | avr.UCSR0A_UPE0)) == 0 {
|
||||||
// Put data from UDR register into buffer.
|
// Put data from UDR register into buffer.
|
||||||
UART0.Receive(byte(data))
|
UART0.Receive(byte(data))
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user