mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
302 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cc313798f3 | |||
| 86acfc7ef4 | |||
| a5fb785334 | |||
| 86b888fdcb | |||
| ef1bfe359c | |||
| 0a5601c3fc | |||
| f5fd49bb74 | |||
| d2856bd6bd | |||
| 869d2c4524 | |||
| 9d35c1197f | |||
| 688dd81400 | |||
| abca3132a9 | |||
| e0ebc75df2 | |||
| b4859240e1 | |||
| 4eb34b36f8 | |||
| 61750be9aa | |||
| efd9cf72ba | |||
| 500ee8067d | |||
| 8cd2c7502e | |||
| d905476231 | |||
| 10ed3decb0 | |||
| 8d959b7c63 | |||
| 02c4020228 | |||
| bc41cc688f | |||
| 7d481c179f | |||
| 69aaea44a0 | |||
| 6c1abfe047 | |||
| 656fb4e372 | |||
| f4485f414f | |||
| 55144ad608 | |||
| 1dbfc976e8 | |||
| e26f0b35e3 | |||
| 4ee26fd54b | |||
| a718b10502 | |||
| 8a5fa51f60 | |||
| 7c5f943564 | |||
| 7938740bf3 | |||
| 319d21e662 | |||
| 3bf2487dc5 | |||
| 6917faabf5 | |||
| db4de46d88 | |||
| 9b1dd8e03d | |||
| e356bad4d1 | |||
| c19c738f52 | |||
| bbc3046687 | |||
| e4fc3bb66a | |||
| 542135c357 | |||
| 61f711ef26 | |||
| fd3309afa8 | |||
| fea56d4164 | |||
| ea8e4079bc | |||
| 0818a125c0 | |||
| 7c758b0dfc | |||
| 4ea59742d7 | |||
| b7cb10519c | |||
| 562ad740da | |||
| 92206558fb | |||
| fd6b671494 | |||
| e2c8654237 | |||
| 5012be337f | |||
| 614433cb75 | |||
| b8cd8b6f25 | |||
| f43d01bdc7 | |||
| 9979cf2bbd | |||
| 79b470acee | |||
| 0b7047cc0e | |||
| 99add1571b | |||
| b7f4373681 | |||
| 95721a8d8c | |||
| 33dc4b5121 | |||
| a04db67ea9 | |||
| 9846c062b3 | |||
| 9878f7ebc4 | |||
| 4688664b41 | |||
| 54169c714f | |||
| f76385850d | |||
| cb09531873 | |||
| 51f2a152d5 | |||
| c3880b3ec5 | |||
| 7d5542dda7 | |||
| 8eb6039052 | |||
| 64597de344 | |||
| 515daa7d3c | |||
| ced964f039 | |||
| 8a704e43fc | |||
| 847681457f | |||
| 66d8899aa7 | |||
| b0cad7ed63 | |||
| 00cc486619 | |||
| d627208c48 | |||
| 152caa3b0a | |||
| 7ed6b45149 | |||
| c66d979ba3 | |||
| fc9188a298 | |||
| ffa38b183b | |||
| f0eb4eef5a | |||
| fa928e8cd3 | |||
| c49d80628c | |||
| af523c63d5 | |||
| aabb6ba22b | |||
| 6611578ec8 | |||
| d49a363d0d | |||
| 385d1d0a5d | |||
| 00e91ec569 | |||
| 1fd0c8d48c | |||
| d34bb7e708 | |||
| a328bbdff3 | |||
| 4ecd478d82 | |||
| 40b193f1fa | |||
| 7fd5ec8661 | |||
| 84618c45eb | |||
| c0ff4e566d | |||
| ce9b21a270 | |||
| ed7c242a09 | |||
| 46872a70b1 | |||
| e9f6e51fc8 | |||
| fa5855bff5 | |||
| 16201c41dc | |||
| 208e1719ad | |||
| a3d1f1a514 | |||
| 3e1c0d90c5 | |||
| b7197bcaae | |||
| 8890a0f3c8 | |||
| 5be412dabe | |||
| 412ec5b789 | |||
| b0e767c4a7 | |||
| 6b5b4a681d | |||
| 88bb61f287 | |||
| e169e3b996 | |||
| aa8957dd05 | |||
| 8e5731aee7 | |||
| 0cc35b6188 | |||
| 5cf7fba1a4 | |||
| e67506ee68 | |||
| f2c205a008 | |||
| c84c625585 | |||
| 9673ad3774 | |||
| 66aca428ba | |||
| ec87811420 | |||
| 776dc1e0d9 | |||
| beea2f1f30 | |||
| 5f9c683abf | |||
| 0ce4d90779 | |||
| 1047c9bd05 | |||
| 1d7cc2c242 | |||
| 23c8d15847 | |||
| da85710894 | |||
| 0ae467d3e2 | |||
| 4442b1304e | |||
| 83ab61e261 | |||
| 2504754325 | |||
| be491abc46 | |||
| 31189deb3b | |||
| 90cd3f8ea5 | |||
| 2f95a5d452 | |||
| eb1d834dd4 | |||
| 3313decb68 | |||
| 191a076956 | |||
| 0f2dcba7b3 | |||
| 94b8214529 | |||
| 421ef04efb | |||
| f7687c43aa | |||
| 5a7bab8808 | |||
| 3a73e64557 | |||
| 9f8340a970 | |||
| 0f6873cf02 | |||
| f2cd4d12e8 | |||
| 7e6a54ac62 | |||
| 7156afca9e | |||
| 87ac804642 | |||
| edcb11f9f6 | |||
| 3568254593 | |||
| 3bf4c06c99 | |||
| f1d9e7b75e | |||
| e4d53daa02 | |||
| 98a3047b58 | |||
| ba85c82fbb | |||
| 7b6ef65fe7 | |||
| de032cddd2 | |||
| 51c6b972bf | |||
| 4cd151faf5 | |||
| 5342d392aa | |||
| f94af9f61e | |||
| 7ada00790c | |||
| dfa713040a | |||
| 82dc14b741 | |||
| d3f2237d44 | |||
| fc2ed2bdd0 | |||
| e0cf74e638 | |||
| 6f6afb0515 | |||
| 397b90753c | |||
| 3c2639ad55 | |||
| 371c468e8e | |||
| 0a40219680 | |||
| c981f14e61 | |||
| 763b9d7d10 | |||
| 55fc7b904a | |||
| 17c42810d0 | |||
| 064d001550 | |||
| a4cd3bb77c | |||
| 8d3f19bc84 | |||
| d90f1947d9 | |||
| 11567c62d4 | |||
| 4619207f99 | |||
| 99587fe073 | |||
| eb0ce8a298 | |||
| 4ae4ef5e12 | |||
| d7460b945e | |||
| 638bc17eeb | |||
| 1113f9ec0c | |||
| 019331e8af | |||
| 4c8c048c49 | |||
| 08ee1916f5 | |||
| 141a70f401 | |||
| a79edf416c | |||
| 2511aefac0 | |||
| 4978065c9c | |||
| 78a26fec13 | |||
| 9cad8bd0c8 | |||
| 9a54ee4241 | |||
| 46d5ea8cf6 | |||
| fb952a722a | |||
| 7e46c1766d | |||
| 1f0595438e | |||
| d594342642 | |||
| 99da328453 | |||
| 387e1340bf | |||
| b1ed8a46b7 | |||
| 35af33ead7 | |||
| 4bd1b9e53d | |||
| 80ee343e6d | |||
| 1d59a960bc | |||
| 5ca2e1322c | |||
| 5b0b35f9e4 | |||
| 9a3d0683b3 | |||
| d155e31b64 | |||
| 45cacda7b3 | |||
| c25fe609a9 | |||
| 6d23809218 | |||
| 024eceb476 | |||
| 0fd90c49cc | |||
| d1efffe96b | |||
| 8e7ea92d44 | |||
| 2f2d62cc0c | |||
| d396abb690 | |||
| b815d3f760 | |||
| 9c46ac4eed | |||
| b2e96fc35a | |||
| fa5df4f524 | |||
| 09db7ead50 | |||
| 21a4c14e86 | |||
| b716cf1afd | |||
| 586023b45d | |||
| a00a51e70e | |||
| 745b5dfb81 | |||
| 9c50d47b82 | |||
| 6c63a0d6e7 | |||
| 2a0a7722f9 | |||
| 1460877c28 | |||
| 0739775719 | |||
| 8d3b5a58d1 | |||
| 0d2a3ce532 | |||
| d2b3a5486c | |||
| 536086988c | |||
| 684543b7f1 | |||
| bd8e47af80 | |||
| 5b34713d41 | |||
| f1aea13c51 | |||
| 7bcabe53ca | |||
| 7de3d4be2b | |||
| 02ecab833f | |||
| 1322f404a6 | |||
| 315cd4059f | |||
| 078dd9ff52 | |||
| e5029c63d1 | |||
| dcffbc49c4 | |||
| 6a2a587dff | |||
| 3a76a49ddf | |||
| 81a1114ee5 | |||
| 30e192e7e8 | |||
| a965882a34 | |||
| f967c6919a | |||
| 2e926789f5 | |||
| 38c3d0852e | |||
| 85f2ef40f8 | |||
| ae4ead8690 | |||
| ebebdd5651 | |||
| 1778d92858 | |||
| 86f8778748 | |||
| 2523772b5d | |||
| 31d57fd3d1 | |||
| d653088cbe | |||
| cd8471acae | |||
| b64fc5484a | |||
| 38f8cf7bee | |||
| f484dddbc2 | |||
| a2d0f79be3 | |||
| 792274e86f | |||
| 06aa88abfb | |||
| ad7297a539 | |||
| 4d82f42d61 | |||
| 9c41011e17 |
+207
-41
@@ -18,14 +18,12 @@ commands:
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
llvm \
|
||||
python3 \
|
||||
llvm<<parameters.llvm>>-dev \
|
||||
clang<<parameters.llvm>> \
|
||||
libclang<<parameters.llvm>>-dev \
|
||||
lld<<parameters.llvm>> \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -42,61 +40,229 @@ commands:
|
||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||
rm node-v10.15.1-linux-x64.tar.xz
|
||||
dep:
|
||||
llvm-source-linux:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-v5
|
||||
- run:
|
||||
name: "Install Go dependencies"
|
||||
command: |
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure --vendor-only
|
||||
smoketest:
|
||||
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 -size short -o blinky2 examples/blinky2
|
||||
- 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=arduino examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=digispark 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
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm7-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-v5
|
||||
paths:
|
||||
- llvm-project
|
||||
test-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- apt-dependencies:
|
||||
llvm: "-7"
|
||||
llvm: "-8"
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- dep
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- run: go install .
|
||||
- run: make test
|
||||
- run: go test -v ./transform .
|
||||
- run: make gen-device -j4
|
||||
- smoketest
|
||||
- run: make smoketest RISCV=0
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-linux-v7
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
sudo apt-get install cmake clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
# hack ninja to use less jobs
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-linux-v7
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
||||
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
||||
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- run:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-linux-ubuntu14.tar.gz
|
||||
- run: make smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.12.5.darwin-amd64.tar.gz -o go1.12.5.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.12.5.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-macos-v5
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-macos-v5
|
||||
paths:
|
||||
- llvm-project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-macos-v6
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-macos-v6
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- run:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm8-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
test-llvm8-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.12-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
|
||||
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm7-go111
|
||||
- test-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- build-linux
|
||||
- build-macos
|
||||
|
||||
+6
-2
@@ -5,8 +5,12 @@ src/device/avr/*.ld
|
||||
src/device/avr/*.s
|
||||
src/device/nrf/*.go
|
||||
src/device/nrf/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/sam/*.go
|
||||
src/device/sam/*.s
|
||||
src/device/sifive/*.go
|
||||
src/device/sifive/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
vendor
|
||||
llvm
|
||||
llvm-build
|
||||
|
||||
+2
-2
@@ -9,8 +9,8 @@
|
||||
url = https://github.com/avr-rust/avr-mcu.git
|
||||
[submodule "lib/cmsis-svd"]
|
||||
path = lib/cmsis-svd
|
||||
url = https://github.com/posborne/cmsis-svd
|
||||
url = https://github.com/tinygo-org/cmsis-svd
|
||||
[submodule "lib/compiler-rt"]
|
||||
path = lib/compiler-rt
|
||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||
branch = release_70
|
||||
branch = release_80
|
||||
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
language: go
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
go: "1.11"
|
||||
env: PATH="/usr/local/opt/llvm/bin:$PATH"
|
||||
before_install:
|
||||
- mkdir -p /Users/travis/gopath/bin
|
||||
|
||||
addons:
|
||||
homebrew:
|
||||
update: true
|
||||
taps: ArmMbed/homebrew-formulae
|
||||
packages:
|
||||
- llvm@7
|
||||
- qemu
|
||||
- arm-none-eabi-gcc
|
||||
|
||||
install:
|
||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
- dep ensure --vendor-only
|
||||
|
||||
script:
|
||||
- go install github.com/tinygo-org/tinygo
|
||||
- go test -v .
|
||||
- make gen-device
|
||||
- tinygo build -size short -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
||||
- tinygo build -size short -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
||||
- tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||
- tinygo build -size short -o test.nrf.elf -target=pca10040 examples/test
|
||||
- tinygo build -size short -o blinky1.nrf51.elf -target=microbit examples/echo
|
||||
- tinygo build -size short -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||
- tinygo build -size short -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
||||
- tinygo build -size short -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||
- tinygo build -size short -o blinky1.reel.elf -target=reelboard examples/blinky1
|
||||
- tinygo build -size short -o blinky2.reel.elf -target=reelboard examples/blinky2
|
||||
- tinygo build -size short -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
||||
- tinygo build -size short -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
||||
- tinygo build -size short -o blinky1.samd21.elf -target=itsybitsy-m0 examples/blinky1
|
||||
+30
-61
@@ -2,9 +2,9 @@
|
||||
|
||||
TinyGo depends on LLVM and libclang, which are both big C++ libraries. It can
|
||||
also optionally use a built-in lld to ease cross compiling. There are two ways
|
||||
these can be linked: dynamically and statically. The default is dynamic linking
|
||||
because it is fast and works almost out of the box on Debian-based systems with
|
||||
the right libraries installed.
|
||||
these can be linked: dynamically and statically. An install with `go install` is
|
||||
dynamic linking because it is fast and works almost out of the box on
|
||||
Debian-based systems with the right packages installed.
|
||||
|
||||
This guide describes how to statically link TinyGo against LLVM, libclang and
|
||||
lld so that the binary can be easily moved between systems. It also shows how to
|
||||
@@ -16,83 +16,54 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||
|
||||
* Go (1.11+)
|
||||
* [dep](https://golang.github.io/dep/)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git or subversion
|
||||
* git
|
||||
* CMake
|
||||
* [Ninja](https://ninja-build.org/) or make (preferably Ninja)
|
||||
* [Ninja](https://ninja-build.org/)
|
||||
|
||||
The rest of this guide assumes you're running Linux, but it should be equivalent
|
||||
on a different system like Mac.
|
||||
|
||||
## Download the source
|
||||
|
||||
The first step is to get the source code. Place it in some directory, assuming
|
||||
`$HOME/src` here, but you can pick a different one of course:
|
||||
The first step is to download the TinyGo sources (use `--recursive` if you clone
|
||||
the git repository). Then, inside the directory, download the LLVM source:
|
||||
|
||||
git clone -b release_70 https://github.com/llvm-mirror/llvm.git $HOME/src/llvm
|
||||
git clone -b release_70 https://github.com/llvm-mirror/clang.git $HOME/src/llvm/tools/clang
|
||||
git clone -b release_70 https://github.com/llvm-mirror/lld.git $HOME/src/llvm/tools/lld
|
||||
go get -d github.com/tinygo-org/tinygo
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
dep ensure -vendor-only # download dependencies
|
||||
make llvm-source
|
||||
|
||||
Note that Clang and LLD must be placed inside the tools subdirectory of LLVM to
|
||||
be automatically built with the rest of the system.
|
||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||
covered by this guide.
|
||||
|
||||
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
|
||||
Go below 1.13), make sure that Go modules are enabled:
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
## Build LLVM, Clang, LLD
|
||||
|
||||
Building LLVM is quite easy compared to some other software packages. However,
|
||||
the default configuration is _not_ optimized for distribution. It is optimized
|
||||
for development, meaning that binaries produce accurate error messages at the
|
||||
cost of huge binaries and slow compiles.
|
||||
|
||||
Before configuring, you may want to set the following environment variables to
|
||||
speed up the build. Most Linux distributions ship with GCC as the default
|
||||
compiler, but Clang is significantly faster and uses much less memory while
|
||||
producing binaries that are about as fast.
|
||||
Before starting the build, you may want to set the following environment
|
||||
variables to speed up the build. Most Linux distributions ship with GCC as the
|
||||
default compiler, but Clang is significantly faster and uses much less memory
|
||||
while producing binaries that are about as fast.
|
||||
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
|
||||
Make a build directory. LLVM requires out-of-tree builds:
|
||||
The Makefile includes a default configuration that is good for most users. It
|
||||
builds a release version of LLVM (optimized, no asserts) and includes all
|
||||
targets supported by TinyGo:
|
||||
|
||||
mkdir $HOME/src/llvm-build
|
||||
cd $HOME/src/llvm-build
|
||||
|
||||
Configure LLVM with CMake:
|
||||
|
||||
cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;WebAssembly" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON
|
||||
|
||||
You can also choose a different build system than Ninja, but Ninja is fast.
|
||||
|
||||
There are various options you can tune here, but the options given above are
|
||||
preferable for releases. Here is what they do:
|
||||
|
||||
* `LLVM_TARGETS_TO_BUILD` and `LLVM_EXPERIMENTAL_TARGETS_TO_BUILD`: the
|
||||
targets that are natively supported by the LLVM code generators. The targets
|
||||
listed here are the ones supported by TinyGo. Note that LLVM is a cross
|
||||
compiler by default, unlike some other compilers.
|
||||
* `CMAKE_BUILD_TYPE`: the default is Debug, which produces large inefficient
|
||||
binaries that are easy to debug. We want small and fast binaries.
|
||||
* `LLVM_ENABLE_ASSERTIONS`: the default is ON, which greatly slows down LLVM
|
||||
and is only really useful during development. Disable them here.
|
||||
* `LIBCLANG_BUILD_STATIC`: unlike LLVM, libclang is built as a shared library
|
||||
by default. We want a static library for easy distribution.
|
||||
|
||||
Now build it:
|
||||
|
||||
ninja # or make, if you choose make in the previous step
|
||||
make llvm-build
|
||||
|
||||
This can take over an hour depending on the speed of your system.
|
||||
|
||||
## Build TinyGo
|
||||
|
||||
Now that you have a working version of LLVM, build TinyGo using it. You need to
|
||||
specify the directories to the LLVM build directory and to the Clang and LLD source.
|
||||
The last step of course is to build TinyGo itself. This can again be done with
|
||||
make:
|
||||
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
make static LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
||||
make
|
||||
|
||||
## Verify TinyGo
|
||||
|
||||
@@ -109,14 +80,12 @@ The result should not contain libclang or libLLVM.
|
||||
|
||||
## Make a release tarball
|
||||
|
||||
Now that we have a working static build, it's time to make a release tarball.
|
||||
This is just a slight change from the command to build TinyGo:
|
||||
Now that we have a working static build, it's time to make a release tarball:
|
||||
|
||||
cd $HOME/go/src/github.com/tinygo-org/tinygo
|
||||
make release LLVM_BUILDDIR=$HOME/src/llvm-build CLANG_SRC=$HOME/src/llvm/tools/clang LLD_SRC=$HOME/src/llvm/tools/lld
|
||||
make release
|
||||
|
||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||
the following command:
|
||||
the following command (for example in ~/lib):
|
||||
|
||||
tar -xvf path/to/release.tar.gz
|
||||
|
||||
|
||||
+180
@@ -1,3 +1,183 @@
|
||||
0.8.0
|
||||
---
|
||||
* **command line**
|
||||
- fix parsing of beta Go versions
|
||||
- check the major/minor installed version of Go before compiling
|
||||
- validate `-target` flag better to not panic on an invalid target
|
||||
* **compiler**
|
||||
- implement full slice expression: `s[:2:4]`
|
||||
- fix a crash when storing a linked list in an interface
|
||||
- fix comparing struct types by making type IDs more unique
|
||||
- fix some bugs in IR generation
|
||||
- add support for linked lists in reflect data
|
||||
- implement `[]rune` to string conversion
|
||||
- implement support for `go` on func values
|
||||
* **standard library**
|
||||
- `reflect`: add support for named types
|
||||
- `reflect`: add support for `t.Bits()`
|
||||
- `reflect`: add basic support for `t.AssignableTo()`
|
||||
- `reflect`: implement `t.Align()`
|
||||
- `reflect`: add support for struct types
|
||||
- `reflect`: fix bug in `v.IsNil` and `v.Pointer` for addressable values
|
||||
- `reflect`: implement support for array types
|
||||
- `reflect`: implement `t.Comparable()`
|
||||
- `runtime`: implement stack-based scheduler
|
||||
- `runtime`: fix bug in the sleep queue of the scheduler
|
||||
- `runtime`: implement `memcpy` for Cortex-M
|
||||
- `testing`: implement stub `testing.B` struct
|
||||
- `testing`: add common test logging methods such as Errorf/Fatalf/Printf
|
||||
* **targets**
|
||||
- `386`: add support for linux/386 syscalls
|
||||
- `atsamd21`: make SPI pins configurable so that multiple SPI ports can be
|
||||
used
|
||||
- `atsamd21`: correct issue with invalid first reading coming from ADC
|
||||
- `atsamd21`: add support for reset-to-bootloader using 1200baud over USB-CDC
|
||||
- `atsamd21`: make pin selection more flexible for peripherals
|
||||
- `atsamd21`: fix minimum delay in `time.Sleep`
|
||||
- `atsamd51`: fix minimum delay in `time.Sleep`
|
||||
- `nrf`: improve SPI write-only speed, by making use of double buffering
|
||||
- `stm32f103`: fix SPI frequency selection
|
||||
- `stm32f103`: add machine.Pin.Get method for reading GPIO values
|
||||
- `stm32f103`: allow board specific UART usage
|
||||
- `nucleo-f103rb`: add support for NUCLEO-F103RB board
|
||||
- `itsybitsy-m4`: add support for this board with a SAMD51 family chip
|
||||
- `cortex-m`: add support for `arm.SystemReset()`
|
||||
- `gameboy-advance`: add initial support for the GameBoy Advance
|
||||
- `wasm`: add `//go:wasm-module` magic comment to set the wasm module name
|
||||
- `wasm`: add syscall/js.valueSetIndex support
|
||||
- `wasm`: add syscall/js.valueInvoke support
|
||||
|
||||
0.7.1
|
||||
---
|
||||
* **targets**
|
||||
- `atsamd21`: add support for the `-port` flag in the flash subcommand
|
||||
|
||||
0.7.0
|
||||
---
|
||||
* **command line**
|
||||
- try more locations to find Clang built-in headers
|
||||
- add support for `tinygo test`
|
||||
- build current directory if no package is specified
|
||||
- support custom .json target spec with `-target` flag
|
||||
- use zversion.go to detect version of GOROOT version
|
||||
- make initial heap size configurable for some targets (currently WebAssembly
|
||||
only)
|
||||
* **cgo**
|
||||
- add support for bitfields using generated getters and setters
|
||||
- add support for anonymous structs
|
||||
* **compiler**
|
||||
- show an error instead of panicking on duplicate function definitions
|
||||
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
|
||||
- remove `//go:volatile` support
|
||||
It has been replaced with the runtime/volatile package.
|
||||
- allow poiners in map keys
|
||||
- support non-constant syscall numbers
|
||||
- implement non-blocking selects
|
||||
- add support for the `-tags` flag
|
||||
- add support for `string` to `[]rune` conversion
|
||||
- implement a portable conservative garbage collector (with support for wasm)
|
||||
- add the `//go:noinline` pragma
|
||||
* **standard library**
|
||||
- `os`: add `os.Exit` and `syscall.Exit`
|
||||
- `os`: add several stubs
|
||||
- `runtime`: fix heap corruption in conservative GC
|
||||
- `runtime`: add support for math intrinsics where supported, massively
|
||||
speeding up some benchmarks
|
||||
- `testing`: add basic support for testing
|
||||
* **targets**
|
||||
- add support for a generic target that calls `__tinygo_*` functions for
|
||||
peripheral access
|
||||
- `arduino-nano33`: add support for this board
|
||||
- `hifive1`: add support for this RISC-V board
|
||||
- `reelboard`: add e-paper pins
|
||||
- `reelboard`: add `PowerSupplyActive` to enable voltage for on-board devices
|
||||
- `wasm`: put the stack at the start of linear memory, to detect stack
|
||||
overflows
|
||||
|
||||
0.6.0
|
||||
---
|
||||
* **command line**
|
||||
- some portability improvements
|
||||
- make `$GOROOT` more robust and configurable
|
||||
- check for Clang at the Homebrew install location as fallback
|
||||
* **compiler driver**
|
||||
- support multiple variations of LLVM commands, for non-Debian distributions
|
||||
* **compiler**
|
||||
- improve code quality in multiple ways
|
||||
- make panic configurable, adding trap on panic
|
||||
- refactor many internal parts of the compiler
|
||||
- print all errors encountered during compilation
|
||||
- implement calling function values of a named type
|
||||
- implement returning values from blocking functions
|
||||
- allow larger-than-int values to be sent across a channel
|
||||
- implement complex arithmetic
|
||||
- improve hashmap support
|
||||
- add debuginfo for function arguments
|
||||
- insert nil checks on stores (increasing code size)
|
||||
- implement volatile operations as compiler builtins
|
||||
- add `//go:inline` pragma
|
||||
- add build tags for the Go stdlib version
|
||||
* **cgo**
|
||||
- implement `char`, `enum` and `void*` types
|
||||
- support `#include` for builtin headers
|
||||
- improve typedef/struct/enum support
|
||||
- only include symbols that are necessary, for broader support
|
||||
- mark external function args as `nocapture`
|
||||
- implement support for some `#define` constants
|
||||
- implement support for multiple CGo files in a single package
|
||||
- **standard library**
|
||||
- `machine`: remove microbit matrix (moved to drivers repository)
|
||||
- `machine`: refactor pins to use `Pin` type instead of `GPIO`
|
||||
- `runtime`: print more interface types on panic, including `error`
|
||||
* **targets**
|
||||
- `arm`: print an error on HardFault (including stack overflows)
|
||||
- `atsamd21`: fix a bug in the ADC peripheral
|
||||
- `atsamd21`: add support for I2S
|
||||
- `feather-m0`: add support for this board
|
||||
- `nrf51`: fix a bug in I2C
|
||||
- `stm32f103xx`: fix a bug in I2C
|
||||
- `syscall`: implement `Exit` on unix
|
||||
- `trinket-m0`: add support for this board
|
||||
- `wasm`: make _main_ example smaller
|
||||
- `wasm`: don't cache wasm file in the server, for ease of debugging
|
||||
- `wasm`: work around bug #41508 that caused a deadlock while linking
|
||||
- `wasm`: add support for `js.FuncOf`
|
||||
|
||||
0.5.0
|
||||
---
|
||||
- **compiler driver**
|
||||
- use `wasm-ld` instead of `wasm-ld-8` on macOS
|
||||
- drop dependency on `llvm-ar`
|
||||
- fix linker script includes when running outside `TINYGOROOT`
|
||||
- **compiler**
|
||||
- switch to LLVM 8
|
||||
- add support for the Go 1.12 standard library (Go 1.11 is still supported)
|
||||
- work around lack of escape analysis due to nil checks
|
||||
- implement casting named structs and pointers to them
|
||||
- fix int casting to use the source signedness
|
||||
- fix some bugs around `make([]T, …)` with uncommon index types
|
||||
- some other optimizations
|
||||
- support interface asserts in interp for "math/rand" support
|
||||
- resolve all func value targets at compile time (wasm-only at the moment)
|
||||
- **cgo**
|
||||
- improve diagnostics
|
||||
- implement C `struct`, `union`, and arrays
|
||||
- fix CGo-related crash in libclang
|
||||
- implement `C.struct_` types
|
||||
- **targets**
|
||||
- all baremetal: pretend to be linux/arm instead of js/wasm
|
||||
- `avr`: improve `uintptr` support
|
||||
- `cortexm`: implement memmove intrinsic generated by LLVM
|
||||
- `cortexm`: use the lld linker instead of `arm-none-eabi-ld`
|
||||
- `darwin`: use custom syscall package that links to libSystem.dylib
|
||||
- `microbit`: add blink example
|
||||
- `samd21`: support I2C1
|
||||
- `samd21`: machine/atsamd21: correct pad/pin handling when using both UART
|
||||
and USBCDC interfaces at same time
|
||||
- `stm32f4discovery`: add support for this board
|
||||
- `wasm`: support async func values
|
||||
- `wasm`: improve documentation and add extra example
|
||||
|
||||
0.4.1
|
||||
---
|
||||
- **compiler**
|
||||
|
||||
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||
|
||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||
|
||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||
|
||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||
|
||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
||||
|
||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
|
||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# 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>
|
||||
+37
-37
@@ -1,49 +1,49 @@
|
||||
# TinyGo base stage just installs LLVM 7 and the TinyGo compiler itself.
|
||||
FROM golang:latest AS tinygo-base
|
||||
# TinyGo base stage installs Go 1.12, LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:1.12 AS tinygo-base
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-7-dev libclang-7-dev git
|
||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
COPY . /tinygo
|
||||
|
||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||
# after copying the tinygo directory in the previous step.
|
||||
RUN cd /tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
git submodule update --init
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/tinygo-org/tinygo/
|
||||
RUN cd /tinygo/ && \
|
||||
go install /tinygo/
|
||||
|
||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||
FROM tinygo-base AS tinygo-wasm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-7 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-8 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y libllvm7 lld-7
|
||||
apt-get install -y libllvm8 lld-8
|
||||
|
||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
@@ -51,32 +51,32 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
FROM tinygo-base AS tinygo-arm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 && \
|
||||
apt-get install -y apt-utils python3 make clang-8 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-arm-none-eabi clang-7 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 && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get remove -y python3 && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
|
||||
Generated
-42
@@ -1,42 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[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 = "3744606dbb67b99c60d3f11cb10bd3f9e6dad472"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a6a25fd8906c74978f1ed811bc9fd3422da8093be863b458874b02a782b6ae3e"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d5f730401f5069618b275a5241c6417eb0c38a65"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"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
@@ -1,11 +0,0 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -1,5 +1,8 @@
|
||||
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
@@ -3,92 +3,45 @@
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
.PHONY: all tinygo static run-test run-blinky run-blinky2 clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm-project/clang
|
||||
LLD_SRC ?= llvm-project/lld
|
||||
|
||||
TARGET ?= unix
|
||||
|
||||
ifeq ($(TARGET),unix)
|
||||
# Regular *nix system.
|
||||
|
||||
else ifeq ($(TARGET),pca10040)
|
||||
# PCA10040: nRF52832 development board
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),microbit)
|
||||
# BBC micro:bit
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),reelboard)
|
||||
# reel board
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),bluepill)
|
||||
# "blue pill" development board
|
||||
# See: https://wiki.stm32duino.com/index.php?title=Blue_Pill
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else ifeq ($(TARGET),arduino)
|
||||
OBJCOPY = avr-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
|
||||
else
|
||||
$(error Unknown target)
|
||||
|
||||
endif
|
||||
.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
|
||||
|
||||
CLANG_LIBS = -Wl,--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 -Wl,--end-group -lstdc++
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
LLD_LIBS = -Wl,--start-group -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML -Wl,--end-group
|
||||
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++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
|
||||
|
||||
# For static linking.
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||
|
||||
|
||||
|
||||
run-test: build/test
|
||||
./build/test
|
||||
|
||||
run-blinky: run-blinky2
|
||||
run-blinky2: build/blinky2
|
||||
./build/blinky2
|
||||
|
||||
ifeq ($(TARGET),pca10040)
|
||||
flash-%: build/%.hex
|
||||
nrfjprog -f nrf52 --sectorerase --program $< --reset
|
||||
else ifeq ($(TARGET),microbit)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
|
||||
else ifeq ($(TARGET),reelboard)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/cmsis-dap.cfg -f target/nrf51.cfg -c 'program $< reset exit'
|
||||
else ifeq ($(TARGET),arduino)
|
||||
flash-%: build/%.hex
|
||||
avrdude -c arduino -p atmega328p -P /dev/ttyACM0 -U flash:w:$<
|
||||
else ifeq ($(TARGET),bluepill)
|
||||
flash-%: build/%.hex
|
||||
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program $< reset exit'
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config)","")
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
||||
fmt:
|
||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./src/os ./src/runtime ./src/sync
|
||||
@go fmt ./testdata/*.go
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||
|
||||
test:
|
||||
@go test -v .
|
||||
|
||||
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:
|
||||
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||
@@ -103,27 +56,99 @@ gen-device-sam:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
go fmt ./src/device/sam
|
||||
|
||||
gen-device-sifive:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/ --source=https://github.com/AdaCore/svd2ada/tree/master/CMSIS-SVD/SiFive-Community
|
||||
go fmt ./src/device/sifive
|
||||
|
||||
gen-device-stm32:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
go fmt ./src/device/stm32
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
llvm-project/README.md:
|
||||
git clone -b release/8.x https://github.com/llvm/llvm-project
|
||||
llvm-source: llvm-project/README.md
|
||||
|
||||
# Configure LLVM.
|
||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;RISCV" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
@mkdir -p build
|
||||
go build -o build/tinygo .
|
||||
|
||||
static:
|
||||
@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
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||
|
||||
release: static gen-device
|
||||
test:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm ./transform .
|
||||
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && tinygo test
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
# test all examples
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/adc
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinkm
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/button
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/button2
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/echo
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/mcp3008
|
||||
tinygo build -size short -o test.elf -target=microbit examples/microbit-blink
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/pwm
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/serial
|
||||
tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||
# test all targets/boards
|
||||
tinygo build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||
tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=feather-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=trinket-m0 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||
tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
tinygo build -size short -o test.elf -target=gameboy-advance examples/gba-display
|
||||
tinygo build -size short -o test.elf -target=itsybitsy-m4 examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=nucleo-f103rb examples/blinky1
|
||||
ifneq ($(AVR), 0)
|
||||
tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
endif
|
||||
ifneq ($(RISCV), 0)
|
||||
tinygo build -size short -o test.elf -target=hifive1b examples/blinky1
|
||||
endif
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
|
||||
release: build/tinygo gen-device
|
||||
@mkdir -p build/release/tinygo/bin
|
||||
@mkdir -p build/release/tinygo/lib/clang/include
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||
@mkdir -p build/release/tinygo/lib/nrfx
|
||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@echo copying source files
|
||||
@cp -p build/tinygo build/release/tinygo/bin
|
||||
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||
@@ -136,15 +161,3 @@ release: static gen-device
|
||||
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
|
||||
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
|
||||
tar -czf build/release.tar.gz -C build/release tinygo
|
||||
|
||||
# Binary that can run on the host.
|
||||
build/%: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
|
||||
# ELF file that can run on a microcontroller.
|
||||
build/%.elf: src/examples/% src/examples/%/*.go build/tinygo src/runtime/*.go
|
||||
./build/tinygo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
|
||||
# Convert executable to Intel hex file (for flashing).
|
||||
build/%.hex: build/%.elf
|
||||
$(OBJCOPY) -O ihex $^ $@
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://travis-ci.com/tinygo-org/tinygo)
|
||||
[](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.
|
||||
|
||||
@@ -18,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
@@ -44,13 +43,20 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following microcontroller boards are currently supported:
|
||||
The following 18 microcontroller boards are currently supported:
|
||||
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC:Microbit](https://microbit.org/)
|
||||
* [BBC micro:bit](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 "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
|
||||
+5
-3
@@ -9,9 +9,11 @@ import (
|
||||
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
func cacheDir() string {
|
||||
home := getHomeDir()
|
||||
dir := filepath.Join(home, ".cache", "tinygo")
|
||||
return dir
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
}
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
|
||||
+46
-14
@@ -1,11 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -187,13 +191,13 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
if path, err := cacheLoad(outfile, commands["clang"], srcs); path != "" || err != nil {
|
||||
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
var cachepath string
|
||||
err = compileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"], srcs)
|
||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
})
|
||||
@@ -235,28 +239,56 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the
|
||||
// archive itself, which varies each run.
|
||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
}
|
||||
|
||||
// Put all builtins in an archive to link as a static library.
|
||||
// Note: this does not create a symbol index, but ld.lld doesn't seem to
|
||||
// care.
|
||||
arpath := filepath.Join(dir, "librt.a")
|
||||
cmd := exec.Command(commands["ar"], append([]string{"cr", arpath}, objs...)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
arfile, err := os.Create(arpath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to make static library", arpath, err}
|
||||
return err
|
||||
}
|
||||
defer arfile.Close()
|
||||
arwriter := ar.NewWriter(arfile)
|
||||
err = arwriter.WriteGlobalHeader()
|
||||
if err != nil {
|
||||
return &os.PathError{"write ar header", arpath, err}
|
||||
}
|
||||
for _, objpath := range objs {
|
||||
name := filepath.Base(objpath)
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer objfile.Close()
|
||||
st, err := objfile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arwriter.WriteHeader(&ar.Header{
|
||||
Name: name,
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Mode: 0644,
|
||||
Size: st.Size(),
|
||||
})
|
||||
n, err := io.Copy(arwriter, objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + arpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Give the caller the resulting file. The callback must copy the file,
|
||||
// because after it returns the temporary directory will be removed.
|
||||
arfile.Close()
|
||||
return callback(arpath)
|
||||
}
|
||||
|
||||
+997
@@ -0,0 +1,997 @@
|
||||
// Package cgo implements CGo by modifying a loaded AST. It does this by parsing
|
||||
// the `import "C"` statements found in the source code with libclang and
|
||||
// generating stub function and global declarations.
|
||||
//
|
||||
// There are a few advantages to modifying the AST directly instead of doing CGo
|
||||
// as a preprocessing step, with the main advantage being that debug information
|
||||
// is kept intact as much as possible.
|
||||
package cgo
|
||||
|
||||
// This file extracts the `import "C"` statement from the source and modifies
|
||||
// the AST for CGo. It does not use libclang directly: see libclang.go for the C
|
||||
// source file parsing.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// cgoPackage holds all CGo-related information of a package.
|
||||
type cgoPackage struct {
|
||||
generated *ast.File
|
||||
generatedPos token.Pos
|
||||
errors []error
|
||||
dir string
|
||||
fset *token.FileSet
|
||||
tokenFiles map[string]*token.File
|
||||
missingSymbols map[string]struct{}
|
||||
constants map[string]constantInfo
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
// and declared in the Go AST.
|
||||
type constantInfo struct {
|
||||
expr *ast.BasicLit
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a CGo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// elaboratedTypeInfo contains some information about an elaborated type
|
||||
// (struct, union) found in the C AST.
|
||||
type elaboratedTypeInfo struct {
|
||||
typeExpr *ast.StructType
|
||||
pos token.Pos
|
||||
bitfields []bitfieldInfo
|
||||
}
|
||||
|
||||
// bitfieldInfo contains information about a single bitfield in a struct. It
|
||||
// keeps information about the start, end, and the special (renamed) base field
|
||||
// of this bitfield.
|
||||
type bitfieldInfo struct {
|
||||
field *ast.Field
|
||||
name string
|
||||
pos token.Pos
|
||||
startBit int64
|
||||
endBit int64 // may be 0 meaning "until the end of the field"
|
||||
}
|
||||
|
||||
// enumInfo contains information about an enum in the C.
|
||||
type enumInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// globalInfo contains information about a declared global variable in C.
|
||||
type globalInfo struct {
|
||||
typeExpr ast.Expr
|
||||
pos token.Pos
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
// in both languages. See addTypeAliases.
|
||||
var cgoAliases = map[string]string{
|
||||
"C.int8_t": "int8",
|
||||
"C.int16_t": "int16",
|
||||
"C.int32_t": "int32",
|
||||
"C.int64_t": "int64",
|
||||
"C.uint8_t": "uint8",
|
||||
"C.uint16_t": "uint16",
|
||||
"C.uint32_t": "uint32",
|
||||
"C.uint64_t": "uint64",
|
||||
"C.uintptr_t": "uintptr",
|
||||
}
|
||||
|
||||
// builtinAliases are handled specially because they only exist on the Go side
|
||||
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
|
||||
var builtinAliases = map[string]struct{}{
|
||||
"char": struct{}{},
|
||||
"schar": struct{}{},
|
||||
"uchar": struct{}{},
|
||||
"short": struct{}{},
|
||||
"ushort": struct{}{},
|
||||
"int": struct{}{},
|
||||
"uint": struct{}{},
|
||||
"long": struct{}{},
|
||||
"ulong": struct{}{},
|
||||
"longlong": struct{}{},
|
||||
"ulonglong": struct{}{},
|
||||
}
|
||||
|
||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||
// type.
|
||||
const cgoTypes = `
|
||||
typedef char _Cgo_char;
|
||||
typedef signed char _Cgo_schar;
|
||||
typedef unsigned char _Cgo_uchar;
|
||||
typedef short _Cgo_short;
|
||||
typedef unsigned short _Cgo_ushort;
|
||||
typedef int _Cgo_int;
|
||||
typedef unsigned int _Cgo_uint;
|
||||
typedef long _Cgo_long;
|
||||
typedef unsigned long _Cgo_ulong;
|
||||
typedef long long _Cgo_longlong;
|
||||
typedef unsigned long long _Cgo_ulonglong;
|
||||
`
|
||||
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files. If there is one or more error, it returns these in the []error slice
|
||||
// but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
tokenFiles: map[string]*token.File{},
|
||||
missingSymbols: map[string]struct{}{},
|
||||
constants: map[string]constantInfo{},
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||
enums: map[string]enumInfo{},
|
||||
}
|
||||
|
||||
// Add a new location for the following file.
|
||||
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||
generatedTokenPos.SetLines([]int{0})
|
||||
p.generatedPos = generatedTokenPos.Pos(0)
|
||||
|
||||
// Construct a new in-memory AST for CGo declarations of this package.
|
||||
unsafeImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
ValuePos: p.generatedPos,
|
||||
Kind: token.STRING,
|
||||
Value: "\"unsafe\"",
|
||||
},
|
||||
EndPos: p.generatedPos,
|
||||
}
|
||||
p.generated = &ast.File{
|
||||
Package: p.generatedPos,
|
||||
Name: &ast.Ident{
|
||||
NamePos: p.generatedPos,
|
||||
Name: files[0].Name.Name,
|
||||
},
|
||||
Decls: []ast.Decl{
|
||||
&ast.GenDecl{
|
||||
TokPos: p.generatedPos,
|
||||
Tok: token.IMPORT,
|
||||
Specs: []ast.Spec{
|
||||
unsafeImport,
|
||||
},
|
||||
},
|
||||
},
|
||||
Imports: []*ast.ImportSpec{unsafeImport},
|
||||
}
|
||||
|
||||
// Find all C.* symbols.
|
||||
for _, f := range files {
|
||||
astutil.Apply(f, p.findMissingCGoNames, nil)
|
||||
}
|
||||
for name := range builtinAliases {
|
||||
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
for _, f := range files {
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
|
||||
pos := genDecl.Pos()
|
||||
if genDecl.Doc != nil {
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(fset, f)
|
||||
}
|
||||
|
||||
// Declare functions found by libclang.
|
||||
p.addFuncDecls()
|
||||
|
||||
// Declare stub function pointer values found by libclang.
|
||||
p.addFuncPtrDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
p.addConstDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
p.addVarDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
p.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typedef in C.
|
||||
p.addTypedefs()
|
||||
|
||||
// Add elaborated types for C structs and unions.
|
||||
p.addElaboratedTypes()
|
||||
|
||||
// Add enum types and enum constants for C enums.
|
||||
p.addEnumTypes()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
for _, f := range files {
|
||||
astutil.Apply(f, p.walker, nil)
|
||||
}
|
||||
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
return p.generated, p.errors
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
// comment above the `import "C"` statement.
|
||||
func (p *cgoPackage) addFuncDecls() {
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "C." + name,
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: fn.pos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: fn.pos,
|
||||
List: args,
|
||||
Closing: fn.pos,
|
||||
},
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: arg.name,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: arg.typeExpr,
|
||||
}
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
||||
// values will later be replaced with the real values in the compiler.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.add unsafe.Pointer
|
||||
// C.mul unsafe.Pointer
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addFuncPtrDecls() {
|
||||
if len(p.functions) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.functions))
|
||||
for name := range p.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := p.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addConstDecls declares external C constants in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// const (
|
||||
// C.CONST_INT = 5
|
||||
// C.CONST_FLOAT = 5.8
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addConstDecls() {
|
||||
if len(p.constants) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.CONST,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.constants))
|
||||
for name := range p.constants {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
constVal := p.constants[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: constVal.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{constVal.expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addVarDecls declares external C globals in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.globalInt int
|
||||
// C.globalBool bool
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addVarDecls() {
|
||||
if len(p.globals) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
names := make([]string, 0, len(p.globals))
|
||||
for name := range p.globals {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := p.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: global.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: global.typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// type (
|
||||
// C.int8_t = int8
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (p *cgoPackage) addTypeAliases() {
|
||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||
for key := range cgoAliases {
|
||||
aliasKeys = append(aliasKeys, key)
|
||||
}
|
||||
sort.Strings(aliasKeys)
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
for _, typeName := range aliasKeys {
|
||||
goTypeName := cgoAliases[typeName]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: token.NoPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: p.generatedPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: token.NoPos,
|
||||
Name: goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
func (p *cgoPackage) addTypedefs() {
|
||||
if len(p.typedefs) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(p.typedefs))
|
||||
for name := range p.typedefs {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typedef := p.typedefs[name]
|
||||
typeName := "C." + name
|
||||
isAlias := true
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
typeName = "C." + name[len("_Cgo_"):]
|
||||
isAlias = false // C.short etc. should not be aliased to the equivalent Go type (not portable)
|
||||
}
|
||||
if _, ok := cgoAliases[typeName]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: typedef.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typedef.typeExpr,
|
||||
}
|
||||
if isAlias {
|
||||
typeSpec.Assign = typedef.pos
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
p.generated.Decls = append(p.generated.Decls, gen)
|
||||
}
|
||||
|
||||
// addElaboratedTypes adds C elaborated types as aliases. These are the "struct
|
||||
// foo" or "union foo" types, often used in a typedef.
|
||||
//
|
||||
// See also:
|
||||
// https://en.cppreference.com/w/cpp/language/elaborated_type_specifier
|
||||
func (p *cgoPackage) addElaboratedTypes() {
|
||||
if len(p.elaboratedTypes) == 0 {
|
||||
return
|
||||
}
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(p.elaboratedTypes))
|
||||
for name := range p.elaboratedTypes {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typ := p.elaboratedTypes[name]
|
||||
typeName := "C." + 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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// findMissingCGoNames traverses the AST and finds all C.something names. Only
|
||||
// these symbols are extracted from the parsed C AST and converted to the Go
|
||||
// equivalent.
|
||||
func (p *cgoPackage) findMissingCGoNames(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := node.Sel.Name
|
||||
if _, ok := builtinAliases[name]; ok {
|
||||
name = "_Cgo_" + name
|
||||
}
|
||||
p.missingSymbols[name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||
// used in the middle of a name) so in practice all C identifiers live in a
|
||||
// separate namespace (no _Cgo_ hacks like in gc).
|
||||
func (p *cgoPackage) walker(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := p.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: "C." + fun.Sel.Name,
|
||||
}
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := "C." + node.Sel.Name
|
||||
if _, ok := p.functions[node.Sel.Name]; ok {
|
||||
name += "$funcaddr"
|
||||
}
|
||||
cursor.Replace(&ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
+763
@@ -0,0 +1,763 @@
|
||||
package cgo
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
|
||||
// alignment etc. as void*) but does not include void* pointers that are not
|
||||
// always real pointers.
|
||||
// The Go garbage collector assumes that all non-nil pointer-typed integers are
|
||||
// actually pointers. This is not always true, as data[1] often contains 0x1,
|
||||
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
|
||||
// but occasionally it will leading to a crash with a vague error message.
|
||||
typedef struct {
|
||||
enum CXCursorKind kind;
|
||||
int xdata;
|
||||
uintptr_t data[3];
|
||||
} GoCXCursor;
|
||||
|
||||
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
|
||||
// the real functions without doing anything else, thus they are entirely
|
||||
// compatible with the versions without tinygo_ prefix. The only difference is
|
||||
// the CXCursor type, which has been replaced with GoCXCursor.
|
||||
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
|
||||
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
|
||||
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
|
||||
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
|
||||
CXType tinygo_clang_getCursorType(GoCXCursor c);
|
||||
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
|
||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
|
||||
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
|
||||
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
|
||||
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
||||
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
||||
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
||||
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
|
||||
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
||||
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// storedRefs stores references to types, used for clang_visitChildren.
|
||||
var storedRefs refMap
|
||||
|
||||
var diagnosticSeverity = [...]string{
|
||||
C.CXDiagnostic_Ignored: "ignored",
|
||||
C.CXDiagnostic_Note: "note",
|
||||
C.CXDiagnostic_Warning: "warning",
|
||||
C.CXDiagnostic_Error: "error",
|
||||
C.CXDiagnostic_Fatal: "fatal",
|
||||
}
|
||||
|
||||
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
|
||||
index := C.clang_createIndex(0, 0)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
// pretend to be a .c file
|
||||
filenameC := C.CString(posFilename + "!cgo.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
// fix up error locations
|
||||
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
unsavedFile := C.struct_CXUnsavedFile{
|
||||
Filename: filenameC,
|
||||
Length: C.ulong(len(fragment)),
|
||||
Contents: fragmentC,
|
||||
}
|
||||
|
||||
// convert Go slice of strings to C array of strings.
|
||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
defer C.free(cmdargsC)
|
||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||
for i, cflag := range cflags {
|
||||
s := C.CString(cflag)
|
||||
cmdargs[i] = s
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
var unit C.CXTranslationUnit
|
||||
errCode := C.clang_parseTranslationUnit2(
|
||||
index,
|
||||
filenameC,
|
||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
||||
var libclangFilename C.CXString
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
C.clang_getPresumedLocation(location, &libclangFilename, &line, &column)
|
||||
filename := getString(libclangFilename)
|
||||
if filepath.IsAbs(filename) {
|
||||
// Relative paths for readability, like other Go parser errors.
|
||||
relpath, err := filepath.Rel(p.dir, filename)
|
||||
if err == nil {
|
||||
filename = relpath
|
||||
}
|
||||
}
|
||||
p.errors = append(p.errors, &scanner.Error{
|
||||
Pos: token.Position{
|
||||
Filename: filename,
|
||||
Offset: 0, // not provided by clang_getPresumedLocation
|
||||
Line: int(line),
|
||||
Column: int(column),
|
||||
},
|
||||
Msg: severity + ": " + spelling,
|
||||
})
|
||||
}
|
||||
for i := 0; i < numDiagnostics; i++ {
|
||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||
addDiagnostic(diagnostic)
|
||||
|
||||
// Child diagnostics (like notes on redefinitions).
|
||||
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
||||
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
}
|
||||
|
||||
//export tinygo_clang_globals_visitor
|
||||
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
||||
kind := C.tinygo_clang_getCursorKind(c)
|
||||
pos := p.getCursorPosition(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{
|
||||
pos: pos,
|
||||
}
|
||||
p.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
if argName == "" {
|
||||
argName = "$" + strconv.Itoa(i)
|
||||
}
|
||||
fn.args = append(fn.args, paramInfo{
|
||||
name: argName,
|
||||
typeExpr: p.makeASTType(argType, pos),
|
||||
})
|
||||
}
|
||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: p.makeASTType(resultType, pos),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case C.CXCursor_StructDecl:
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols["struct_"+name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
p.makeASTType(typ, pos)
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.tinygo_clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
p.makeASTType(typedefType, pos)
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
p.globals[name] = globalInfo{
|
||||
typeExpr: p.makeASTType(cursorType, pos),
|
||||
pos: pos,
|
||||
}
|
||||
case C.CXCursor_MacroDefinition:
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if _, required := p.missingSymbols[name]; !required {
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
||||
start := C.clang_getRangeStart(sourceRange)
|
||||
end := C.clang_getRangeEnd(sourceRange)
|
||||
var file, endFile C.CXFile
|
||||
var startOffset, endOffset C.unsigned
|
||||
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
||||
if file == nil {
|
||||
panic("could not find file where macro is defined")
|
||||
}
|
||||
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
||||
if file != endFile {
|
||||
panic("expected start and end location of a #define to be in the same file")
|
||||
}
|
||||
if startOffset > endOffset {
|
||||
panic("startOffset > endOffset")
|
||||
}
|
||||
|
||||
// read file contents and extract the relevant byte range
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
||||
var size C.size_t
|
||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||
if endOffset >= C.uint(size) {
|
||||
panic("endOffset lies after end of file")
|
||||
}
|
||||
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
||||
if !strings.HasPrefix(source, name) {
|
||||
panic(fmt.Sprintf("expected #define value to start with %#v, got %#v", name, source))
|
||||
}
|
||||
value := strings.TrimSpace(source[len(name):])
|
||||
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||
}
|
||||
if len(value) == 0 {
|
||||
// Pretend it doesn't exist at all.
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
// For information about integer literals:
|
||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||
if value[0] == '"' {
|
||||
// string constant
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.STRING, value}, pos}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
if value[0] == '\'' {
|
||||
// char constant
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.CHAR, value}, pos}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
// assume it's a number (int or float)
|
||||
value = strings.Replace(value, "'", "", -1) // remove ' chars
|
||||
value = strings.TrimRight(value, "lu") // remove llu suffixes etc.
|
||||
// find the first non-number
|
||||
nonnum := byte(0)
|
||||
for i := 0; i < len(value); i++ {
|
||||
if value[i] < '0' || value[i] > '9' {
|
||||
nonnum = value[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
// determine number type based on the first non-number
|
||||
switch nonnum {
|
||||
case 0:
|
||||
// no non-number found, must be an integer
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||
case 'x', 'X':
|
||||
// hex integer constant
|
||||
// TODO: may also be a floating point number per C++17.
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.INT, value}, pos}
|
||||
case '.', 'e':
|
||||
// float constant
|
||||
value = strings.TrimRight(value, "fFlL")
|
||||
p.constants[name] = constantInfo{&ast.BasicLit{pos, token.FLOAT, value}, pos}
|
||||
default:
|
||||
// unknown type, ignore
|
||||
}
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
||||
|
||||
// getCursorPosition returns a usable token.Pos from a libclang cursor. If the
|
||||
// file for this cursor has not been seen before, it is read from libclang
|
||||
// (which already has the file in memory) and added to the token.FileSet.
|
||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
location := C.tinygo_clang_getCursorLocation(cursor)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
var offset C.unsigned
|
||||
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
||||
if line == 0 || file == nil {
|
||||
// Invalid token.
|
||||
return token.NoPos
|
||||
}
|
||||
filename := getString(C.clang_getFileName(file))
|
||||
if _, ok := p.tokenFiles[filename]; !ok {
|
||||
// File has not been seen before in this package, add line information
|
||||
// now by reading the file from libclang.
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(cursor)
|
||||
var size C.size_t
|
||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
||||
lines := []int{0}
|
||||
for i := 0; i < len(source)-1; i++ {
|
||||
if source[i] == '\n' {
|
||||
lines = append(lines, i+1)
|
||||
}
|
||||
}
|
||||
f := p.fset.AddFile(filename, -1, int(size))
|
||||
f.SetLines(lines)
|
||||
p.tokenFiles[filename] = f
|
||||
}
|
||||
return p.tokenFiles[filename].Pos(int(offset))
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
// it converts a libclang type to a type in the Go AST.
|
||||
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
var typeName string
|
||||
switch typ.kind {
|
||||
case C.CXType_Char_S, C.CXType_Char_U:
|
||||
typeName = "C.char"
|
||||
case C.CXType_SChar:
|
||||
typeName = "C.schar"
|
||||
case C.CXType_UChar:
|
||||
typeName = "C.uchar"
|
||||
case C.CXType_Short:
|
||||
typeName = "C.short"
|
||||
case C.CXType_UShort:
|
||||
typeName = "C.ushort"
|
||||
case C.CXType_Int:
|
||||
typeName = "C.int"
|
||||
case C.CXType_UInt:
|
||||
typeName = "C.uint"
|
||||
case C.CXType_Long:
|
||||
typeName = "C.long"
|
||||
case C.CXType_ULong:
|
||||
typeName = "C.ulong"
|
||||
case C.CXType_LongLong:
|
||||
typeName = "C.longlong"
|
||||
case C.CXType_ULongLong:
|
||||
typeName = "C.ulonglong"
|
||||
case C.CXType_Bool:
|
||||
typeName = "bool"
|
||||
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 4:
|
||||
typeName = "float32"
|
||||
case 8:
|
||||
typeName = "float64"
|
||||
default:
|
||||
// Don't do anything, rely on the fallback code to show a somewhat
|
||||
// sensible error message like "undeclared name: C.long double".
|
||||
}
|
||||
case C.CXType_Complex:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 8:
|
||||
typeName = "complex64"
|
||||
case 16:
|
||||
typeName = "complex128"
|
||||
}
|
||||
case C.CXType_Pointer:
|
||||
pointeeType := C.clang_getPointeeType(typ)
|
||||
if pointeeType.kind == C.CXType_Void {
|
||||
// void* type is translated to Go as unsafe.Pointer
|
||||
return &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ast.StarExpr{
|
||||
Star: pos,
|
||||
X: p.makeASTType(pointeeType, pos),
|
||||
}
|
||||
case C.CXType_ConstantArray:
|
||||
return &ast.ArrayType{
|
||||
Lbrack: pos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: pos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
||||
},
|
||||
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
|
||||
}
|
||||
case C.CXType_FunctionProto:
|
||||
// Be compatible with gc, which uses the *[0]byte type for function
|
||||
// pointer types.
|
||||
// Return type [0]byte because this is a function type, not a pointer to
|
||||
// this function type.
|
||||
return &ast.ArrayType{
|
||||
Lbrack: pos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: pos,
|
||||
Kind: token.INT,
|
||||
Value: "0",
|
||||
},
|
||||
Elt: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "byte",
|
||||
},
|
||||
}
|
||||
case C.CXType_Typedef:
|
||||
name := getString(C.clang_getTypedefName(typ))
|
||||
if _, ok := p.typedefs[name]; !ok {
|
||||
p.typedefs[name] = nil // don't recurse
|
||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
expr := p.makeASTType(underlyingType, pos)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
switch expr.Name {
|
||||
case "C.char":
|
||||
if typeSize != 1 {
|
||||
// This happens for some very special purpose architectures
|
||||
// (DSPs etc.) that are not currently targeted.
|
||||
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
||||
panic("unknown char width")
|
||||
}
|
||||
switch underlyingType.kind {
|
||||
case C.CXType_Char_S:
|
||||
expr.Name = "int8"
|
||||
case C.CXType_Char_U:
|
||||
expr.Name = "uint8"
|
||||
}
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "int8"
|
||||
case 2:
|
||||
expr.Name = "int16"
|
||||
case 4:
|
||||
expr.Name = "int32"
|
||||
case 8:
|
||||
expr.Name = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "uint8"
|
||||
case 2:
|
||||
expr.Name = "uint16"
|
||||
case 4:
|
||||
expr.Name = "uint32"
|
||||
case 8:
|
||||
expr.Name = "uint64"
|
||||
}
|
||||
}
|
||||
}
|
||||
p.typedefs[name] = &typedefInfo{
|
||||
typeExpr: expr,
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + name,
|
||||
}
|
||||
case C.CXType_Elaborated:
|
||||
underlying := C.clang_Type_getNamedType(typ)
|
||||
switch underlying.kind {
|
||||
case C.CXType_Record:
|
||||
return p.makeASTType(underlying, pos)
|
||||
case C.CXType_Enum:
|
||||
return p.makeASTType(underlying, pos)
|
||||
default:
|
||||
panic("unknown elaborated type")
|
||||
}
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
if name == "" {
|
||||
// Anonymous record, probably inside a typedef.
|
||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
||||
if bitfieldList != nil {
|
||||
// This struct has bitfields, so we have to declare it as a
|
||||
// named type (for bitfield getters/setters to work).
|
||||
p.anonStructNum++
|
||||
cgoName := "struct_" + strconv.Itoa(p.anonStructNum)
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: typeExpr,
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
return typeExpr
|
||||
} else {
|
||||
var cgoName string
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
cgoName = "struct_" + name
|
||||
case C.CXCursor_UnionDecl:
|
||||
cgoName = "union_" + name
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
typeExpr, bitfieldList := p.makeASTRecordType(cursor, pos)
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: typeExpr,
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
case C.CXType_Enum:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
|
||||
if name == "" {
|
||||
// anonymous enum
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
||||
return p.makeASTType(underlying, pos)
|
||||
} else {
|
||||
// named enum
|
||||
if _, ok := p.enums[name]; !ok {
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
||||
p.enums[name] = enumInfo{
|
||||
typeExpr: p.makeASTType(underlying, pos),
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C.enum_" + name,
|
||||
}
|
||||
}
|
||||
}
|
||||
if typeName == "" {
|
||||
// Report this as an error.
|
||||
spelling := getString(C.clang_getTypeSpelling(typ))
|
||||
p.errors = append(p.errors, scanner.Error{
|
||||
Pos: p.fset.PositionFor(pos, true),
|
||||
Msg: fmt.Sprintf("unknown C type: %v (libclang type kind %d)", spelling, typ.kind),
|
||||
})
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
// type name.
|
||||
typeName = "C." + spelling
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
}
|
||||
}
|
||||
|
||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
||||
// a Go struct type. Unions are implemented by setting the first field to a
|
||||
// zero-lengt "C union" field, which cannot be written in Go directly.
|
||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) (*ast.StructType, []bitfieldInfo) {
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
}
|
||||
var bitfieldList []bitfieldInfo
|
||||
inBitfield := false
|
||||
bitfieldNum := 0
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
inBitfield *bool
|
||||
bitfieldNum *int
|
||||
bitfieldList *[]bitfieldInfo
|
||||
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
||||
defer storedRefs.Remove(ref)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
return &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
}, bitfieldList
|
||||
case C.CXCursor_UnionDecl:
|
||||
if bitfieldList != nil {
|
||||
// This is valid C... but please don't do this.
|
||||
p.errors = append(p.errors, scanner.Error{
|
||||
Pos: p.fset.PositionFor(pos, true),
|
||||
Msg: fmt.Sprintf("bitfield in a union is not supported"),
|
||||
})
|
||||
}
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
return &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
}, bitfieldList
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
}
|
||||
}
|
||||
|
||||
//export tinygo_clang_struct_visitor
|
||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
inBitfield *bool
|
||||
bitfieldNum *int
|
||||
bitfieldList *[]bitfieldInfo
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
p := passed.pkg
|
||||
inBitfield := passed.inBitfield
|
||||
bitfieldNum := passed.bitfieldNum
|
||||
bitfieldList := passed.bitfieldList
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||
panic("expected field inside cursor")
|
||||
}
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
if name == "" {
|
||||
// Assume this is a bitfield of 0 bits.
|
||||
// Warning: this is not necessarily true!
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
pos := p.getCursorPosition(c)
|
||||
field := &ast.Field{
|
||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
||||
}
|
||||
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
|
||||
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
|
||||
bitfieldOffset := offsetof % alignOf
|
||||
if bitfieldOffset != 0 {
|
||||
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
||||
panic("expected a bitfield")
|
||||
}
|
||||
if !*inBitfield {
|
||||
*bitfieldNum++
|
||||
}
|
||||
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
|
||||
prevField := fieldList.List[len(fieldList.List)-1]
|
||||
if !*inBitfield {
|
||||
// The previous element also was a bitfield, but wasn't noticed
|
||||
// then. Add it now.
|
||||
*inBitfield = true
|
||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
||||
field: prevField,
|
||||
name: prevField.Names[0].Name,
|
||||
startBit: 0,
|
||||
pos: prevField.Names[0].NamePos,
|
||||
})
|
||||
prevField.Names[0].Name = bitfieldName
|
||||
prevField.Names[0].Obj.Name = bitfieldName
|
||||
}
|
||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
||||
prevBitfield.endBit = bitfieldOffset
|
||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
||||
field: prevField,
|
||||
name: name,
|
||||
startBit: bitfieldOffset,
|
||||
pos: pos,
|
||||
})
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
*inBitfield = false
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: name,
|
||||
Decl: field,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
//export tinygo_clang_enum_visitor
|
||||
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
pos := p.getCursorPosition(c)
|
||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
||||
p.constants[name] = constantInfo{
|
||||
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
|
||||
pos: pos,
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
// +build !byollvm
|
||||
|
||||
package loader
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-8/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-8/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm/lib -lclang -lffi
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,70 @@
|
||||
|
||||
// This file implements some small trampoline functions. The signatures
|
||||
// are slightly different from the ones defined in libclang.go, but they
|
||||
// should be ABI compatible.
|
||||
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
|
||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||
return clang_getTranslationUnitCursor(tu);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_visitChildren(CXCursor parent, CXCursorVisitor visitor, CXClientData client_data) {
|
||||
return clang_visitChildren(parent, visitor, client_data);
|
||||
}
|
||||
|
||||
CXString tinygo_clang_getCursorSpelling(CXCursor c) {
|
||||
return clang_getCursorSpelling(c);
|
||||
}
|
||||
|
||||
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
|
||||
return clang_getCursorKind(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getCursorType(CXCursor c) {
|
||||
return clang_getCursorType(c);
|
||||
}
|
||||
|
||||
CXCursor tinygo_clang_getTypeDeclaration(CXType t) {
|
||||
return clang_getTypeDeclaration(t);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(CXCursor c) {
|
||||
return clang_getTypedefDeclUnderlyingType(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getCursorResultType(CXCursor c) {
|
||||
return clang_getCursorResultType(c);
|
||||
}
|
||||
|
||||
int tinygo_clang_Cursor_getNumArguments(CXCursor c) {
|
||||
return clang_Cursor_getNumArguments(c);
|
||||
}
|
||||
|
||||
CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
|
||||
return clang_Cursor_getArgument(c, i);
|
||||
}
|
||||
|
||||
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
||||
return clang_getCursorLocation(c);
|
||||
}
|
||||
|
||||
CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
||||
return clang_getCursorExtent(c);
|
||||
}
|
||||
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||
return clang_Cursor_getTranslationUnit(c);
|
||||
}
|
||||
|
||||
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
|
||||
return clang_getEnumConstantDeclValue(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
|
||||
return clang_getEnumDeclIntegerType(c);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
||||
return clang_Cursor_isBitField(c);
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// #include <stdlib.h>
|
||||
import "C"
|
||||
|
||||
// refMap is a convenient way to store opaque references that can be passed to
|
||||
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
||||
// pointer but only a C pointer.
|
||||
type refMap struct {
|
||||
refs map[unsafe.Pointer]interface{}
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||
// be removed using Remove to avoid memory leaks.
|
||||
func (m *refMap) Put(v interface{}) unsafe.Pointer {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if m.refs == nil {
|
||||
m.refs = make(map[unsafe.Pointer]interface{}, 1)
|
||||
}
|
||||
ref := C.malloc(1)
|
||||
m.refs[ref] = v
|
||||
return ref
|
||||
}
|
||||
|
||||
// Get returns a stored value previously inserted with Put. Use the same
|
||||
// reference as you got from Put.
|
||||
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
return m.refs[ref]
|
||||
}
|
||||
|
||||
// Remove deletes a single reference from the map.
|
||||
func (m *refMap) Remove(ref unsafe.Pointer) {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
delete(m.refs, ref)
|
||||
C.free(ref)
|
||||
}
|
||||
+42
-8
@@ -1,11 +1,45 @@
|
||||
// +build !darwin
|
||||
|
||||
package main
|
||||
|
||||
// commands used by the compilation process might have different file names on Linux than those used on macOS.
|
||||
var commands = map[string]string{
|
||||
"ar": "llvm-ar-7",
|
||||
"clang": "clang-7",
|
||||
"ld.lld": "ld.lld-7",
|
||||
"wasm-ld": "wasm-ld-7",
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Commands used by the compilation process might have different file names
|
||||
// across operating systems and distributions.
|
||||
var commands = map[string][]string{
|
||||
"clang": {"clang-8"},
|
||||
"ld.lld": {"ld.lld-8", "ld.lld"},
|
||||
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add the path to a Homebrew-installed LLVM 8 for ease of use (no need to
|
||||
// manually set $PATH).
|
||||
if runtime.GOOS == "darwin" {
|
||||
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm/bin/clang-8")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm/bin/wasm-ld")
|
||||
}
|
||||
}
|
||||
|
||||
func execCommand(cmdNames []string, args ...string) error {
|
||||
for _, cmdName := range cmdNames {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||
// this command was not found, try the next
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build darwin
|
||||
|
||||
package main
|
||||
|
||||
// commands used by the compilation process might have different file names on macOS than those used on Linux.
|
||||
var commands = map[string]string{
|
||||
"ar": "llvm-ar",
|
||||
"clang": "clang-7",
|
||||
"ld.lld": "ld.lld-7",
|
||||
"wasm-ld": "wasm-ld-7",
|
||||
}
|
||||
+45
-8
@@ -42,7 +42,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("lookuppanic", nil, "")
|
||||
c.createRuntimeCall("lookupPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
@@ -51,7 +51,12 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
|
||||
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||
// sure it is within bounds.
|
||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||
//
|
||||
// This function is both used for slicing a slice (low and high have their
|
||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||
// logic is the same in both cases.
|
||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
@@ -66,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = high.Type()
|
||||
}
|
||||
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = max.Type()
|
||||
}
|
||||
if capacityType != capacity.Type() {
|
||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||
}
|
||||
@@ -85,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = c.builder.CreateZExt(max, capacityType, "")
|
||||
} else {
|
||||
max = c.builder.CreateSExt(max, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||
@@ -92,13 +107,15 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("slicepanic", nil, "")
|
||||
c.createRuntimeCall("slicePanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
@@ -109,19 +126,39 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||
// pointer dereferences exist in valid Go code.
|
||||
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||
// Check whether we need to emit this check at all.
|
||||
if !ptr.IsAGlobalValue().IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether this is a nil pointer.
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Compare against nil.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
var isnil llvm.Value
|
||||
if ptr.Type().PointerAddressSpace() == 0 {
|
||||
// Do the nil check using the isnil builtin, which marks the parameter
|
||||
// as nocapture.
|
||||
// The reason it has to go through a builtin, is that a regular icmp
|
||||
// instruction may capture the pointer in LLVM semantics, see
|
||||
// https://reviews.llvm.org/D60047 for details. Pointer capturing
|
||||
// unfortunately breaks escape analysis, so we use this trick to let the
|
||||
// functionattr pass know that this pointer doesn't really escape.
|
||||
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
|
||||
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
|
||||
} else {
|
||||
// Do the nil check using a regular icmp. This can happen with function
|
||||
// pointers on AVR, which don't benefit from escape analysis anyway.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
}
|
||||
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("nilpanic", nil, "")
|
||||
c.createRuntimeCall("nilPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
|
||||
+42
-5
@@ -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
|
||||
// paramter list.
|
||||
// parameter list.
|
||||
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
@@ -55,6 +55,25 @@ func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Expand an argument type to a list of offsets from the start of the object.
|
||||
// Used together with expandFormalParam to get the offset of each value from the
|
||||
// start of the non-expanded value.
|
||||
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := c.flattenAggregateTypeOffsets(t)
|
||||
if len(fields) <= MaxFieldsPerParam {
|
||||
return fields
|
||||
} else {
|
||||
// failed to lower
|
||||
return []uint64{0}
|
||||
}
|
||||
default:
|
||||
// TODO: split small arrays
|
||||
return []uint64{0}
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of expandFormalParamType for parameter values.
|
||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
@@ -92,6 +111,27 @@ func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
|
||||
}
|
||||
}
|
||||
|
||||
// Return the offsets from the start of the object if this object type were
|
||||
// flattened like in flattenAggregate. Used together with flattenAggregate to
|
||||
// know the start indices of each value in the non-flattened object.
|
||||
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
for fieldIndex, field := range t.StructElementTypes() {
|
||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||
for i := range suboffsets {
|
||||
suboffsets[i] += offset
|
||||
}
|
||||
fields = append(fields, suboffsets...)
|
||||
}
|
||||
return fields
|
||||
default:
|
||||
return []uint64{0}
|
||||
}
|
||||
}
|
||||
|
||||
// Break down a struct into its elementary types for argument passing. The value
|
||||
// equivalent of flattenAggregateType
|
||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
@@ -123,10 +163,7 @@ func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||
value, err := c.getZeroValue(t)
|
||||
if err != nil {
|
||||
panic("could not get zero value of struct: " + err.Error())
|
||||
}
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
|
||||
+201
-60
@@ -4,6 +4,7 @@ package compiler
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -12,86 +13,226 @@ import (
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(expr.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
if c.targetData.TypeAllocSize(valueType) > c.targetData.TypeAllocSize(c.intType) {
|
||||
// Values bigger than int overflow the data part of the coroutine.
|
||||
// TODO: make the coroutine data part big enough to hold these bigger
|
||||
// values.
|
||||
return llvm.Value{}, c.makeError(expr.Pos(), "todo: channel with values bigger than int")
|
||||
}
|
||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||
size := c.targetData.TypeAllocSize(chanType)
|
||||
chanType := c.getLLVMType(expr.Type())
|
||||
size := c.targetData.TypeAllocSize(chanType.ElementType())
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
||||
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
|
||||
ptr = c.builder.CreateBitCast(ptr, chanType, "chan")
|
||||
// Set the elementSize field
|
||||
elementSizePtr := c.builder.CreateGEP(ptr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "")
|
||||
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
||||
if elementSize > 0xffff {
|
||||
return ptr, c.makeError(expr.Pos(), fmt.Sprintf("element size is %d bytes, which is bigger than the maximum of %d bytes", elementSize, 0xffff))
|
||||
}
|
||||
elementSizeValue := llvm.ConstInt(c.ctx.Int16Type(), elementSize, false)
|
||||
c.builder.CreateStore(elementSizeValue, elementSizePtr)
|
||||
return ptr, nil
|
||||
}
|
||||
|
||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||
// channel send operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) error {
|
||||
valueType, err := c.getLLVMType(instr.Chan.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, instr.Chan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chanValue, err := c.parseExpr(frame, instr.X)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||
ch := c.getValue(frame, instr.Chan)
|
||||
chanValue := c.getValue(frame, instr.X)
|
||||
|
||||
// store value-to-send
|
||||
valueType := c.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
c.createRuntimeCall("chanSendStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueSize}, "")
|
||||
return nil
|
||||
|
||||
// Do the send.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
||||
|
||||
// End the lifetime of the alloca.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
|
||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
// actual channel receive operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) (llvm.Value, error) {
|
||||
valueType, err := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch, err := c.parseExpr(frame, unop.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
valueOk := c.builder.CreateAlloca(c.ctx.Int1Type(), "chan.comma-ok.alloca")
|
||||
c.createRuntimeCall("chanRecvStub", []llvm.Value{llvm.Undef(c.i8ptrType), ch, valueAllocaCast, valueOk, valueSize}, "")
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
ch := c.getValue(frame, unop.X)
|
||||
|
||||
// Allocate memory to receive into.
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
|
||||
// Do the receive.
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
commaOk := c.builder.CreateLoad(valueOk, "chan.comma-ok")
|
||||
commaOk := c.createRuntimeCall("getTaskStateData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
return tuple, nil
|
||||
return tuple
|
||||
} else {
|
||||
return received, nil
|
||||
return received
|
||||
}
|
||||
}
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) error {
|
||||
valueType, err := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||
return nil
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||
ch := c.getValue(frame, param)
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
// emitSelect emits all IR necessary for a select statements. That's a
|
||||
// non-trivial amount of code because select is very complex to implement.
|
||||
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
if len(expr.States) == 0 {
|
||||
// Shortcuts for some simple selects.
|
||||
llvmType := c.getLLVMType(expr.Type())
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlock", nil, "")
|
||||
return llvm.Undef(llvmType)
|
||||
} else {
|
||||
// No-op:
|
||||
// select {
|
||||
// default:
|
||||
// }
|
||||
retval := llvm.Undef(llvmType)
|
||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||
return retval // {-1, false}
|
||||
}
|
||||
}
|
||||
|
||||
// This code create a (stack-allocated) slice containing all the select
|
||||
// cases and then calls runtime.chanSelect to perform the actual select
|
||||
// statement.
|
||||
// Simple selects (blocking and with just one case) are already transformed
|
||||
// into regular chan operations during SSA construction so we don't have to
|
||||
// optimize such small selects.
|
||||
|
||||
// Go through all the cases. Create the selectStates slice and and
|
||||
// determine the receive buffer size and alignment.
|
||||
recvbufSize := uint64(0)
|
||||
recvbufAlign := 0
|
||||
hasReceives := false
|
||||
var selectStates []llvm.Value
|
||||
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
ch := c.getValue(frame, state.Chan)
|
||||
selectState := llvm.ConstNull(chanSelectStateType)
|
||||
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
|
||||
switch state.Dir {
|
||||
case types.RecvOnly:
|
||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
||||
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
|
||||
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
||||
recvbufSize = size
|
||||
}
|
||||
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
||||
recvbufAlign = align
|
||||
}
|
||||
hasReceives = true
|
||||
case types.SendOnly:
|
||||
// Store this value in an alloca and put a pointer to this alloca
|
||||
// in the send state.
|
||||
sendValue := c.getValue(frame, state.Send)
|
||||
alloca := c.createEntryBlockAlloca(sendValue.Type(), "select.send.value")
|
||||
c.builder.CreateStore(sendValue, alloca)
|
||||
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
||||
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
selectStates = append(selectStates, selectState)
|
||||
}
|
||||
|
||||
// Create a receive buffer, where the received value will be stored.
|
||||
recvbuf := llvm.Undef(c.i8ptrType)
|
||||
if hasReceives {
|
||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca := c.builder.CreateAlloca(allocaType, "select.recvbuf.alloca")
|
||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "select.recvbuf")
|
||||
}
|
||||
|
||||
// Create the states slice (allocated on the stack).
|
||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
||||
statesAlloca := c.builder.CreateAlloca(statesAllocaType, "select.states.alloca")
|
||||
for i, state := range selectStates {
|
||||
// Set each slice element to the appropriate channel.
|
||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
c.builder.CreateStore(state, gep)
|
||||
}
|
||||
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "select.states")
|
||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||
|
||||
// Convert the 'blocking' flag on this select into a LLVM value.
|
||||
blockingInt := uint64(0)
|
||||
if expr.Blocking {
|
||||
blockingInt = 1
|
||||
}
|
||||
blockingValue := llvm.ConstInt(c.ctx.Int1Type(), blockingInt, false)
|
||||
|
||||
// Do the select in the runtime.
|
||||
results := c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
blockingValue,
|
||||
}, "")
|
||||
|
||||
// The result value does not include all the possible received values,
|
||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
||||
// instruction will treat a *ssa.Select specially and load it there inline.
|
||||
// Store the receive alloca in a sidetable until we hit this extract
|
||||
// instruction.
|
||||
if frame.selectRecvBuf == nil {
|
||||
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
||||
}
|
||||
frame.selectRecvBuf[expr] = recvbuf
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// getChanSelectResult returns the special values from a *ssa.Extract expression
|
||||
// when extracting a value from a select statement (*ssa.Select). Because
|
||||
// *ssa.Select cannot load all values in advance, it does this later in the
|
||||
// *ssa.Extract expression.
|
||||
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
|
||||
if expr.Index == 0 {
|
||||
// index
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
index := c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
|
||||
index = c.builder.CreateSExt(index, c.intType, "")
|
||||
}
|
||||
return index
|
||||
} else if expr.Index == 1 {
|
||||
// comma-ok
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
return c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
} else {
|
||||
// Select statements are (index, ok, ...) where ... is a number of
|
||||
// received values, depending on how many receive statements there
|
||||
// are. They are all combined into one alloca (because only one
|
||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
||||
// it to the correct type, and dereference it.
|
||||
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
|
||||
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
|
||||
return c.builder.CreateLoad(ptr, "")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
+979
-1069
File diff suppressed because it is too large
Load Diff
+31
-62
@@ -29,14 +29,14 @@ func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||
frame.deferClosureFuncs = make(map[*ir.Function]int)
|
||||
|
||||
// Create defer list pointer.
|
||||
deferType := llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)
|
||||
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
|
||||
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
||||
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
||||
}
|
||||
|
||||
// emitDefer emits a single defer instruction, to be run when this function
|
||||
// returns.
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
// The pointer to the previous defer struct, which we will replace to
|
||||
// make a linked list.
|
||||
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
||||
@@ -56,18 +56,12 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by the call parameters).
|
||||
itf, err := c.parseExpr(frame, instr.Call.Value) // interface
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
values = []llvm.Value{callback, next, receiverValue}
|
||||
valueTypes = append(valueTypes, c.i8ptrType)
|
||||
for _, arg := range instr.Call.Args {
|
||||
val, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val := c.getValue(frame, arg)
|
||||
values = append(values, val)
|
||||
valueTypes = append(valueTypes, val.Type())
|
||||
}
|
||||
@@ -86,10 +80,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
llvmParam := c.getValue(frame, param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
@@ -101,10 +92,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// pointer.
|
||||
// TODO: ignore this closure entirely and put pointers to the free
|
||||
// variables directly in the defer struct, avoiding a memory allocation.
|
||||
closure, err := c.parseExpr(frame, instr.Call.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
closure := c.getValue(frame, instr.Call.Value)
|
||||
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||
|
||||
// Get the callback number.
|
||||
@@ -120,10 +108,7 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// context pointer).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam, err := c.parseExpr(frame, param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
llvmParam := c.getValue(frame, param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
@@ -131,15 +116,13 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
valueTypes = append(valueTypes, context.Type())
|
||||
|
||||
} else {
|
||||
return c.makeError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||
return
|
||||
}
|
||||
|
||||
// Make a struct out of the collected values to put in the defer frame.
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFrame, err := c.getZeroValue(deferFrameType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deferFrame := llvm.ConstNull(deferFrameType)
|
||||
for i, value := range values {
|
||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||
}
|
||||
@@ -147,15 +130,17 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) error {
|
||||
// Put this struct in an alloca.
|
||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(alloca)
|
||||
}
|
||||
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||
return nil
|
||||
}
|
||||
|
||||
// emitRunDefers emits code to run all deferred functions.
|
||||
func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// Add a loop like the following:
|
||||
// for stack != nil {
|
||||
// _stack := stack
|
||||
@@ -172,10 +157,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// }
|
||||
|
||||
// Create loop.
|
||||
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||
c.builder.CreateBr(loophead)
|
||||
|
||||
// Create loop head:
|
||||
@@ -190,13 +175,13 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// stack = stack.next
|
||||
// switch stack.callback {
|
||||
c.builder.SetInsertPointAtEnd(loop)
|
||||
nextStackGEP := c.builder.CreateGEP(deferData, []llvm.Value{
|
||||
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||
}, "stack.next.gep")
|
||||
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||
c.builder.CreateStore(nextStack, frame.deferPtr)
|
||||
gep := c.builder.CreateGEP(deferData, []llvm.Value{
|
||||
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||
}, "callback.gep")
|
||||
@@ -207,7 +192,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// Create switch case, for example:
|
||||
// case 0:
|
||||
// // run first deferred call
|
||||
block := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
switch callback := callback.(type) {
|
||||
@@ -218,13 +203,9 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
}
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
|
||||
for _, arg := range callback.Args {
|
||||
llvmType, err := c.getLLVMType(arg.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
@@ -233,7 +214,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -246,23 +227,16 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||
c.createCall(fnPtr, forwardParams, "")
|
||||
|
||||
case *ir.Function:
|
||||
// Direct call.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
llvmType, err := c.getLLVMType(param.Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
@@ -271,7 +245,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := range callback.Params {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -289,14 +263,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
case *ssa.MakeClosure:
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
llvmType, err := c.getLLVMType(params.At(i).Type())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueTypes = append(valueTypes, llvmType)
|
||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
@@ -306,7 +276,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
@@ -334,5 +304,4 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
|
||||
// End of loop.
|
||||
c.builder.SetInsertPointAtEnd(end)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,3 +12,7 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
package compiler
|
||||
|
||||
// This file lowers func values into their final form. This is necessary for
|
||||
// funcValueSwitch, which needs full program analysis.
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// funcSignatureInfo keeps information about a single signature and its uses.
|
||||
type funcSignatureInfo struct {
|
||||
sig llvm.Value // *uint8 to identify the signature
|
||||
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
|
||||
}
|
||||
|
||||
// funcWithUses keeps information about a single function used as func value and
|
||||
// the assigned function ID. More commonly used functions are assigned a lower
|
||||
// ID.
|
||||
type funcWithUses struct {
|
||||
funcPtr llvm.Value
|
||||
useCount int // how often this function is used in a func value
|
||||
id int // assigned ID
|
||||
}
|
||||
|
||||
// Slice to sort functions by their use counts, or else their name if they're
|
||||
// used equally often.
|
||||
type funcWithUsesList []*funcWithUses
|
||||
|
||||
func (l funcWithUsesList) Len() int { return len(l) }
|
||||
func (l funcWithUsesList) Less(i, j int) bool {
|
||||
if l[i].useCount != l[j].useCount {
|
||||
// return the reverse: we want the highest use counts sorted first
|
||||
return l[i].useCount > l[j].useCount
|
||||
}
|
||||
iName := l[i].funcPtr.Name()
|
||||
jName := l[j].funcPtr.Name()
|
||||
return iName < jName
|
||||
}
|
||||
func (l funcWithUsesList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
|
||||
// runtime.getFuncPtr function to their final form.
|
||||
func (c *Compiler) LowerFuncValues() {
|
||||
if c.funcImplementation() != funcValueSwitch {
|
||||
return
|
||||
}
|
||||
|
||||
// Find all func values used in the program with their signatures.
|
||||
funcValueWithSignaturePtr := llvm.PointerType(c.getLLVMRuntimeType("funcValueWithSignature"), 0)
|
||||
signatures := map[string]*funcSignatureInfo{}
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.Type() != funcValueWithSignaturePtr {
|
||||
continue
|
||||
}
|
||||
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
||||
name := sig.Name()
|
||||
if info, ok := signatures[name]; ok {
|
||||
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
|
||||
} else {
|
||||
signatures[name] = &funcSignatureInfo{
|
||||
sig: sig,
|
||||
funcValueWithSignatures: []llvm.Value{global},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the signatures, for deterministic execution.
|
||||
names := make([]string, 0, len(signatures))
|
||||
for name := range signatures {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
|
||||
for _, name := range names {
|
||||
info := signatures[name]
|
||||
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
|
||||
for i, use := range info.funcValueWithSignatures {
|
||||
var useCount int
|
||||
for _, use2 := range getUses(use) {
|
||||
useCount += len(getUses(use2))
|
||||
}
|
||||
functions[i] = &funcWithUses{
|
||||
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
|
||||
useCount: useCount,
|
||||
}
|
||||
}
|
||||
sort.Sort(functions)
|
||||
|
||||
for i, fn := range functions {
|
||||
fn.id = i + 1
|
||||
for _, ptrtoint := range getUses(fn.funcPtr) {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
continue
|
||||
}
|
||||
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
||||
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
||||
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
||||
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint")
|
||||
}
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, getFuncPtrCall := range getUses(info.sig) {
|
||||
if getFuncPtrCall.IsACallInst().IsNil() {
|
||||
continue
|
||||
}
|
||||
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
|
||||
panic("expected all call uses to be runtime.getFuncPtr")
|
||||
}
|
||||
funcID := getFuncPtrCall.Operand(1)
|
||||
switch len(functions) {
|
||||
case 0:
|
||||
// There are no functions used in a func value that implement
|
||||
// this signature. The only possible value is a nil value.
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
nilptr := llvm.ConstPointerNull(inttoptr.Type())
|
||||
inttoptr.ReplaceAllUsesWith(nilptr)
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
case 1:
|
||||
// There is exactly one function with this signature that is
|
||||
// used in a func value. The func value itself can be either nil
|
||||
// or this one function.
|
||||
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
||||
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
inttoptr.ReplaceAllUsesWith(funcPtr)
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
default:
|
||||
// There are multiple functions used in a func value that
|
||||
// implement this signature.
|
||||
// What we'll do is transform the following:
|
||||
// rawPtr := runtime.getFuncPtr(func.ptr)
|
||||
// if rawPtr == nil {
|
||||
// runtime.nilPanic()
|
||||
// }
|
||||
// result := rawPtr(...args, func.context)
|
||||
// into this:
|
||||
// if false {
|
||||
// runtime.nilPanic()
|
||||
// }
|
||||
// var result // Phi
|
||||
// switch fn.id {
|
||||
// case 0:
|
||||
// runtime.nilPanic()
|
||||
// case 1:
|
||||
// result = call first implementation...
|
||||
// case 2:
|
||||
// result = call second implementation...
|
||||
// default:
|
||||
// unreachable
|
||||
// }
|
||||
|
||||
// Remove some casts, checks, and the old call which we're going
|
||||
// to replace.
|
||||
for _, callIntPtr := range getUses(getFuncPtrCall) {
|
||||
if !callIntPtr.IsACallInst().IsNil() && callIntPtr.CalledValue().Name() == "runtime.makeGoroutine" {
|
||||
for _, inttoptr := range getUses(callIntPtr) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected a inttoptr")
|
||||
}
|
||||
for _, use := range getUses(inttoptr) {
|
||||
c.addFuncLoweringSwitch(funcID, use, c.emitStartGoroutine, functions)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
callIntPtr.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
if callIntPtr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
for _, ptrUse := range getUses(callIntPtr) {
|
||||
if !ptrUse.IsABitCastInst().IsNil() {
|
||||
for _, bitcastUse := range getUses(ptrUse) {
|
||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().IsAFunction().IsNil() {
|
||||
panic("expected a call instruction")
|
||||
}
|
||||
switch bitcastUse.CalledValue().Name() {
|
||||
case "runtime.isnil":
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
default:
|
||||
panic("expected a call to runtime.isnil")
|
||||
}
|
||||
}
|
||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == callIntPtr {
|
||||
c.addFuncLoweringSwitch(funcID, ptrUse, func(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
return c.builder.CreateCall(funcPtr, params, "")
|
||||
}, functions)
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
}
|
||||
ptrUse.EraseFromParentAsInstruction()
|
||||
}
|
||||
callIntPtr.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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package compiler
|
||||
|
||||
// This file implements function values and closures. It may need some lowering
|
||||
// in a later step, see func-lowering.go.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type funcValueImplementation int
|
||||
|
||||
const (
|
||||
funcValueNone funcValueImplementation = iota
|
||||
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct containing
|
||||
// the free variables, or it may be undef if the function being pointed to
|
||||
// doesn't need a context. The function pointer is a regular function
|
||||
// pointer.
|
||||
funcValueDoubleword
|
||||
|
||||
// As funcValueDoubleword, but with the function pointer replaced by a
|
||||
// unique ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
funcValueSwitch
|
||||
)
|
||||
|
||||
// funcImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||
// Always pick the switch implementation, as it allows the use of blocking
|
||||
// inside a function that is used as a func value.
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
return funcValueSwitch
|
||||
case "tasks":
|
||||
return funcValueDoubleword
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case funcValueSwitch:
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
})
|
||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
funcValueType := c.getFuncType(sig)
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
}
|
||||
|
||||
// extractFuncContext extracts the context pointer from this function value. It
|
||||
// is a cheap operation.
|
||||
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
}
|
||||
|
||||
// decodeFuncValue extracts the context and the function pointer from this func
|
||||
// value. This may be an expensive operation.
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
case funcValueSwitch:
|
||||
llvmSig := c.getRawFuncType(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case funcValueSwitch:
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
}
|
||||
|
||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// Get the return type.
|
||||
var returnType llvm.Type
|
||||
switch typ.Results().Len() {
|
||||
case 0:
|
||||
// No return values.
|
||||
returnType = c.ctx.VoidType()
|
||||
case 1:
|
||||
// Just one return value.
|
||||
returnType = c.getLLVMType(typ.Results().At(0).Type())
|
||||
default:
|
||||
// Multiple return values. Put them together in a struct.
|
||||
// This appears to be the common way to handle multiple return values in
|
||||
// LLVM.
|
||||
members := make([]llvm.Type, typ.Results().Len())
|
||||
for i := 0; i < typ.Results().Len(); i++ {
|
||||
members[i] = c.getLLVMType(typ.Results().At(i).Type())
|
||||
}
|
||||
returnType = c.ctx.StructType(members, false)
|
||||
}
|
||||
|
||||
// Get the parameter types.
|
||||
var paramTypes []llvm.Type
|
||||
if typ.Recv() != nil {
|
||||
recv := c.getLLVMType(typ.Recv().Type())
|
||||
if recv.StructName() == "runtime._interface" {
|
||||
// This is a call on an interface, not a concrete type.
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||
}
|
||||
// All functions take these parameters at the end.
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
|
||||
// Make a func type out of the signature.
|
||||
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
|
||||
}
|
||||
|
||||
// parseMakeClosure makes a function value (with context) from the given
|
||||
// closure expression.
|
||||
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
if len(expr.Bindings) == 0 {
|
||||
panic("unexpected: MakeClosure without bound variables")
|
||||
}
|
||||
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||
|
||||
// Collect all bound variables.
|
||||
boundVars := make([]llvm.Value, len(expr.Bindings))
|
||||
for i, binding := range expr.Bindings {
|
||||
// The context stores the bound variables.
|
||||
llvmBoundVar := c.getValue(frame, binding)
|
||||
boundVars[i] = llvmBoundVar
|
||||
}
|
||||
|
||||
// Store the bound variables in a single object, allocating it on the heap
|
||||
// if necessary.
|
||||
context := c.emitPointerPack(boundVars)
|
||||
|
||||
// Create the closure.
|
||||
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||
}
|
||||
+471
@@ -0,0 +1,471 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+230
-145
@@ -1,5 +1,16 @@
|
||||
package compiler
|
||||
|
||||
// This file implements lowering for the goroutine scheduler. There are two
|
||||
// scheduler implementations, one based on tasks (like RTOSes and the main Go
|
||||
// runtime) and one based on a coroutine compiler transformation. The task based
|
||||
// implementation requires very little work from the compiler but is not very
|
||||
// portable (in particular, it is very hard if not impossible to support on
|
||||
// WebAssembly). The coroutine based one requires a lot of work by the compiler
|
||||
// to implement, but can run virtually anywhere with a single scheduler
|
||||
// implementation.
|
||||
//
|
||||
// The below description is for the coroutine based scheduler.
|
||||
//
|
||||
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
||||
// scheduler at runtime. It uses coroutine support in LLVM for this
|
||||
// transformation: https://llvm.org/docs/Coroutines.html
|
||||
@@ -10,8 +21,8 @@ package compiler
|
||||
// go foo()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar()
|
||||
// println("done")
|
||||
// i := bar()
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
@@ -21,9 +32,10 @@ package compiler
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// func bar() *int {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// return new(int)
|
||||
// }
|
||||
//
|
||||
// It is transformed by the IR generator in compiler.go into the following
|
||||
@@ -34,8 +46,8 @@ package compiler
|
||||
// fn()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done")
|
||||
// i := bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
@@ -45,9 +57,10 @@ package compiler
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar() {
|
||||
// func bar() *int {
|
||||
// time.Sleep(time.Second)
|
||||
// println("blocking operation completed)
|
||||
// return new(int)
|
||||
// }
|
||||
//
|
||||
// The pass in this file transforms this code even further, to the following
|
||||
@@ -59,9 +72,11 @@ package compiler
|
||||
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("some other operation")
|
||||
// var i *int // allocate space on the stack for the return value
|
||||
// runtime.setTaskStatePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||
// println("done")
|
||||
// println("done", *i)
|
||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||
// }
|
||||
//
|
||||
@@ -102,10 +117,69 @@ type asyncFunc struct {
|
||||
unreachableBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
// LowerGoroutines is a pass called during optimization that transforms the IR
|
||||
// into one where all blocking functions are turned into goroutines and blocking
|
||||
// calls into await calls.
|
||||
// LowerGoroutines performs some IR transformations necessary to support
|
||||
// goroutines. It does something different based on whether it uses the
|
||||
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
||||
// are necessary at all.
|
||||
func (c *Compiler) LowerGoroutines() error {
|
||||
switch c.selectScheduler() {
|
||||
case "coroutines":
|
||||
return c.lowerCoroutines()
|
||||
case "tasks":
|
||||
return c.lowerTasks()
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
// lowerTasks starts the main goroutine and then runs the scheduler.
|
||||
// This is enough compiler-level transformation for the task-based scheduler.
|
||||
func (c *Compiler) lowerTasks() error {
|
||||
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
||||
}
|
||||
mainCall := uses[0]
|
||||
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
if len(getUses(c.mod.NamedFunction("runtime.startGoroutine"))) != 0 {
|
||||
// Program needs a scheduler. Start main.main as a goroutine and start
|
||||
// the scheduler.
|
||||
realMainWrapper := c.createGoroutineStartWrapper(realMain)
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
c.createRuntimeCall("startGoroutine", []llvm.Value{realMainWrapper, zero}, "")
|
||||
c.createRuntimeCall("scheduler", nil, "")
|
||||
sleep := c.mod.NamedFunction("time.Sleep")
|
||||
if !sleep.IsNil() {
|
||||
sleep.ReplaceAllUsesWith(c.mod.NamedFunction("runtime.sleepCurrentTask"))
|
||||
}
|
||||
} else {
|
||||
// Program doesn't need a scheduler. Call main.main directly.
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
params := []llvm.Value{
|
||||
llvm.Undef(c.i8ptrType), // unused context parameter
|
||||
llvm.Undef(c.i8ptrType), // unused coroutine handle
|
||||
}
|
||||
c.createCall(realMain, params, "")
|
||||
// runtime.Goexit isn't needed so let it be optimized away by
|
||||
// globalopt.
|
||||
c.mod.NamedFunction("runtime.Goexit").SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
mainCall.EraseFromParentAsInstruction()
|
||||
|
||||
// main.main was set to external linkage during IR construction. Set it to
|
||||
// internal linkage to enable interprocedural optimizations.
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lowerCoroutines transforms the IR into one where all blocking functions are
|
||||
// turned into goroutines and blocking calls into await calls. It also makes
|
||||
// sure that the first coroutine is started and the coroutine scheduler will be
|
||||
// run.
|
||||
func (c *Compiler) lowerCoroutines() error {
|
||||
needsScheduler, err := c.markAsyncFunctions()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -140,13 +214,6 @@ func (c *Compiler) LowerGoroutines() error {
|
||||
// main.main was set to external linkage during IR construction. Set it to
|
||||
// internal linkage to enable interprocedural optimizations.
|
||||
realMain.SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanSend").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.chanRecv").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.activateTask").SetLinkage(llvm.InternalLinkage)
|
||||
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -170,13 +237,17 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
if !sleep.IsNil() {
|
||||
worklist = append(worklist, sleep)
|
||||
}
|
||||
chanSendStub := c.mod.NamedFunction("runtime.chanSendStub")
|
||||
if !chanSendStub.IsNil() {
|
||||
worklist = append(worklist, chanSendStub)
|
||||
deadlock := c.mod.NamedFunction("runtime.deadlock")
|
||||
if !deadlock.IsNil() {
|
||||
worklist = append(worklist, deadlock)
|
||||
}
|
||||
chanRecvStub := c.mod.NamedFunction("runtime.chanRecvStub")
|
||||
if !chanRecvStub.IsNil() {
|
||||
worklist = append(worklist, chanRecvStub)
|
||||
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||
if !chanSend.IsNil() {
|
||||
worklist = append(worklist, chanSend)
|
||||
}
|
||||
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
|
||||
if !chanRecv.IsNil() {
|
||||
worklist = append(worklist, chanRecv)
|
||||
}
|
||||
|
||||
if len(worklist) == 0 {
|
||||
@@ -204,17 +275,25 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Add all callees to the worklist.
|
||||
for _, use := range getUses(f) {
|
||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||
bitcastUses := getUses(use)
|
||||
for _, call := range bitcastUses {
|
||||
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
||||
for _, call := range getUses(use) {
|
||||
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
||||
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
||||
return false, errors.New("async function " + f.Name() + " incorrectly used in ptrtoint, expected runtime.makeGoroutine")
|
||||
}
|
||||
}
|
||||
// This is a go statement. Do not mark the parent as async, as
|
||||
// starting a goroutine is not a blocking operation.
|
||||
continue
|
||||
}
|
||||
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||
// Not sure why this const bitcast is here but as long as it
|
||||
// has no uses it can be ignored, I guess?
|
||||
// I think it was created for the runtime.isnil check but
|
||||
// somehow wasn't removed when all these checks are removed.
|
||||
if len(getUses(use)) == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if use.IsACallInst().IsNil() {
|
||||
// Not a call instruction. Maybe a store to a global? In any
|
||||
// case, this requires support for async calls across function
|
||||
@@ -243,12 +322,12 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// goroutine is not async (does not do any blocking operation), no
|
||||
// scheduler is necessary as it can be called directly.
|
||||
for _, use := range getUses(makeGoroutine) {
|
||||
// Input param must be const bitcast of function.
|
||||
bitcast := use.Operand(0)
|
||||
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
||||
panic("expected const bitcast operand of runtime.makeGoroutine")
|
||||
// Input param must be const ptrtoint of function.
|
||||
ptrtoint := use.Operand(0)
|
||||
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint operand of runtime.makeGoroutine")
|
||||
}
|
||||
goroutine := bitcast.Operand(0)
|
||||
goroutine := ptrtoint.Operand(0)
|
||||
if _, ok := asyncFuncs[goroutine]; ok {
|
||||
needsScheduler = true
|
||||
break
|
||||
@@ -274,9 +353,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||
|
||||
coroPromiseType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.i8ptrType, c.ctx.Int32Type(), c.ctx.Int1Type()}, false)
|
||||
coroPromiseFunc := llvm.AddFunction(c.mod, "llvm.coro.promise", coroPromiseType)
|
||||
|
||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||
|
||||
@@ -288,7 +364,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Transform all async functions into coroutines.
|
||||
for _, f := range asyncList {
|
||||
if f == sleep || f == chanSendStub || f == chanRecvStub {
|
||||
if f == sleep || f == deadlock || f == chanSend || f == chanRecv {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -305,7 +381,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() {
|
||||
callee := inst.CalledValue()
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == chanSendStub || callee == chanRecvStub {
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlock || callee == chanSend || callee == chanRecv {
|
||||
continue
|
||||
}
|
||||
asyncCalls = append(asyncCalls, inst)
|
||||
@@ -317,7 +393,7 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
|
||||
// Coroutine setup.
|
||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
||||
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
|
||||
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
@@ -332,6 +408,9 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||
}
|
||||
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(data)
|
||||
}
|
||||
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||
|
||||
// Modify async calls so this function suspends right after the child
|
||||
@@ -343,10 +422,18 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
// Split this basic block.
|
||||
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||
|
||||
// Set task state to TASK_STATE_CALL.
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
// Allocate space for the return value.
|
||||
var retvalAlloca llvm.Value
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||
}
|
||||
|
||||
// Suspend.
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
@@ -354,44 +441,76 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
// Load the return value from the alloca. The callee has
|
||||
// written the return value to it.
|
||||
c.builder.SetInsertPointBefore(await.FirstInstruction())
|
||||
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||
inst.ReplaceAllUsesWith(retval)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace return instructions with suspend points that should
|
||||
// reactivate the parent coroutine.
|
||||
for _, inst := range returns {
|
||||
if inst.OperandsCount() == 0 {
|
||||
// These properties were added by the functionattrs pass.
|
||||
// Remove them, because now we start using the parameter.
|
||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||
for _, kind := range []string{"nocapture", "readnone"} {
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||
}
|
||||
|
||||
// Reactivate the parent coroutine. This adds it back to
|
||||
// the run queue, so it is started again by the
|
||||
// scheduler when possible (possibly right after the
|
||||
// following suspend).
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
parentHandle := f.LastParam()
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// Suspend this coroutine.
|
||||
// It would look like this is unnecessary, but if this
|
||||
// suspend point is left out, it leads to undefined
|
||||
// behavior somehow (with the unreachable instruction).
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||
}, "ret")
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
} else {
|
||||
panic("todo: return value from coroutine")
|
||||
// These properties were added by the functionattrs pass. Remove
|
||||
// them, because now we start using the parameter.
|
||||
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||
for _, kind := range []string{"nocapture", "readnone"} {
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||
}
|
||||
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
var parentHandle llvm.Value
|
||||
if f.Linkage() == llvm.ExternalLinkage {
|
||||
// Exported function.
|
||||
// Note that getTaskStatePtr will panic if it is called with
|
||||
// a nil pointer, so blocking exported functions that try to
|
||||
// return anything will not work.
|
||||
parentHandle = llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else {
|
||||
parentHandle = f.LastParam()
|
||||
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
|
||||
// sanity check
|
||||
panic("trying to make exported function async: " + f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
// Store return values.
|
||||
switch inst.OperandsCount() {
|
||||
case 0:
|
||||
// Nothing to return.
|
||||
case 1:
|
||||
// Return this value by writing to the pointer stored in the
|
||||
// parent handle. The parent coroutine has made an alloca that
|
||||
// we can write to to store our return value.
|
||||
returnValuePtr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// Reactivate the parent coroutine. This adds it back to the run
|
||||
// queue, so it is started again by the scheduler when possible
|
||||
// (possibly right after the following suspend).
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// Suspend this coroutine.
|
||||
// It would look like this is unnecessary, but if this
|
||||
// suspend point is left out, it leads to undefined
|
||||
// behavior somehow (with the unreachable instruction).
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "ret")
|
||||
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(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||
@@ -416,6 +535,14 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
c.builder.CreateUnreachable()
|
||||
}
|
||||
|
||||
// Replace calls to runtime.getCoroutineCall with the coroutine of this
|
||||
// frame.
|
||||
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
|
||||
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
|
||||
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
|
||||
getCoroutineCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||
for _, sleepCall := range getUses(sleep) {
|
||||
// sleepCall must be a call instruction.
|
||||
@@ -439,37 +566,31 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sleepCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanSendStub into channel send operations.
|
||||
for _, sendOp := range getUses(chanSendStub) {
|
||||
// Transform calls to runtime.deadlock into coroutine suspends (without
|
||||
// resume).
|
||||
for _, deadlockCall := range getUses(deadlock) {
|
||||
// deadlockCall must be a call instruction.
|
||||
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
||||
|
||||
// Exit coroutine.
|
||||
c.builder.SetInsertPointBefore(deadlockCall)
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
|
||||
c.builder.SetInsertPointBefore(deadlockCall)
|
||||
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(), 1, false), frame.cleanupBlock)
|
||||
deadlockCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanSend into channel send operations.
|
||||
for _, sendOp := range getUses(chanSend) {
|
||||
// sendOp must be a call instruction.
|
||||
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
||||
|
||||
// Send the value over the channel, or block.
|
||||
sendOp.SetOperand(0, frame.taskHandle)
|
||||
sendOp.SetOperand(sendOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanSend"))
|
||||
|
||||
// Use taskState.data to store the value to send:
|
||||
// *(*valueType)(&coroutine.promise().data) = valueToSend
|
||||
// runtime.chanSend(coroutine, ch)
|
||||
bitcast := sendOp.Operand(2)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
c.builder.SetInsertPointBefore(valueAlloca)
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
sendOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
@@ -482,21 +603,11 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
}
|
||||
|
||||
// Transform calls to runtime.chanRecvStub into channel receive operations.
|
||||
for _, recvOp := range getUses(chanRecvStub) {
|
||||
// Transform calls to runtime.chanRecv into channel receive operations.
|
||||
for _, recvOp := range getUses(chanRecv) {
|
||||
// recvOp must be a call instruction.
|
||||
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
||||
|
||||
bitcast := recvOp.Operand(2)
|
||||
commaOk := recvOp.Operand(3)
|
||||
valueAlloca := bitcast.Operand(0)
|
||||
|
||||
// Receive the value over the channel, or block.
|
||||
recvOp.SetOperand(0, frame.taskHandle)
|
||||
recvOp.SetOperand(recvOp.OperandsCount()-1, c.mod.NamedFunction("runtime.chanRecv"))
|
||||
recvOp.SetOperand(2, llvm.Undef(c.i8ptrType))
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
|
||||
// Yield to scheduler.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
@@ -508,32 +619,6 @@ func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
|
||||
// The value to receive is stored in taskState.data:
|
||||
// runtime.chanRecv(coroutine, ch)
|
||||
// promise := coroutine.promise()
|
||||
// valueReceived := *(*valueType)(&promise.data)
|
||||
// ok := promise.commaOk
|
||||
c.builder.SetInsertPointBefore(wakeup.FirstInstruction())
|
||||
promiseType := c.mod.GetTypeByName("runtime.taskState")
|
||||
promiseRaw := c.builder.CreateCall(coroPromiseFunc, []llvm.Value{
|
||||
frame.taskHandle,
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(c.targetData.PrefTypeAlignment(promiseType)), false),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "task.promise.raw")
|
||||
promise := c.builder.CreateBitCast(promiseRaw, llvm.PointerType(promiseType, 0), "task.promise")
|
||||
dataPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 2, false),
|
||||
}, "task.promise.data")
|
||||
valueAlloca.ReplaceAllUsesWith(c.builder.CreateBitCast(dataPtr, valueAlloca.Type(), ""))
|
||||
valueAlloca.EraseFromParentAsInstruction()
|
||||
commaOkPtr := c.builder.CreateGEP(promise, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
|
||||
}, "task.promise.comma-ok")
|
||||
commaOk.ReplaceAllUsesWith(commaOkPtr)
|
||||
recvOp.SetOperand(3, llvm.Undef(commaOk.Type()))
|
||||
}
|
||||
|
||||
return true, c.lowerMakeGoroutineCalls()
|
||||
@@ -558,14 +643,14 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
for _, goroutine := range getUses(makeGoroutine) {
|
||||
bitcastIn := goroutine.Operand(0)
|
||||
origFunc := bitcastIn.Operand(0)
|
||||
ptrtointIn := goroutine.Operand(0)
|
||||
origFunc := ptrtointIn.Operand(0)
|
||||
uses := getUses(goroutine)
|
||||
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
||||
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
||||
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
|
||||
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
||||
}
|
||||
bitcastOut := uses[0]
|
||||
uses = getUses(bitcastOut)
|
||||
inttoptrOut := uses[0]
|
||||
uses = getUses(inttoptrOut)
|
||||
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||
}
|
||||
@@ -580,7 +665,7 @@ func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||
c.builder.SetInsertPointBefore(realCall)
|
||||
c.builder.CreateCall(origFunc, params, "")
|
||||
realCall.EraseFromParentAsInstruction()
|
||||
bitcastOut.EraseFromParentAsInstruction()
|
||||
inttoptrOut.EraseFromParentAsInstruction()
|
||||
goroutine.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
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, "")
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package compiler
|
||||
|
||||
// This file implements inline asm support by calling special functions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This is a compiler builtin, which reads the given register by name:
|
||||
//
|
||||
// func ReadRegister(name string) uintptr
|
||||
//
|
||||
// The register name must be a constant, for example "sp".
|
||||
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
|
||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
var asm string
|
||||
switch name {
|
||||
case "device/arm.ReadRegister":
|
||||
asm = "mov $0, " + regname
|
||||
case "device/riscv.ReadRegister":
|
||||
asm = "mv $0, " + regname
|
||||
default:
|
||||
panic("unknown architecture")
|
||||
}
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which emits a piece of inline assembly with no
|
||||
// operands or return values. It is useful for trivial instructions, like wfi in
|
||||
// ARM or sleep in AVR.
|
||||
//
|
||||
// func Asm(asm string)
|
||||
//
|
||||
// The provided assembly must be a constant.
|
||||
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
// Magic function: insert inline assembly instead of calling it.
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
|
||||
asm := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which allows assembly to be called in a flexible
|
||||
// way.
|
||||
//
|
||||
// func AsmFull(asm string, regs map[string]interface{})
|
||||
//
|
||||
// The asm parameter must be a constant string. The regs parameter must be
|
||||
// provided immediately. For example:
|
||||
//
|
||||
// arm.AsmFull(
|
||||
// "str {value}, {result}",
|
||||
// map[string]interface{}{
|
||||
// "value": 1
|
||||
// "result": &dest,
|
||||
// })
|
||||
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
registers := map[string]llvm.Value{}
|
||||
registerMap := instr.Args[1].(*ssa.MakeMap)
|
||||
for _, r := range *registerMap.Referrers() {
|
||||
switch r := r.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.MapUpdate:
|
||||
if r.Block() != registerMap.Block() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||
}
|
||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
|
||||
case *ssa.Call:
|
||||
if r.Common() == instr {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||
}
|
||||
}
|
||||
// TODO: handle dollar signs in asm string
|
||||
registerNumbers := map[string]int{}
|
||||
var err error
|
||||
argTypes := []llvm.Type{}
|
||||
args := []llvm.Value{}
|
||||
constraints := []string{}
|
||||
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
||||
// instructions.
|
||||
name := s[1 : len(s)-1]
|
||||
if _, ok := registers[name]; !ok {
|
||||
if err == nil {
|
||||
err = c.makeError(instr.Pos(), "unknown register name: "+name)
|
||||
}
|
||||
return s
|
||||
}
|
||||
if _, ok := registerNumbers[name]; !ok {
|
||||
registerNumbers[name] = len(registerNumbers)
|
||||
argTypes = append(argTypes, registers[name].Type())
|
||||
args = append(args, registers[name])
|
||||
switch registers[name].Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
constraints = append(constraints, "r")
|
||||
case llvm.PointerTypeKind:
|
||||
constraints = append(constraints, "*m")
|
||||
default:
|
||||
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||
return s
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("${%v}", registerNumbers[name])
|
||||
})
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||
// be one of:
|
||||
//
|
||||
// func SVCall0(num uintptr) uintptr
|
||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
//
|
||||
// The num parameter must be a constant. All other parameters may be any scalar
|
||||
// value supported by LLVM inline assembly.
|
||||
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={r0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// Implement the ARM calling convention by marking r1-r3 as
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
return c.builder.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
@@ -4,7 +4,6 @@ package compiler
|
||||
// form, optimizing them in the process.
|
||||
//
|
||||
// During SSA construction, the following pseudo-calls are created:
|
||||
// runtime.makeInterface(typecode, methodSet)
|
||||
// runtime.typeAssert(typecode, assertedType)
|
||||
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
||||
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
||||
@@ -14,16 +13,13 @@ package compiler
|
||||
//
|
||||
// This pass lowers the above functions to their final form:
|
||||
//
|
||||
// makeInterface:
|
||||
// Replaced with a constant typecode.
|
||||
//
|
||||
// typeAssert:
|
||||
// Replaced with an icmp instruction so it can be directly used in a type
|
||||
// switch. This is very easy to optimize for LLVM: it will often translate a
|
||||
// type switch into a regular switch statement.
|
||||
// When this type assert is not possible (the type is never used in an
|
||||
// interface with makeInterface), this call is replaced with a constant
|
||||
// false to optimize the type assert away completely.
|
||||
// interface), this call is replaced with a constant false to optimize the
|
||||
// type assert away completely.
|
||||
//
|
||||
// interfaceImplements:
|
||||
// This call is translated into a call that checks whether the underlying
|
||||
@@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
|
||||
|
||||
// run runs the pass itself.
|
||||
func (p *lowerInterfacesPass) run() {
|
||||
// Count per type how often it is put in an interface. Also, collect all
|
||||
// methods this type has (if it is named).
|
||||
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
|
||||
makeInterfaceUses := getUses(makeInterface)
|
||||
for _, use := range makeInterfaceUses {
|
||||
typecode := use.Operand(0)
|
||||
name := typecode.Name()
|
||||
if t, ok := p.types[name]; !ok {
|
||||
// This is the first time this type has been seen, add it to the
|
||||
// list of types.
|
||||
t = p.addType(typecode)
|
||||
p.addTypeMethods(t, use.Operand(1))
|
||||
} else {
|
||||
p.addTypeMethods(t, use.Operand(1))
|
||||
}
|
||||
// Collect all type codes.
|
||||
typecodeIDPtr := llvm.PointerType(p.getLLVMRuntimeType("typecodeID"), 0)
|
||||
typeInInterfacePtr := llvm.PointerType(p.getLLVMRuntimeType("typeInInterface"), 0)
|
||||
var typesInInterfaces []llvm.Value
|
||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
switch global.Type() {
|
||||
case typecodeIDPtr:
|
||||
// Retrieve Go type information based on an opaque global variable.
|
||||
// Only the name of the global is relevant, the object itself is
|
||||
// discarded afterwards.
|
||||
name := global.Name()
|
||||
t := &typeInfo{
|
||||
name: name,
|
||||
typecode: global,
|
||||
}
|
||||
p.types[name] = t
|
||||
case typeInInterfacePtr:
|
||||
// Count per type how often it is put in an interface. Also, collect
|
||||
// all methods this type has (if it is named).
|
||||
typesInInterfaces = append(typesInInterfaces, global)
|
||||
initializer := global.Initializer()
|
||||
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
|
||||
t := p.types[typecode.Name()]
|
||||
p.addTypeMethods(t, methodSet)
|
||||
|
||||
// Count the number of MakeInterface instructions, for sorting the
|
||||
// typecodes later.
|
||||
p.types[name].countMakeInterfaces++
|
||||
// Count the number of MakeInterface instructions, for sorting the
|
||||
// typecodes later.
|
||||
t.countMakeInterfaces += len(getUses(global))
|
||||
}
|
||||
}
|
||||
|
||||
// Count per type how often it is type asserted on (e.g. in a switch
|
||||
@@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
for _, use := range typeAssertUses {
|
||||
typecode := use.Operand(1)
|
||||
name := typecode.Name()
|
||||
if _, ok := p.types[name]; !ok {
|
||||
p.addType(typecode)
|
||||
}
|
||||
p.types[name].countTypeAsserts++
|
||||
}
|
||||
|
||||
@@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
typecode := use.Operand(0)
|
||||
signature := p.signatures[use.Operand(2).Name()]
|
||||
|
||||
// If the interface was created in the same function, we can insert a
|
||||
// direct call. This may not happen often but it is an easy
|
||||
// optimization so let's do it anyway.
|
||||
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
|
||||
name := typecode.Operand(0).Name()
|
||||
typ := p.types[name]
|
||||
p.replaceInvokeWithCall(use, typ, signature)
|
||||
continue
|
||||
}
|
||||
|
||||
methodSet := use.Operand(1).Operand(0) // global variable
|
||||
itf := p.interfaces[methodSet.Name()]
|
||||
if len(itf.types) == 0 {
|
||||
@@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
// types, if possible.
|
||||
for _, use := range interfaceImplementsUses {
|
||||
actualType := use.Operand(0)
|
||||
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
|
||||
// Type assert is in the same function that creates the interface
|
||||
// value. This means the underlying type is already known so match
|
||||
// on that.
|
||||
// This may not happen often but it is an easy optimization.
|
||||
name := actualType.Operand(0).Name()
|
||||
typ := p.types[name]
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
|
||||
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
|
||||
use.ReplaceAllUsesWith(commaOk)
|
||||
use.EraseFromParentAsInstruction()
|
||||
continue
|
||||
}
|
||||
|
||||
methodSet := use.Operand(1).Operand(0) // global variable
|
||||
itf := p.interfaces[methodSet.Name()]
|
||||
@@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
|
||||
// Assign a type code for each type.
|
||||
p.assignTypeCodes(typeSlice)
|
||||
|
||||
// Replace each call to runtime.makeInterface with the constant type code.
|
||||
for _, use := range makeInterfaceUses {
|
||||
global := use.Operand(0)
|
||||
t := p.types[global.Name()]
|
||||
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
|
||||
use.EraseFromParentAsInstruction()
|
||||
// Replace each use of a runtime.typeInInterface with the constant type
|
||||
// code.
|
||||
for _, global := range typesInInterfaces {
|
||||
for _, use := range getUses(global) {
|
||||
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
|
||||
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
||||
use.ReplaceAllUsesWith(typecode)
|
||||
}
|
||||
}
|
||||
|
||||
// Replace each type assert with an actual type comparison or (if the type
|
||||
@@ -458,12 +440,18 @@ func (p *lowerInterfacesPass) run() {
|
||||
// numbers.
|
||||
for _, typ := range p.types {
|
||||
for _, use := range getUses(typ.typecode) {
|
||||
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
|
||||
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
||||
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stray runtime.typeInInterface globals. Required for the following
|
||||
// cleanup.
|
||||
for _, global := range typesInInterfaces {
|
||||
global.EraseFromParentAsGlobal()
|
||||
}
|
||||
|
||||
// Remove method sets of types. Unnecessary, but cleans up the IR for
|
||||
// inspection.
|
||||
for _, typ := range p.types {
|
||||
@@ -474,19 +462,6 @@ func (p *lowerInterfacesPass) run() {
|
||||
}
|
||||
}
|
||||
|
||||
// addType retrieves Go type information based on a i16 global variable.
|
||||
// Only the name of the i16 is relevant, the object itself is const-propagated
|
||||
// and discared afterwards.
|
||||
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
|
||||
name := typecode.Name()
|
||||
t := &typeInfo{
|
||||
name: name,
|
||||
typecode: typecode,
|
||||
}
|
||||
p.types[name] = t
|
||||
return t
|
||||
}
|
||||
|
||||
// addTypeMethods reads the method set of the given type info struct. It
|
||||
// retrieves the signatures and the references to the method functions
|
||||
// themselves for later type<->interface matching.
|
||||
@@ -552,11 +527,40 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
|
||||
}
|
||||
inttoptr := inttoptrs[0]
|
||||
function := typ.getMethod(signature).function
|
||||
if inttoptr.Type() != function.Type() {
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
function = p.builder.CreateBitCast(function, inttoptr.Type(), "")
|
||||
if inttoptr.Type() == function.Type() {
|
||||
// Easy case: the types are the same. Simply replace the inttoptr
|
||||
// result (which is directly called) with the actual function.
|
||||
inttoptr.ReplaceAllUsesWith(function)
|
||||
} else {
|
||||
// Harder case: the type is not actually the same. Go through each call
|
||||
// (of which there should be only one), extract the receiver params for
|
||||
// this call and replace the call with a direct call to the target
|
||||
// function.
|
||||
for _, call := range getUses(inttoptr) {
|
||||
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
|
||||
panic("expected the inttoptr to be called as a method, this is not a method call")
|
||||
}
|
||||
operands := make([]llvm.Value, call.OperandsCount()-1)
|
||||
for i := range operands {
|
||||
operands[i] = call.Operand(i)
|
||||
}
|
||||
paramTypes := function.Type().ElementType().ParamTypes()
|
||||
receiverParamTypes := paramTypes[:len(paramTypes)-(len(operands)-1)]
|
||||
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
|
||||
for i, methodParamType := range methodParamTypes {
|
||||
if methodParamType != operands[i+1].Type() {
|
||||
panic("expected method call param type and function param type to be the same")
|
||||
}
|
||||
}
|
||||
p.builder.SetInsertPointBefore(call)
|
||||
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
|
||||
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
|
||||
if result.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
call.ReplaceAllUsesWith(result)
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
inttoptr.ReplaceAllUsesWith(function)
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
@@ -599,9 +603,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
|
||||
// TODO: debug info
|
||||
|
||||
// Create all used basic blocks.
|
||||
entry := llvm.AddBasicBlock(fn, "entry")
|
||||
thenBlock := llvm.AddBasicBlock(fn, "then")
|
||||
elseBlock := llvm.AddBasicBlock(fn, "else")
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
||||
elseBlock := p.ctx.AddBasicBlock(fn, "else")
|
||||
|
||||
// Add all possible types as cases.
|
||||
p.builder.SetInsertPointAtEnd(entry)
|
||||
@@ -657,11 +661,11 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
// TODO: debug info
|
||||
|
||||
// Create entry block.
|
||||
entry := llvm.AddBasicBlock(fn, "entry")
|
||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||
|
||||
// Create default block and make it unreachable (which it is, because all
|
||||
// possible types are checked).
|
||||
defaultBlock := llvm.AddBasicBlock(fn, "default")
|
||||
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
|
||||
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
p.builder.CreateUnreachable()
|
||||
|
||||
@@ -680,7 +684,7 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||
|
||||
// Define all possible functions that can be called.
|
||||
for _, typ := range itf.types {
|
||||
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||
|
||||
// The function we will redirect to when the interface has this type.
|
||||
|
||||
+141
-164
@@ -22,75 +22,123 @@ import (
|
||||
// value field.
|
||||
//
|
||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
|
||||
var itfValue llvm.Value
|
||||
size := c.targetData.TypeAllocSize(val.Type())
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Allocate on the heap and put a pointer in the interface.
|
||||
// TODO: escape analysis.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
|
||||
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
|
||||
c.builder.CreateStore(val, itfValueCast)
|
||||
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
|
||||
} else if size == 0 {
|
||||
itfValue = llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else {
|
||||
// Directly place the value in the interface.
|
||||
switch val.Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
|
||||
case llvm.PointerTypeKind:
|
||||
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
|
||||
case llvm.StructTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
// A bitcast would be useful here, but bitcast doesn't allow
|
||||
// aggregate types. So we'll bitcast it using an alloca.
|
||||
// Hopefully this will get optimized away.
|
||||
mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
|
||||
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
|
||||
c.builder.CreateStore(val, memStructPtr)
|
||||
itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(pos, "todo: makeinterface: cast small type to i8*")
|
||||
}
|
||||
}
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
|
||||
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
|
||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf, nil
|
||||
return itf
|
||||
}
|
||||
|
||||
// getTypeCode returns a reference to a type code.
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||
// Create a new typecode global.
|
||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
||||
// Some type classes contain more information for underlying types or
|
||||
// element types. Store it directly in the typecode global to make
|
||||
// reflect lowering simpler.
|
||||
var references llvm.Value
|
||||
var length int64
|
||||
switch typ := typ.(type) {
|
||||
case *types.Named:
|
||||
references = c.getTypeCode(typ.Underlying())
|
||||
case *types.Chan:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Pointer:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Slice:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Array:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
length = typ.Len()
|
||||
case *types.Struct:
|
||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||
structGlobal := c.makeStructTypeFields(typ)
|
||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
||||
}
|
||||
if !references.IsNil() {
|
||||
// Set the 'references' field of the runtime.typecodeID struct.
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// makeStructTypeFields creates a new global that stores all type information
|
||||
// related to this struct type, and returns the resulting global. This global is
|
||||
// actually an array of all the fields in the structs.
|
||||
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
// The global is an array of runtime.structField structs.
|
||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
||||
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
|
||||
structGlobalValue := llvm.ConstNull(structGlobalType)
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
|
||||
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
|
||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldName.SetUnnamedAddr(true)
|
||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
||||
if typ.Tag(i) != "" {
|
||||
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
|
||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldTag.SetUnnamedAddr(true)
|
||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
||||
}
|
||||
if typ.Field(i).Embedded() {
|
||||
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
|
||||
}
|
||||
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
|
||||
}
|
||||
structGlobal.SetInitializer(structGlobalValue)
|
||||
structGlobal.SetUnnamedAddr(true)
|
||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
return structGlobal
|
||||
}
|
||||
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) string {
|
||||
name := ""
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
name = "~" + named.String() + ":"
|
||||
t = t.Underlying()
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
return "named:" + t.String()
|
||||
case *types.Array:
|
||||
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
@@ -133,21 +181,21 @@ func getTypeCodeName(t types.Type) string {
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + name + kind
|
||||
return "basic:" + kind
|
||||
case *types.Chan:
|
||||
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||
return "chan:" + getTypeCodeName(t.Elem())
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||
return "pointer:" + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
@@ -157,15 +205,26 @@ func getTypeCodeName(t types.Type) string {
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
return "slice:" + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
||||
// TODO: report this as a normal error instead of panicking.
|
||||
panic("cgo unions are not allowed in interfaces")
|
||||
}
|
||||
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
embedded := ""
|
||||
if t.Field(i).Embedded() {
|
||||
embedded = "#"
|
||||
}
|
||||
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
|
||||
if t.Tag(i) != "" {
|
||||
elems[i] += "`" + t.Tag(i) + "`"
|
||||
}
|
||||
}
|
||||
return "struct:" + "{" + strings.Join(elems, ",") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
@@ -173,22 +232,22 @@ func getTypeCodeName(t types.Type) string {
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
if !global.IsNil() {
|
||||
// the method set already exists
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
||||
}
|
||||
|
||||
methods := make([]llvm.Value, ms.Len())
|
||||
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
|
||||
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
@@ -197,10 +256,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
// compiler error, so panic
|
||||
panic("cannot find function: " + f.LinkName())
|
||||
}
|
||||
fn, err := c.getInterfaceInvokeWrapper(f)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fn := c.getInterfaceInvokeWrapper(f)
|
||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||
signatureGlobal,
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
@@ -213,7 +269,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||
@@ -262,19 +318,9 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
//
|
||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||
// asserts on interfaces are more difficult, see the comments in the function.
|
||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
|
||||
itf, err := c.parseExpr(frame, expr.X)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
assertedType, err := c.getLLVMType(expr.AssertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
valueNil, err := c.getZeroValue(assertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||
itf := c.getValue(frame, expr.X)
|
||||
assertedType := c.getLLVMType(expr.AssertedType)
|
||||
|
||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||
commaOk := llvm.Value{}
|
||||
@@ -327,71 +373,35 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
size := c.targetData.TypeAllocSize(assertedType)
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Value was stored in an allocated buffer, load it from there.
|
||||
valuePtrCast := c.builder.CreateBitCast(valuePtr, llvm.PointerType(assertedType, 0), "")
|
||||
valueOk = c.builder.CreateLoad(valuePtrCast, "typeassert.value.ok")
|
||||
} else if size == 0 {
|
||||
valueOk, err = c.getZeroValue(assertedType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
} else {
|
||||
// Value was stored directly in the interface.
|
||||
switch assertedType.TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
|
||||
case llvm.PointerTypeKind:
|
||||
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
|
||||
default: // struct, float, etc.
|
||||
// A bitcast would be useful here, but bitcast doesn't allow
|
||||
// aggregate types. So we'll bitcast it using an alloca.
|
||||
// Hopefully this will get optimized away.
|
||||
mem := c.builder.CreateAlloca(c.i8ptrType, "")
|
||||
c.builder.CreateStore(valuePtr, mem)
|
||||
memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
|
||||
valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
|
||||
}
|
||||
}
|
||||
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||
}
|
||||
c.builder.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{valueNil, valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
return tuple, nil
|
||||
return tuple
|
||||
} else {
|
||||
// This is kind of dirty as the branch above becomes mostly useless,
|
||||
// but hopefully this gets optimized away.
|
||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
return phi, nil
|
||||
return phi
|
||||
}
|
||||
}
|
||||
|
||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||
// interface call. It can be used in a call or defer instruction.
|
||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value, error) {
|
||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||
// Call an interface method with dynamic dispatch.
|
||||
itf, err := c.parseExpr(frame, instr.Value) // interface
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
itf := c.getValue(frame, instr.Value) // interface
|
||||
|
||||
llvmFnType, err := c.getLLVMType(instr.Method.Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
// getLLVMType() has created a closure type for us, but we don't actually
|
||||
// want a closure type as an interface call can never be a closure call. So
|
||||
// extract the function pointer type from the closure.
|
||||
llvmFnType = llvmFnType.Subtypes()[1]
|
||||
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||
|
||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||
values := []llvm.Value{
|
||||
@@ -405,11 +415,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
|
||||
args := []llvm.Value{receiverValue}
|
||||
for _, arg := range instr.Args {
|
||||
val, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, nil, err
|
||||
}
|
||||
args = append(args, val)
|
||||
args = append(args, c.getValue(frame, arg))
|
||||
}
|
||||
// Add the context parameter. An interface call never takes a context but we
|
||||
// have to supply the parameter anyway.
|
||||
@@ -417,7 +423,7 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
// Add the parent goroutine handle.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
|
||||
return fnCast, args, nil
|
||||
return fnCast, args
|
||||
}
|
||||
|
||||
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||
@@ -433,19 +439,16 @@ type interfaceInvokeWrapper struct {
|
||||
// the underlying value, dereferences it, and calls the real method. This
|
||||
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||
// pointer and a pointer to the value must be created.
|
||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
|
||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
wrapperName := f.LinkName() + "$invoke"
|
||||
wrapper := c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
// Wrapper already created. Return it directly.
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// Get the expanded receiver type.
|
||||
receiverType, err := c.getLLVMType(f.Params[0].Type())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||
|
||||
// Does this method even need any wrapping?
|
||||
@@ -454,7 +457,7 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
||||
// Casting a function signature to a different signature and calling it
|
||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||
// interface) is hopefully a safe (defined) operation.
|
||||
return f.LLVMFn, nil
|
||||
return f.LLVMFn
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
@@ -467,12 +470,12 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error)
|
||||
wrapper: wrapper,
|
||||
receiverType: receiverType,
|
||||
})
|
||||
return wrapper, nil
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||
// see that function for details.
|
||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
|
||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||
wrapper := state.wrapper
|
||||
fn := state.fn
|
||||
receiverType := state.receiverType
|
||||
@@ -482,10 +485,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
// add debug info if needed
|
||||
if c.Debug {
|
||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||
difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
@@ -493,28 +493,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
|
||||
var receiverPtr llvm.Value
|
||||
if c.targetData.TypeAllocSize(receiverType) > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// The receiver is passed in using a pointer. We have to load it here
|
||||
// and pass it by value to the real function.
|
||||
|
||||
// Load the underlying value.
|
||||
receiverPtrType := llvm.PointerType(receiverType, 0)
|
||||
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
|
||||
} else {
|
||||
// The value is stored in the interface, but it is of type struct which
|
||||
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to
|
||||
// receive the struct as parameter, expand it, and pass it on to the
|
||||
// real function.
|
||||
|
||||
// Cast the passed-in i8* to the struct value (using an alloca) and
|
||||
// extract its values.
|
||||
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
|
||||
c.builder.CreateStore(wrapper.Param(0), alloca)
|
||||
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
|
||||
}
|
||||
|
||||
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
|
||||
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||
@@ -523,6 +502,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||
c.builder.CreateRet(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -22,6 +24,65 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
return uses
|
||||
}
|
||||
|
||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||
// at the end of the current block.
|
||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) llvm.Value {
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
||||
if entryBlock.FirstInstruction().IsNil() {
|
||||
c.builder.SetInsertPointAtEnd(entryBlock)
|
||||
} else {
|
||||
c.builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
||||
}
|
||||
alloca := c.builder.CreateAlloca(t, name)
|
||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
return alloca
|
||||
}
|
||||
|
||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
||||
// lifetime start infromation in the IR signalling that the alloca won't be used
|
||||
// before this point.
|
||||
//
|
||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
||||
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
alloca = c.createEntryBlockAlloca(t, name)
|
||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||
return
|
||||
}
|
||||
|
||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
||||
// createTemporaryAlloca.
|
||||
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, ptr}, "")
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
// first if it doesn't exist yet.
|
||||
func (c *Compiler) getLifetimeStartFunc() llvm.Value {
|
||||
fn := c.mod.NamedFunction("llvm.lifetime.start.p0i8")
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.start.p0i8", fnType)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
||||
// first if it doesn't exist yet.
|
||||
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
||||
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||
// after afterInst are moved into a new basic block (created right after the
|
||||
// current one) with the given name.
|
||||
@@ -93,3 +154,48 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
|
||||
|
||||
return newBlock
|
||||
}
|
||||
|
||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
||||
// contents, and returns the global.
|
||||
// Note that it is left with the default linkage etc., you should set
|
||||
// linkage/constant/etc properties yourself.
|
||||
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
|
||||
buf := reflect.ValueOf(bufItf)
|
||||
globalType := llvm.ArrayType(elementType, buf.Len())
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
for i := 0; i < buf.Len(); i++ {
|
||||
ch := buf.Index(i).Uint()
|
||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
||||
}
|
||||
global.SetInitializer(value)
|
||||
return global
|
||||
}
|
||||
|
||||
// getGlobalBytes returns the slice contained in the array of the provided
|
||||
// global. It can recover the bytes originally created using makeGlobalArray, if
|
||||
// makeGlobalArray was given a byte slice.
|
||||
func getGlobalBytes(global llvm.Value) []byte {
|
||||
value := global.Initializer()
|
||||
buf := make([]byte, value.Type().ArrayLength())
|
||||
for i := range buf {
|
||||
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
// replaceGlobalByteWithArray replaces a global integer type in the module with
|
||||
// an integer array, using a GEP to make the types match. It is a convenience
|
||||
// function used for creating reflection sidetables, for example.
|
||||
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
|
||||
oldGlobal := c.mod.NamedGlobal(name)
|
||||
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
|
||||
gep := llvm.ConstGEP(global, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
oldGlobal.ReplaceAllUsesWith(gep)
|
||||
oldGlobal.EraseFromParentAsGlobal()
|
||||
global.SetName(name)
|
||||
return global
|
||||
}
|
||||
|
||||
+33
-22
@@ -10,12 +10,14 @@ import (
|
||||
)
|
||||
|
||||
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
llvmValueType, err := c.getLLVMType(valueType)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
||||
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||
llvmValueType := c.getLLVMType(valueType)
|
||||
|
||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||
// will be zeroed by the hashmap get implementation if the key is not
|
||||
// present in the map.
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
|
||||
// Do the lookup. How it is done depends on the key type.
|
||||
var commaOkValue llvm.Value
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
@@ -23,15 +25,24 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||
// growth.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, mapKeyAlloca)
|
||||
// Fetch the value from the hashmap.
|
||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
} else {
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Load the resulting value from the hashmap. The value is set to the zero
|
||||
// value if the key doesn't exist in the hashmap.
|
||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
||||
@@ -42,27 +53,25 @@ 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) error {
|
||||
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
|
||||
c.builder.CreateStore(value, valueAlloca)
|
||||
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valuePtr}
|
||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
return nil
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
c.emitLifetimeEnd(valuePtr, valueSize)
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||
@@ -73,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||
params := []llvm.Value{m, keyPtr}
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +121,8 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||
switch keyType := keyType.(type) {
|
||||
case *types.Basic:
|
||||
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
||||
case *types.Pointer:
|
||||
return true
|
||||
case *types.Struct:
|
||||
for i := 0; i < keyType.NumFields(); i++ {
|
||||
fieldType := keyType.Field(i).Type().Underlying()
|
||||
|
||||
+61
-143
@@ -3,6 +3,7 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -18,6 +19,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
builder.AddCoroutinePassesToExtensionPoints()
|
||||
|
||||
if c.PanicStrategy == "trap" {
|
||||
c.replacePanicsWithTrap() // -panic=trap
|
||||
}
|
||||
|
||||
// run a check of all of our code
|
||||
if c.VerifyIR {
|
||||
c.checkModule()
|
||||
}
|
||||
|
||||
// Run function passes for each function.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||
defer funcPasses.Dispose()
|
||||
@@ -39,10 +49,11 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
c.OptimizeMaps()
|
||||
transform.OptimizeMaps(c.mod)
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
@@ -50,9 +61,25 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
c.OptimizeStringToBytes()
|
||||
|
||||
// Lower runtime.isnil calls to regular nil comparisons.
|
||||
isnil := c.mod.NamedFunction("runtime.isnil")
|
||||
if !isnil.IsNil() {
|
||||
for _, use := range getUses(isnil) {
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
ptr := use.Operand(0)
|
||||
if !ptr.IsABitCastInst().IsNil() {
|
||||
ptr = ptr.Operand(0)
|
||||
}
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
use.ReplaceAllUsesWith(icmp)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -60,6 +87,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -79,6 +107,15 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
}
|
||||
|
||||
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
||||
for _, name := range functionsUsedInTransforms {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
|
||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||
// doesn't get lowered.
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
@@ -92,47 +129,32 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
builder.Populate(modPasses)
|
||||
modPasses.Run(c.mod)
|
||||
|
||||
hasGCPass := c.addGlobalsBitmap()
|
||||
hasGCPass = c.makeGCStackSlots() || hasGCPass
|
||||
if hasGCPass {
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("GC pass caused a verification failure")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
|
||||
// -panic=trap intrinsic.
|
||||
func (c *Compiler) replacePanicsWithTrap() {
|
||||
trap := c.mod.NamedFunction("llvm.trap")
|
||||
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !unknownUses {
|
||||
// This map can be entirely removed, as it is only created but never
|
||||
// used.
|
||||
for _, inst := range updateInsts {
|
||||
inst.EraseFromParentAsInstruction()
|
||||
for _, use := range getUses(fn) {
|
||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||
panic("expected use of a panic function to be a call")
|
||||
}
|
||||
makeInst.EraseFromParentAsInstruction()
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
c.builder.CreateCall(trap, nil, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,110 +209,6 @@ 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 {
|
||||
// Call only escapes when the (pointer) parameter is not marked
|
||||
// "nocapture". This flag means that the parameter does not escape
|
||||
// the give function.
|
||||
if use.CalledValue().IsAFunction() != nilValue {
|
||||
if use.CalledValue().IsDeclaration() {
|
||||
// Kind of dirty: assume external functions don't let
|
||||
// pointers escape.
|
||||
// TODO: introduce //go:noescape that sets the 'nocapture'
|
||||
// flag on each input parameter.
|
||||
continue
|
||||
}
|
||||
}
|
||||
if !c.hasFlag(use, value, "nocapture") {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// Unknown instruction, might escape.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// does not escape
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the given value (which is of pointer type) is never stored to.
|
||||
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
|
||||
+407
-74
@@ -1,10 +1,43 @@
|
||||
package compiler
|
||||
|
||||
// This file has some compiler support for run-time reflection using the reflect
|
||||
// package. In particular, it encodes type information in type codes in such a
|
||||
// way that the reflect package can decode the type from this information.
|
||||
// Where needed, it also adds some side tables for looking up more information
|
||||
// about a type, when that information cannot be stored directly in the type
|
||||
// code.
|
||||
//
|
||||
// Go has 26 different type kinds.
|
||||
//
|
||||
// Type kinds are subdivided in basic types (see the list of basicTypes below)
|
||||
// that are mostly numeric literals and non-basic (or "complex") types that are
|
||||
// more difficult to encode. These non-basic types come in two forms:
|
||||
// * Prefix types (pointer, slice, interface, channel): these just add
|
||||
// something to an existing type. For example, a pointer like *int just adds
|
||||
// the fact that it's a pointer to an existing type (int).
|
||||
// These are encoded efficiently by adding a prefix to a type code.
|
||||
// * Types with multiple fields (struct, array, func, map). All of these have
|
||||
// multiple fields contained within. Most obviously structs can contain many
|
||||
// types as fields. Also arrays contain not just the element type but also
|
||||
// the length parameter which can be any arbitrary number and thus may not
|
||||
// fit in a type code.
|
||||
// These types are encoded using side tables.
|
||||
//
|
||||
// This distinction is also important for how named types are encoded. At the
|
||||
// moment, named basic type just get a unique number assigned while named
|
||||
// non-basic types have their underlying type stored in a sidetable.
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"go/ast"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A list of basic types and their numbers. This list should be kept in sync
|
||||
// with the list of Kind constants of type.go in the reflect package.
|
||||
var basicTypes = map[string]int64{
|
||||
"bool": 1,
|
||||
"int": 2,
|
||||
@@ -26,6 +59,72 @@ var basicTypes = map[string]int64{
|
||||
"unsafeptr": 18,
|
||||
}
|
||||
|
||||
// A list of non-basic types. Adding 19 to this number will give the Kind as
|
||||
// used in src/reflect/types.go, and it must be kept in sync with that list.
|
||||
var nonBasicTypes = map[string]int64{
|
||||
"chan": 0,
|
||||
"interface": 1,
|
||||
"pointer": 2,
|
||||
"slice": 3,
|
||||
"array": 4,
|
||||
"func": 5,
|
||||
"map": 6,
|
||||
"struct": 7,
|
||||
}
|
||||
|
||||
// typeCodeAssignmentState keeps some global state around for type code
|
||||
// assignments, used to assign one unique type code to each Go type.
|
||||
type typeCodeAssignmentState struct {
|
||||
// An integer that's incremented each time it's used to give unique IDs to
|
||||
// type codes that are not yet fully supported otherwise by the reflect
|
||||
// package (or are simply unused in the compiled program).
|
||||
fallbackIndex int
|
||||
|
||||
// This is the length of an uintptr. Only used occasionally to know whether
|
||||
// a given number can be encoded as a varint.
|
||||
uintptrLen int
|
||||
|
||||
// Map of named types to their type code. It is important that named types
|
||||
// get unique IDs for each type.
|
||||
namedBasicTypes map[string]int
|
||||
namedNonBasicTypes map[string]int
|
||||
|
||||
// Map of array types to their type code.
|
||||
arrayTypes map[string]int
|
||||
arrayTypesSidetable []byte
|
||||
needsArrayTypesSidetable bool
|
||||
|
||||
// Map of struct types to their type code.
|
||||
structTypes map[string]int
|
||||
structTypesSidetable []byte
|
||||
needsStructNamesSidetable bool
|
||||
|
||||
// Map of struct names and tags to their name string.
|
||||
structNames map[string]int
|
||||
structNamesSidetable []byte
|
||||
needsStructTypesSidetable bool
|
||||
|
||||
// This byte array is stored in reflect.namedNonBasicTypesSidetable and is
|
||||
// used at runtime to get details about a named non-basic type.
|
||||
// Entries are varints (see makeVarint below and readVarint in
|
||||
// reflect/sidetables.go for the encoding): one varint per entry. The
|
||||
// integers in namedNonBasicTypes are indices into this array. Because these
|
||||
// are varints, most type codes are really small (just one byte).
|
||||
//
|
||||
// Note that this byte buffer is not created when it is not needed
|
||||
// (reflect.namedNonBasicTypesSidetable has no uses), see
|
||||
// needsNamedTypesSidetable.
|
||||
namedNonBasicTypesSidetable []uint64
|
||||
|
||||
// This indicates whether namedNonBasicTypesSidetable needs to be created at
|
||||
// all. If it is false, namedNonBasicTypesSidetable will contain simple
|
||||
// monotonically increasing numbers.
|
||||
needsNamedNonBasicTypesSidetable bool
|
||||
}
|
||||
|
||||
// assignTypeCodes is used to assign a type code to each type in the program
|
||||
// that is ever stored in an interface. It tries to use the smallest possible
|
||||
// numbers to make the code that works with interfaces as small as possible.
|
||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||
if fn.IsNil() {
|
||||
@@ -38,13 +137,21 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
}
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
fallbackIndex := 1
|
||||
namedTypes := make(map[string]int)
|
||||
state := typeCodeAssignmentState{
|
||||
fallbackIndex: 1,
|
||||
uintptrLen: c.uintptrType.IntTypeWidth(),
|
||||
namedBasicTypes: make(map[string]int),
|
||||
namedNonBasicTypes: make(map[string]int),
|
||||
arrayTypes: make(map[string]int),
|
||||
structTypes: make(map[string]int),
|
||||
structNames: make(map[string]int),
|
||||
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
||||
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
||||
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||
}
|
||||
for _, t := range typeSlice {
|
||||
if t.name[:5] != "type:" {
|
||||
panic("expected type name to start with 'type:'")
|
||||
}
|
||||
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||
num := state.getTypeCodeNum(t.typecode)
|
||||
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||
// TODO: support this in some way, using a side table for example.
|
||||
// That's less efficient but better than not working at all.
|
||||
@@ -54,25 +161,41 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
}
|
||||
|
||||
// Only create this sidetable when it is necessary.
|
||||
if state.needsNamedNonBasicTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsArrayTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructNamesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
// 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:]
|
||||
class, value := getClassAndValueFromTypeCode(typecode)
|
||||
name := ""
|
||||
if value[0] == '~' {
|
||||
name = value[1:strings.IndexByte(value, ':')]
|
||||
value = value[len(name)+2:]
|
||||
if class == "named" {
|
||||
name = value
|
||||
typecode = llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||
class, value = getClassAndValueFromTypeCode(typecode)
|
||||
}
|
||||
if class == "basic" {
|
||||
// Basic types follow the following bit pattern:
|
||||
@@ -81,76 +204,286 @@ func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[
|
||||
// upper bits are used to indicate the named type.
|
||||
num, ok := basicTypes[value]
|
||||
if !ok {
|
||||
panic("invalid basic type: " + id)
|
||||
panic("invalid basic type: " + value)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||
num |= int64(state.getBasicNamedTypeNum(name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Complex types use the following bit pattern:
|
||||
// Non-baisc types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the complex type (any non-basic type). The upper
|
||||
// bits contain whatever the type contains. Types that wrap a single
|
||||
// other type (channel, interface, pointer, slice) just contain the bits
|
||||
// of the wrapped type. Other types (like struct) have a different
|
||||
// method of encoding the contents of the type.
|
||||
var num *big.Int
|
||||
// where xxx indicates the non-basic type. The upper bits contain
|
||||
// whatever the type contains. Types that wrap a single other type
|
||||
// (channel, interface, pointer, slice) just contain the bits of the
|
||||
// wrapped type. Other types (like struct) need more fields and thus
|
||||
// cannot be encoded as a simple prefix.
|
||||
var classNumber int64
|
||||
switch class {
|
||||
case "chan":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 0
|
||||
case "interface":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 1
|
||||
case "pointer":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 2
|
||||
case "slice":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 3
|
||||
case "array":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 4
|
||||
case "func":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 5
|
||||
case "map":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 6
|
||||
case "struct":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 7
|
||||
default:
|
||||
panic("unknown type kind: " + id)
|
||||
}
|
||||
if name == "" {
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
if n, ok := nonBasicTypes[class]; ok {
|
||||
classNumber = n
|
||||
} else {
|
||||
// TODO: store num in a sidetable
|
||||
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
panic("unknown type kind: " + class)
|
||||
}
|
||||
var num *big.Int
|
||||
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
|
||||
if name == "" {
|
||||
num = state.getNonBasicTypeCode(class, typecode)
|
||||
} else {
|
||||
// We must return a named type here. But first check whether it
|
||||
// has already been defined.
|
||||
if index, ok := state.namedNonBasicTypes[name]; ok {
|
||||
num := big.NewInt(int64(index))
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
|
||||
return num
|
||||
}
|
||||
lowBits |= 1 << 4 // set the 'n' bit (see above)
|
||||
if !state.needsNamedNonBasicTypesSidetable {
|
||||
// Use simple small integers in this case, to make these numbers
|
||||
// smaller.
|
||||
index := len(state.namedNonBasicTypes) + 1
|
||||
state.namedNonBasicTypes[name] = index
|
||||
num = big.NewInt(int64(index))
|
||||
} else {
|
||||
// We need to store full type information.
|
||||
// First allocate a number in the named non-basic type
|
||||
// sidetable.
|
||||
index := len(state.namedNonBasicTypesSidetable)
|
||||
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
|
||||
state.namedNonBasicTypes[name] = index
|
||||
// Get the typecode of the underlying type (which could be the
|
||||
// element type in the case of pointers, for example).
|
||||
num = state.getNonBasicTypeCode(class, typecode)
|
||||
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
||||
panic("cannot store value in sidetable")
|
||||
}
|
||||
// Now update the side table with the number we just
|
||||
// determined. We need this multi-step approach to avoid stack
|
||||
// overflow due to adding types recursively in the case of
|
||||
// linked lists (a pointer which points to a struct that
|
||||
// contains that same pointer).
|
||||
state.namedNonBasicTypesSidetable[index] = num.Uint64()
|
||||
num = big.NewInt(int64(index))
|
||||
}
|
||||
}
|
||||
// Concatenate the 'num' and 'lowBits' bitstrings.
|
||||
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||
// type. If the name already has a number that number is returned, else a new
|
||||
// number is returned. The number is always non-zero.
|
||||
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||
if num, ok := namedTypes[name]; ok {
|
||||
return num
|
||||
} else {
|
||||
num = len(namedTypes) + 1
|
||||
namedTypes[name] = num
|
||||
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
|
||||
// the type code used there in the type code.
|
||||
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
|
||||
switch class {
|
||||
case "chan", "pointer", "slice":
|
||||
// Prefix-style type kinds. The upper bits contain the element type.
|
||||
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
|
||||
return state.getTypeCodeNum(sub)
|
||||
case "array":
|
||||
// An array is basically a pair of (typecode, length) stored in a
|
||||
// sidetable.
|
||||
return big.NewInt(int64(state.getArrayTypeNum(typecode)))
|
||||
case "struct":
|
||||
// More complicated type kind. The upper bits contain the index to the
|
||||
// struct type in the struct types sidetable.
|
||||
return big.NewInt(int64(state.getStructTypeNum(typecode)))
|
||||
default:
|
||||
// Type has not yet been implemented, so fall back by using a unique
|
||||
// number.
|
||||
num := big.NewInt(int64(state.fallbackIndex))
|
||||
state.fallbackIndex++
|
||||
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)]
|
||||
}
|
||||
|
||||
+39
-3
@@ -63,6 +63,12 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
|
||||
|
||||
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||
offsets := make([]int64, len(fields))
|
||||
if len(fields) > 1 && fields[0].Name() == "C union" {
|
||||
// This struct contains the magic "C union" field which indicates that
|
||||
// this is actually a union from CGo.
|
||||
// All fields in the union start at 0 so return that.
|
||||
return offsets // all fields are still set to 0
|
||||
}
|
||||
var o int64
|
||||
for i, f := range fields {
|
||||
a := s.Alignof(f.Type())
|
||||
@@ -107,6 +113,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
if k == types.Uintptr {
|
||||
return s.PtrSize
|
||||
}
|
||||
if k == types.UnsafePointer {
|
||||
return s.PtrSize
|
||||
}
|
||||
panic("unknown basic type: " + t.String())
|
||||
case *types.Array:
|
||||
n := t.Len()
|
||||
@@ -125,11 +134,38 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
return 0
|
||||
}
|
||||
fields := make([]*types.Var, t.NumFields())
|
||||
maxAlign := int64(1)
|
||||
for i := range fields {
|
||||
fields[i] = t.Field(i)
|
||||
field := t.Field(i)
|
||||
fields[i] = field
|
||||
al := s.Alignof(field.Type())
|
||||
if al > maxAlign {
|
||||
maxAlign = al
|
||||
}
|
||||
}
|
||||
if fields[0].Name() == "C union" {
|
||||
// Magic field that indicates this is a CGo union and not a struct.
|
||||
// The size is the biggest element, aligned to the element with the
|
||||
// biggest alignment. This is not necessarily the same, for example
|
||||
// in the following union:
|
||||
// union { int32_t l; int16_t s[3] }
|
||||
maxSize := int64(0)
|
||||
for _, field := range fields[1:] {
|
||||
si := s.Sizeof(field.Type())
|
||||
if si > maxSize {
|
||||
maxSize = si
|
||||
}
|
||||
}
|
||||
return align(maxSize, maxAlign)
|
||||
} else {
|
||||
// This is a regular struct.
|
||||
// Pick the size that fits this struct and add some alignment. Some
|
||||
// structs have some extra padding at the end which should also be
|
||||
// taken care of:
|
||||
// struct { int32 n; byte b }
|
||||
offsets := s.Offsetsof(fields)
|
||||
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
||||
}
|
||||
offsets := s.Offsetsof(fields)
|
||||
return offsets[n-1] + s.Sizeof(fields[n-1].Type())
|
||||
case *types.Interface:
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
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]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-20
@@ -4,7 +4,6 @@ package compiler
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -14,7 +13,7 @@ import (
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
||||
num := c.getValue(frame, call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64":
|
||||
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num += 0x2000000
|
||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
@@ -51,10 +50,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{r12}",
|
||||
"{r13}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
@@ -62,6 +58,32 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "386" && c.GOOS == "linux":
|
||||
// Sources:
|
||||
// syscall(2) man page
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
||||
constraints := "={eax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"{ebx}",
|
||||
"{ecx}",
|
||||
"{edx}",
|
||||
"{esi}",
|
||||
"{edi}",
|
||||
"{ebp}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
@@ -80,14 +102,11 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
@@ -113,14 +132,11 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
llvmValue, err := c.parseExpr(frame, arg)
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
@@ -149,7 +165,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
@@ -165,7 +181,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package compiler
|
||||
|
||||
// This file implements volatile loads/stores in runtime/volatile.LoadT and
|
||||
// runtime/volatile.StoreT as compiler builtins.
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
val := c.builder.CreateLoad(addr, "")
|
||||
val.SetVolatile(true)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
val := c.getValue(frame, instr.Args[1])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
store := c.builder.CreateStore(val, addr)
|
||||
store.SetVolatile(true)
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package compiler
|
||||
|
||||
// This file contains utility functions to pack and unpack sets of values. It
|
||||
// can take in a list of values and tries to store it efficiently in the pointer
|
||||
// itself if possible and legal.
|
||||
|
||||
import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitPointerPack packs the list of values into a single pointer value using
|
||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||
// pointer value directly. It returns the pointer with the packed data.
|
||||
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
valueTypes := make([]llvm.Type, len(values))
|
||||
for i, value := range values {
|
||||
valueTypes[i] = value.Type()
|
||||
}
|
||||
packedType := c.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Allocate memory for the packed data.
|
||||
var packedAlloc, packedHeapAlloc llvm.Value
|
||||
size := c.targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
return llvm.ConstPointerNull(c.i8ptrType)
|
||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||
return c.builder.CreateBitCast(values[0], c.i8ptrType, "pack.ptr")
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Packed data fits in a pointer, so store it directly inside the
|
||||
// pointer.
|
||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||
// Try to keep this cast in SSA form.
|
||||
return c.builder.CreateIntToPtr(values[0], c.i8ptrType, "pack.int")
|
||||
}
|
||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||
packedAlloc, _, _ = c.createTemporaryAlloca(packedType, "")
|
||||
} else {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
if c.needsStackObjects() {
|
||||
c.trackPointer(packedHeapAlloc)
|
||||
}
|
||||
packedAlloc = c.builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
}
|
||||
// Store all values in the alloca or heap pointer.
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
c.builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
if packedHeapAlloc.IsNil() {
|
||||
// Load value (as *i8) from the alloca.
|
||||
packedAlloc = c.builder.CreateBitCast(packedAlloc, llvm.PointerType(c.i8ptrType, 0), "")
|
||||
result := c.builder.CreateLoad(packedAlloc, "")
|
||||
packedPtr := c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
||||
packedSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||
c.emitLifetimeEnd(packedPtr, packedSize)
|
||||
return result
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an *i8.
|
||||
return packedHeapAlloc
|
||||
}
|
||||
}
|
||||
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
packedType := c.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Get a correctly-typed pointer to the packed data.
|
||||
var packedAlloc, packedRawAlloc llvm.Value
|
||||
size := c.targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
// No data to unpack.
|
||||
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
||||
// A single pointer is always stored directly.
|
||||
return []llvm.Value{c.builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||
} else if size <= c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
// Packed data stored directly in pointer.
|
||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||
// Keep this cast in SSA form.
|
||||
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||
}
|
||||
// Fallback: load it using an alloca.
|
||||
packedRawAlloc, _, _ = c.createTemporaryAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
||||
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||
} else {
|
||||
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
||||
// correct pointer type.
|
||||
packedAlloc = c.builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
||||
}
|
||||
// Load each value from the packed data.
|
||||
values := make([]llvm.Value, len(valueTypes))
|
||||
for i, valueType := range valueTypes {
|
||||
if c.targetData.TypeAllocSize(valueType) == 0 {
|
||||
// This value has length zero, so there's nothing to load.
|
||||
values[i] = llvm.ConstNull(valueType)
|
||||
continue
|
||||
}
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := c.builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
values[i] = c.builder.CreateLoad(gep, "")
|
||||
}
|
||||
if !packedRawAlloc.IsNil() {
|
||||
allocPtr := c.builder.CreateBitCast(packedRawAlloc, c.i8ptrType, "")
|
||||
allocSize := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(c.uintptrType), false)
|
||||
c.emitLifetimeEnd(allocPtr, allocSize)
|
||||
}
|
||||
return values
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
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=
|
||||
+96
-18
@@ -86,7 +86,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||
alloca.SetInitializer(getZeroValue(allocType))
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
@@ -95,7 +95,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() || operand.Underlying.Opcode() == llvm.BitCast {
|
||||
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
|
||||
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
||||
} else {
|
||||
value = operand.Load()
|
||||
@@ -176,6 +176,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
||||
// Special case for runtime.trackPointer calls.
|
||||
// Note: this might not be entirely sound in some rare cases
|
||||
// where the map is stored in a dirty global.
|
||||
uses := getUses(inst)
|
||||
if len(uses) == 1 {
|
||||
use := uses[0]
|
||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It is not possible in Go to bitcast a map value to a pointer.
|
||||
panic("unimplemented: bitcast of map")
|
||||
}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
@@ -184,6 +198,25 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.IntPredicate()
|
||||
if predicate == llvm.IntEQ && lhs.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
// Unfortunately, the const propagation in the IR builder
|
||||
// doesn't handle pointer compares of inttoptr values. So we
|
||||
// implement it manually here.
|
||||
lhsNil, ok1 := isPointerNil(lhs)
|
||||
rhsNil, ok2 := isPointerNil(rhs)
|
||||
if ok1 && ok2 {
|
||||
if lhsNil && rhsNil {
|
||||
// Both are nil, so this icmp is always evaluated to true.
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
||||
continue
|
||||
}
|
||||
if lhsNil != rhsNil {
|
||||
// Only one of them is nil, so this comparison must return false.
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
|
||||
case !inst.IsAFCmpInst().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
@@ -220,7 +253,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
allocType = llvm.ArrayType(allocType, elementCount)
|
||||
}
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
||||
alloc.SetInitializer(getZeroValue(allocType))
|
||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
@@ -279,7 +312,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
stringType := fr.Mod.GetTypeByName("runtime._string")
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := getZeroValue(stringType)
|
||||
ret := llvm.ConstNull(stringType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
@@ -302,14 +335,55 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
sliceType := inst.Type()
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := getZeroValue(sliceType)
|
||||
ret := llvm.ConstNull(sliceType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.makeInterface":
|
||||
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)}
|
||||
case callee.Name() == "runtime.interfaceImplements":
|
||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||
panic("interp: expected typecode to be a ptrtoint")
|
||||
}
|
||||
typecode = typecode.Operand(0)
|
||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
||||
}
|
||||
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
||||
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
||||
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
||||
panic("interp: expected method set to be a constant gep")
|
||||
}
|
||||
methodSet = methodSet.Operand(0).Initializer()
|
||||
|
||||
// Make a set of all the methods on the concrete type, for
|
||||
// easier checking in the next step.
|
||||
definedMethods := map[string]struct{}{}
|
||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
||||
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
||||
definedMethods[name] = struct{}{}
|
||||
}
|
||||
// Check whether all interface methods are also in the list
|
||||
// of defined methods calculated above.
|
||||
implements := uint64(1) // i1 true
|
||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
||||
if _, ok := definedMethods[name]; !ok {
|
||||
// There is a method on the interface that is not
|
||||
// implemented by the type.
|
||||
implements = 0 // i1 false
|
||||
break
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
||||
case callee.Name() == "runtime.nanotime":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case callee.Name() == "runtime.trackPointer":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
@@ -422,17 +496,21 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
}
|
||||
thenBB := inst.Operand(1)
|
||||
elseBB := inst.Operand(2)
|
||||
if !cond.IsConstant() {
|
||||
if !cond.IsAInstruction().IsNil() {
|
||||
return nil, nil, errors.New("interp: branch on a non-constant")
|
||||
} else {
|
||||
switch cond.ZExtValue() {
|
||||
case 0: // false
|
||||
return nil, []llvm.Value{thenBB}, nil // then
|
||||
case 1: // true
|
||||
return nil, []llvm.Value{elseBB}, nil // else
|
||||
default:
|
||||
panic("branch was not true or false")
|
||||
}
|
||||
}
|
||||
if !cond.IsAConstantExpr().IsNil() {
|
||||
// This may happen when the instruction builder could not
|
||||
// const-fold some instructions.
|
||||
return nil, nil, errors.New("interp: branch on a non-const-propagated constant expression")
|
||||
}
|
||||
switch cond {
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
||||
return nil, []llvm.Value{thenBB}, nil // then
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
||||
return nil, []llvm.Value{elseBB}, nil // else
|
||||
default:
|
||||
panic("branch was not true or false")
|
||||
}
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||
// unconditional branch (goto)
|
||||
|
||||
@@ -18,7 +18,6 @@ type Eval struct {
|
||||
TargetData llvm.TargetData
|
||||
Debug bool
|
||||
builder llvm.Builder
|
||||
dibuilder *llvm.DIBuilder
|
||||
dirtyGlobals map[llvm.Value]struct{}
|
||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||
}
|
||||
@@ -38,7 +37,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
bb := initAll.EntryBasicBlock()
|
||||
@@ -49,7 +47,6 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
|
||||
+19
-6
@@ -28,8 +28,17 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
case "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.nanotime":
|
||||
// Fixed value at compile time.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}
|
||||
case "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
@@ -81,11 +90,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
continue
|
||||
}
|
||||
if child.IsDeclaration() {
|
||||
switch child.Name() {
|
||||
case "runtime.makeInterface":
|
||||
// Can be interpreted so does not have side effects.
|
||||
continue
|
||||
}
|
||||
// External function call. Assume only limited side effects
|
||||
// (no affected globals, etc.).
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
@@ -107,7 +111,16 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
case llvm.Load, llvm.Store:
|
||||
case llvm.Load:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
}
|
||||
case llvm.Store:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
|
||||
+26
-44
@@ -16,50 +16,6 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
return uses
|
||||
}
|
||||
|
||||
// Return a zero LLVM value for any LLVM type. Setting this value as an
|
||||
// initializer has the same effect as setting 'zeroinitializer' on a value.
|
||||
// Sadly, I haven't found a way to do it directly with the Go API but this works
|
||||
// just fine.
|
||||
func getZeroValue(typ llvm.Type) llvm.Value {
|
||||
switch typ.TypeKind() {
|
||||
case llvm.ArrayTypeKind:
|
||||
subTyp := typ.ElementType()
|
||||
subVal := getZeroValue(subTyp)
|
||||
vals := make([]llvm.Value, typ.ArrayLength())
|
||||
for i := range vals {
|
||||
vals[i] = subVal
|
||||
}
|
||||
return llvm.ConstArray(subTyp, vals)
|
||||
case llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return llvm.ConstFloat(typ, 0.0)
|
||||
case llvm.IntegerTypeKind:
|
||||
return llvm.ConstInt(typ, 0, false)
|
||||
case llvm.PointerTypeKind:
|
||||
return llvm.ConstPointerNull(typ)
|
||||
case llvm.StructTypeKind:
|
||||
types := typ.StructElementTypes()
|
||||
vals := make([]llvm.Value, len(types))
|
||||
for i, subTyp := range types {
|
||||
val := getZeroValue(subTyp)
|
||||
vals[i] = val
|
||||
}
|
||||
if typ.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(typ, vals)
|
||||
} else {
|
||||
return typ.Context().ConstStruct(vals, false)
|
||||
}
|
||||
case llvm.VectorTypeKind:
|
||||
zero := getZeroValue(typ.ElementType())
|
||||
vals := make([]llvm.Value, typ.VectorSize())
|
||||
for i := range vals {
|
||||
vals[i] = zero
|
||||
}
|
||||
return llvm.ConstVector(vals, false)
|
||||
default:
|
||||
panic("interp: unknown LLVM type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
// getStringBytes loads the byte slice of a Go string represented as a
|
||||
// {ptr, len} pair.
|
||||
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
||||
@@ -94,3 +50,29 @@ func isScalar(t llvm.Type) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// isPointerNil returns whether this is a nil pointer or not. The ok value
|
||||
// indicates whether the result is certain: if it is false the result boolean is
|
||||
// not valid.
|
||||
func isPointerNil(v llvm.Value) (result bool, ok bool) {
|
||||
if !v.IsAConstantExpr().IsNil() {
|
||||
switch v.Opcode() {
|
||||
case llvm.IntToPtr:
|
||||
// Whether a constant inttoptr is nil is easy to
|
||||
// determine.
|
||||
operand := v.Operand(0)
|
||||
if operand.IsConstant() {
|
||||
return operand.ZExtValue() == 0, true
|
||||
}
|
||||
case llvm.BitCast, llvm.GetElementPtr:
|
||||
// These const instructions are just a kind of wrappers for the
|
||||
// underlying pointer.
|
||||
return isPointerNil(v.Operand(0))
|
||||
}
|
||||
}
|
||||
if !v.IsAConstantPointerNull().IsNil() {
|
||||
// A constant pointer null is always null, of course.
|
||||
return true, true
|
||||
}
|
||||
return false, false // not valid
|
||||
}
|
||||
|
||||
+2
-2
@@ -162,7 +162,7 @@ func (v *MapValue) newBucket() llvm.Value {
|
||||
llvm.ArrayType(v.KeyType, 8), // key type
|
||||
llvm.ArrayType(v.ValueType, 8), // value type
|
||||
}, false)
|
||||
bucketValue := getZeroValue(bucketType)
|
||||
bucketValue := llvm.ConstNull(bucketType)
|
||||
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
|
||||
bucket.SetInitializer(bucketValue)
|
||||
bucket.SetLinkage(llvm.InternalLinkage)
|
||||
@@ -311,7 +311,7 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
||||
|
||||
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
||||
v.KeyType = keyType
|
||||
key := getZeroValue(keyType)
|
||||
key := llvm.ConstNull(keyType)
|
||||
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
|
||||
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package ir
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -23,43 +22,19 @@ type Program struct {
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
}
|
||||
|
||||
// Function or method.
|
||||
type Function struct {
|
||||
*ssa.Function
|
||||
LLVMFn llvm.Value
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
}
|
||||
|
||||
// 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
|
||||
module string // go:wasm-module
|
||||
linkName string // go:linkname, go:export, go:interrupt
|
||||
exported bool // go:export
|
||||
nobounds bool // go:nobounds
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
inline InlineType // go:inline
|
||||
}
|
||||
|
||||
// Interface type that is at some point used in a type assert (to check whether
|
||||
@@ -69,37 +44,27 @@ type Interface struct {
|
||||
Type *types.Interface
|
||||
}
|
||||
|
||||
// Create and intialize a new *Program from a *ssa.Program.
|
||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
comments := map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
for _, file := range pkgInfo.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
case token.TYPE, token.VAR:
|
||||
if len(decl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.TypeSpec: // decl.Tok == token.TYPE
|
||||
id := pkgInfo.Pkg.Path() + "." + spec.Name.Name
|
||||
comments[id] = decl.Doc
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||
comments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
type InlineType int
|
||||
|
||||
// How much to inline.
|
||||
const (
|
||||
// Default behavior. The compiler decides for itself whether any given
|
||||
// function will be inlined. Whether any function is inlined depends on the
|
||||
// optimization level.
|
||||
InlineDefault InlineType = iota
|
||||
|
||||
// Inline hint, just like the C inline keyword (signalled using
|
||||
// //go:inline). The compiler will be more likely to inline this function,
|
||||
// but it is not a guarantee.
|
||||
InlineHint
|
||||
|
||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
||||
// //go:noinline.
|
||||
InlineNone
|
||||
)
|
||||
|
||||
// Create and initialize a new *Program from a *ssa.Program.
|
||||
func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
program := lprogram.LoadSSA()
|
||||
program.Build()
|
||||
|
||||
@@ -171,8 +136,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
LoaderProgram: lprogram,
|
||||
mainPkg: mainPkg,
|
||||
functionMap: make(map[*ssa.Function]*Function),
|
||||
globalMap: make(map[*ssa.Global]*Global),
|
||||
comments: comments,
|
||||
}
|
||||
|
||||
for _, pkg := range packageList {
|
||||
@@ -197,8 +160,6 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
case *ssa.Function:
|
||||
p.addFunction(member)
|
||||
case *ssa.Type:
|
||||
t := &NamedType{Type: member}
|
||||
p.NamedTypes = append(p.NamedTypes, t)
|
||||
methods := getAllMethods(pkg.Prog, member.Type())
|
||||
if !types.IsInterface(member.Type()) {
|
||||
// named type
|
||||
@@ -207,13 +168,7 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
}
|
||||
}
|
||||
case *ssa.Global:
|
||||
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
|
||||
// Ignore. Globals are not handled here.
|
||||
case *ssa.NamedConst:
|
||||
// Ignore: these are already resolved.
|
||||
default:
|
||||
@@ -247,10 +202,6 @@ func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
|
||||
return p.functionMap[ssaFn]
|
||||
}
|
||||
|
||||
func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
|
||||
return p.globalMap[ssaGlobal]
|
||||
}
|
||||
|
||||
func (p *Program) MainPkg() *ssa.Package {
|
||||
return p.mainPkg
|
||||
}
|
||||
@@ -279,6 +230,16 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
f.linkName = parts[1]
|
||||
f.exported = true
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
f.module = parts[1]
|
||||
case "//go:inline":
|
||||
f.inline = InlineHint
|
||||
case "//go:noinline":
|
||||
f.inline = InlineNone
|
||||
case "//go:interrupt":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
@@ -332,6 +293,16 @@ func (f *Function) IsInterrupt() bool {
|
||||
return f.interrupt
|
||||
}
|
||||
|
||||
// Return the inline directive of this function.
|
||||
func (f *Function) Inline() InlineType {
|
||||
return f.inline
|
||||
}
|
||||
|
||||
// Return the module name if not the default.
|
||||
func (f *Function) Module() string {
|
||||
return f.module
|
||||
}
|
||||
|
||||
// Return the link name for this function.
|
||||
func (f *Function) LinkName() string {
|
||||
if f.linkName != "" {
|
||||
@@ -366,72 +337,6 @@ func (f *Function) CName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Parse //go: pragma comments from the source.
|
||||
func (g *Global) parsePragmas(doc *ast.CommentGroup) {
|
||||
for _, comment := range doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
switch parts[0] {
|
||||
case "//go:extern":
|
||||
g.extern = true
|
||||
if len(parts) == 2 {
|
||||
g.linkName = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the link name for this global.
|
||||
func (g *Global) LinkName() string {
|
||||
if g.linkName != "" {
|
||||
return g.linkName
|
||||
}
|
||||
if name := g.CName(); name != "" {
|
||||
return name
|
||||
}
|
||||
return g.RelString(nil)
|
||||
}
|
||||
|
||||
func (g *Global) IsExtern() bool {
|
||||
return g.extern || g.CName() != ""
|
||||
}
|
||||
|
||||
// Return the name of the C global if this is a CGo wrapper. Otherwise, return a
|
||||
// zero-length string.
|
||||
func (g *Global) CName() string {
|
||||
name := g.Name()
|
||||
if strings.HasPrefix(name, "C.") {
|
||||
// created by ../loader/cgo.go
|
||||
return name[2:]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Return true if this named type is annotated with the //go:volatile pragma,
|
||||
// for volatile loads and stores.
|
||||
func (p *Program) IsVolatile(t types.Type) bool {
|
||||
if t, ok := t.(*types.Named); !ok {
|
||||
return false
|
||||
} else {
|
||||
if t.Obj().Pkg() == nil {
|
||||
return false
|
||||
}
|
||||
id := t.Obj().Pkg().Path() + "." + t.Obj().Name()
|
||||
doc := p.comments[id]
|
||||
if doc == nil {
|
||||
return false
|
||||
}
|
||||
for _, line := range doc.List {
|
||||
if strings.TrimSpace(line.Text) == "//go:volatile" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: b6f0a65ac3...5910f3d115
+1
-1
Submodule lib/compiler-rt updated: a4cbb02bca...5bc79797e1
+7
-4
@@ -22,9 +22,9 @@ import "C"
|
||||
// Link invokes a linker with the given name and flags.
|
||||
//
|
||||
// This version uses the built-in linker when trying to use lld.
|
||||
func Link(dir, linker string, flags ...string) error {
|
||||
func Link(linker string, flags ...string) error {
|
||||
switch linker {
|
||||
case "ld.lld", commands["ld.lld"]:
|
||||
case "ld.lld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
@@ -39,7 +39,7 @@ func Link(dir, linker string, flags ...string) error {
|
||||
return errors.New("failed to link using built-in ld.lld")
|
||||
}
|
||||
return nil
|
||||
case "wasm-ld", commands["wasm-ld"]:
|
||||
case "wasm-ld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
@@ -57,10 +57,13 @@ func Link(dir, linker string, flags ...string) error {
|
||||
return nil
|
||||
default:
|
||||
// Fall back to external command.
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -13,10 +13,13 @@ import (
|
||||
// Link invokes a linker with the given name and arguments.
|
||||
//
|
||||
// This version always runs the linker as an external command.
|
||||
func Link(dir, linker string, flags ...string) error {
|
||||
func Link(linker string, flags ...string) error {
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
-414
@@ -1,414 +0,0 @@
|
||||
package loader
|
||||
|
||||
// This file extracts the `import "C"` statement from the source and modifies
|
||||
// the AST for Cgo. It does not use libclang directly (see libclang.go).
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions map[string]*functionInfo
|
||||
globals map[string]*globalInfo
|
||||
typedefs map[string]*typedefInfo
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// globalInfo contains information about a declared global variable in C.
|
||||
type globalInfo struct {
|
||||
typeExpr ast.Expr
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
// in both languages. See addTypeAliases.
|
||||
var cgoAliases = map[string]string{
|
||||
"C.int8_t": "int8",
|
||||
"C.int16_t": "int16",
|
||||
"C.int32_t": "int32",
|
||||
"C.int64_t": "int64",
|
||||
"C.uint8_t": "uint8",
|
||||
"C.uint16_t": "uint16",
|
||||
"C.uint32_t": "uint32",
|
||||
"C.uint64_t": "uint64",
|
||||
"C.uintptr_t": "uintptr",
|
||||
}
|
||||
|
||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||
// somehow from C. This is done by adding some typedefs to get the size of each
|
||||
// type.
|
||||
const cgoTypes = `
|
||||
typedef signed char _Cgo_schar;
|
||||
typedef unsigned char _Cgo_uchar;
|
||||
typedef short _Cgo_short;
|
||||
typedef unsigned short _Cgo_ushort;
|
||||
typedef int _Cgo_int;
|
||||
typedef unsigned int _Cgo_uint;
|
||||
typedef long _Cgo_long;
|
||||
typedef unsigned long _Cgo_ulong;
|
||||
typedef long long _Cgo_longlong;
|
||||
typedef unsigned long long _Cgo_ulonglong;
|
||||
`
|
||||
|
||||
// processCgo extracts the `import "C"` statement from the AST, parses the
|
||||
// comment with libclang, and modifies the AST to use this information.
|
||||
func (p *Package) processCgo(filename string, f *ast.File, cflags []string) error {
|
||||
info := &fileInfo{
|
||||
File: f,
|
||||
filename: filename,
|
||||
functions: map[string]*functionInfo{},
|
||||
globals: map[string]*globalInfo{},
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if len(genDecl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
spec, ok := genDecl.Specs[0].(*ast.ImportSpec)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
path, err := strconv.Unquote(spec.Path.Value)
|
||||
if err != nil {
|
||||
panic("could not parse import path: " + err.Error())
|
||||
}
|
||||
if path != "C" {
|
||||
continue
|
||||
}
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
|
||||
// Stored for later use by generated functions, to use a somewhat sane
|
||||
// source location.
|
||||
info.importCPos = spec.Path.ValuePos
|
||||
|
||||
err = info.parseFragment(cgoComment+cgoTypes, cflags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(p.fset, f)
|
||||
|
||||
// Declare functions found by libclang.
|
||||
info.addFuncDecls()
|
||||
|
||||
// Declare stub function pointer values found by libclang.
|
||||
info.addFuncPtrDecls()
|
||||
|
||||
// Declare globals found by libclang.
|
||||
info.addVarDecls()
|
||||
|
||||
// Forward C types to Go types (like C.uint32_t -> uint32).
|
||||
info.addTypeAliases()
|
||||
|
||||
// Add type declarations for C types, declared using typeef in C.
|
||||
info.addTypedefs()
|
||||
|
||||
// Patch the AST to use the declared types and functions.
|
||||
f = astutil.Apply(f, info.walker, nil).(*ast.File)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
// comment above the `import "C"` statement.
|
||||
func (info *fileInfo) addFuncDecls() {
|
||||
// TODO: replace all uses of importCPos with the real locations from
|
||||
// libclang.
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
fn := info.functions[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "C." + name,
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: info.importCPos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
},
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: arg.name,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: arg.typeExpr,
|
||||
}
|
||||
}
|
||||
info.Decls = append(info.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncPtrDecls creates stub declarations of function pointer values. These
|
||||
// values will later be replaced with the real values in the compiler.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.add unsafe.Pointer
|
||||
// C.mul unsafe.Pointer
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addFuncPtrDecls() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.functions))
|
||||
for name := range info.functions {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// addVarDecls declares external C globals in the Go source.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// var (
|
||||
// C.globalInt int
|
||||
// C.globalBool bool
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addVarDecls() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.VAR,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
names := make([]string, 0, len(info.globals))
|
||||
for name := range info.globals {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := info.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: global.typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// addTypeAliases aliases some built-in Go types with their equivalent C types.
|
||||
// It adds code like the following to the AST:
|
||||
//
|
||||
// type (
|
||||
// C.int8_t = int8
|
||||
// C.int16_t = int16
|
||||
// // ...
|
||||
// )
|
||||
func (info *fileInfo) addTypeAliases() {
|
||||
aliasKeys := make([]string, 0, len(cgoAliases))
|
||||
for key := range cgoAliases {
|
||||
aliasKeys = append(aliasKeys, key)
|
||||
}
|
||||
sort.Strings(aliasKeys)
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
Lparen: info.importCPos,
|
||||
Rparen: info.importCPos,
|
||||
}
|
||||
for _, typeName := range aliasKeys {
|
||||
goTypeName := cgoAliases[typeName]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: info.importCPos,
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: goTypeName,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
func (info *fileInfo) addTypedefs() {
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: info.importCPos,
|
||||
Tok: token.TYPE,
|
||||
}
|
||||
names := make([]string, 0, len(info.typedefs))
|
||||
for name := range info.typedefs {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
typedef := info.typedefs[name]
|
||||
typeName := "C." + name
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
typeName = "C." + name[len("_Cgo_"):]
|
||||
}
|
||||
if _, ok := cgoAliases[typeName]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typedef.typeExpr,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> expressions to literal "C.<something>"
|
||||
// expressions. Such expressions are impossible to write in Go (a dot cannot be
|
||||
// used in the middle of a name) so in practice all C identifiers live in a
|
||||
// separate namespace (no _Cgo_ hacks like in gc).
|
||||
func (info *fileInfo) walker(cursor *astutil.Cursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if _, ok := info.functions[fun.Sel.Name]; ok && x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: "C." + fun.Sel.Name,
|
||||
}
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
x, ok := node.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
name := "C." + node.Sel.Name
|
||||
if _, ok := info.functions[node.Sel.Name]; ok {
|
||||
name += "$funcaddr"
|
||||
}
|
||||
cursor.Replace(&ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package loader
|
||||
|
||||
// This file parses a fragment of C with libclang and stores the result for AST
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-7-dev
|
||||
#include <stdlib.h>
|
||||
|
||||
int tinygo_clang_visitor(CXCursor c, CXCursor parent, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var globalFileInfo *fileInfo
|
||||
|
||||
func (info *fileInfo) parseFragment(fragment string, cflags []string) error {
|
||||
index := C.clang_createIndex(0, 1)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
filenameC := C.CString("cgo-fake.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
unsavedFile := C.struct_CXUnsavedFile{
|
||||
Filename: filenameC,
|
||||
Length: C.ulong(len(fragment)),
|
||||
Contents: fragmentC,
|
||||
}
|
||||
|
||||
// convert Go slice of strings to C array of strings.
|
||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
||||
defer C.free(cmdargsC)
|
||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
||||
for i, cflag := range cflags {
|
||||
s := C.CString(cflag)
|
||||
cmdargs[i] = s
|
||||
defer C.free(unsafe.Pointer(s))
|
||||
}
|
||||
|
||||
var unit C.CXTranslationUnit
|
||||
errCode := C.clang_parseTranslationUnit2(
|
||||
index,
|
||||
filenameC,
|
||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
||||
&unsavedFile, 1, // unsaved files
|
||||
C.CXTranslationUnit_None,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
if C.clang_getNumDiagnostics(unit) != 0 {
|
||||
return errors.New("cgo: libclang cannot parse fragment")
|
||||
}
|
||||
|
||||
if globalFileInfo != nil {
|
||||
// There is a race condition here but that doesn't really matter as it
|
||||
// is a sanity check anyway.
|
||||
panic("libclang.go cannot be used concurrently yet")
|
||||
}
|
||||
globalFileInfo = info
|
||||
defer func() {
|
||||
globalFileInfo = nil
|
||||
}()
|
||||
|
||||
cursor := C.clang_getTranslationUnitCursor(unit)
|
||||
C.clang_visitChildren(cursor, (*[0]byte)(unsafe.Pointer(C.tinygo_clang_visitor)), C.CXClientData(uintptr(0)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//export tinygo_clang_visitor
|
||||
func tinygo_clang_visitor(c, parent C.CXCursor, client_data C.CXClientData) C.int {
|
||||
info := globalFileInfo
|
||||
kind := C.clang_getCursorKind(c)
|
||||
switch kind {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{}
|
||||
info.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
arg := C.clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
if argName == "" {
|
||||
argName = "$" + strconv.Itoa(i)
|
||||
}
|
||||
fn.args = append(fn.args, paramInfo{
|
||||
name: argName,
|
||||
typeExpr: info.makeASTType(argType),
|
||||
})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: info.makeASTType(resultType),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
||||
expr := info.makeASTType(underlyingType)
|
||||
if strings.HasPrefix(name, "_Cgo_") {
|
||||
expr := expr.(*ast.Ident)
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
switch expr.Name {
|
||||
// TODO: plain char (may be signed or unsigned)
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "int8"
|
||||
case 2:
|
||||
expr.Name = "int16"
|
||||
case 4:
|
||||
expr.Name = "int32"
|
||||
case 8:
|
||||
expr.Name = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typeSize {
|
||||
case 1:
|
||||
expr.Name = "uint8"
|
||||
case 2:
|
||||
expr.Name = "uint16"
|
||||
case 4:
|
||||
expr.Name = "uint32"
|
||||
case 8:
|
||||
expr.Name = "uint64"
|
||||
}
|
||||
}
|
||||
}
|
||||
info.typedefs[name] = &typedefInfo{
|
||||
typeExpr: expr,
|
||||
}
|
||||
case C.CXCursor_VarDecl:
|
||||
name := getString(C.clang_getCursorSpelling(c))
|
||||
cursorType := C.clang_getCursorType(c)
|
||||
info.globals[name] = &globalInfo{
|
||||
typeExpr: info.makeASTType(cursorType),
|
||||
}
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
C.clang_disposeString(clangString)
|
||||
return
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
// it converts a libclang type to a type in the Go AST.
|
||||
func (info *fileInfo) makeASTType(typ C.CXType) ast.Expr {
|
||||
var typeName string
|
||||
switch typ.kind {
|
||||
case C.CXType_SChar:
|
||||
typeName = "C.schar"
|
||||
case C.CXType_UChar:
|
||||
typeName = "C.uchar"
|
||||
case C.CXType_Short:
|
||||
typeName = "C.short"
|
||||
case C.CXType_UShort:
|
||||
typeName = "C.ushort"
|
||||
case C.CXType_Int:
|
||||
typeName = "C.int"
|
||||
case C.CXType_UInt:
|
||||
typeName = "C.uint"
|
||||
case C.CXType_Long:
|
||||
typeName = "C.long"
|
||||
case C.CXType_ULong:
|
||||
typeName = "C.ulong"
|
||||
case C.CXType_LongLong:
|
||||
typeName = "C.longlong"
|
||||
case C.CXType_ULongLong:
|
||||
typeName = "C.ulonglong"
|
||||
case C.CXType_Bool:
|
||||
typeName = "bool"
|
||||
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 4:
|
||||
typeName = "float32"
|
||||
case 8:
|
||||
typeName = "float64"
|
||||
default:
|
||||
// Don't do anything, rely on the fallback code to show a somewhat
|
||||
// sensible error message like "undeclared name: C.long double".
|
||||
}
|
||||
case C.CXType_Complex:
|
||||
switch C.clang_Type_getSizeOf(typ) {
|
||||
case 8:
|
||||
typeName = "complex64"
|
||||
case 16:
|
||||
typeName = "complex128"
|
||||
}
|
||||
case C.CXType_Pointer:
|
||||
return &ast.StarExpr{
|
||||
Star: info.importCPos,
|
||||
X: info.makeASTType(C.clang_getPointeeType(typ)),
|
||||
}
|
||||
case C.CXType_FunctionProto:
|
||||
// Be compatible with gc, which uses the *[0]byte type for function
|
||||
// pointer types.
|
||||
// Return type [0]byte because this is a function type, not a pointer to
|
||||
// this function type.
|
||||
return &ast.ArrayType{
|
||||
Lbrack: info.importCPos,
|
||||
Len: &ast.BasicLit{
|
||||
ValuePos: info.importCPos,
|
||||
Kind: token.INT,
|
||||
Value: "0",
|
||||
},
|
||||
Elt: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: "byte",
|
||||
},
|
||||
}
|
||||
}
|
||||
if typeName == "" {
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
// type name.
|
||||
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: typeName,
|
||||
}
|
||||
}
|
||||
+158
-24
@@ -1,6 +1,7 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
@@ -10,17 +11,26 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
)
|
||||
|
||||
// Program holds all packages and some metadata about the program as a whole.
|
||||
type Program struct {
|
||||
Build *build.Context
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
CFlags []string
|
||||
mainPkg string
|
||||
Build *build.Context
|
||||
OverlayBuild *build.Context
|
||||
OverlayPath func(path string) string
|
||||
Packages map[string]*Package
|
||||
sorted []*Package
|
||||
fset *token.FileSet
|
||||
TypeChecker types.Config
|
||||
Dir string // current working directory (for error reporting)
|
||||
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||
CFlags []string
|
||||
ClangHeaders string
|
||||
}
|
||||
|
||||
// Package holds a loaded package, its imports, and its parsed files.
|
||||
@@ -42,7 +52,12 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
}
|
||||
|
||||
// Load this package.
|
||||
buildPkg, err := p.Build.Import(path, srcDir, build.ImportComment)
|
||||
ctx := p.Build
|
||||
if newPath := p.OverlayPath(path); newPath != "" {
|
||||
ctx = p.OverlayBuild
|
||||
path = newPath
|
||||
}
|
||||
buildPkg, err := ctx.Import(path, srcDir, build.ImportComment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -53,6 +68,11 @@ func (p *Program) Import(path, srcDir string) (*Package, error) {
|
||||
p.sorted = nil // invalidate the sorted order of packages
|
||||
pkg := p.newPackage(buildPkg)
|
||||
p.Packages[buildPkg.ImportPath] = pkg
|
||||
|
||||
if p.mainPkg == "" {
|
||||
p.mainPkg = buildPkg.ImportPath
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
@@ -82,6 +102,11 @@ func (p *Program) ImportFile(path string) (*Package, error) {
|
||||
p.sorted = nil // invalidate the sorted order of packages
|
||||
pkg := p.newPackage(buildPkg)
|
||||
p.Packages[buildPkg.ImportPath] = pkg
|
||||
|
||||
if p.mainPkg == "" {
|
||||
p.mainPkg = buildPkg.ImportPath
|
||||
}
|
||||
|
||||
return pkg, nil
|
||||
}
|
||||
|
||||
@@ -160,10 +185,12 @@ func (p *Program) sort() {
|
||||
// The returned error may be an Errors error, which contains a list of errors.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Program) Parse() error {
|
||||
func (p *Program) Parse(compileTestBinary bool) error {
|
||||
includeTests := compileTestBinary
|
||||
|
||||
// Load all imports
|
||||
for _, pkg := range p.Sorted() {
|
||||
err := pkg.importRecursively()
|
||||
err := pkg.importRecursively(includeTests)
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
if pkg.ImportPath != err.Packages[0] {
|
||||
@@ -176,7 +203,14 @@ func (p *Program) Parse() error {
|
||||
|
||||
// Parse all packages.
|
||||
for _, pkg := range p.Sorted() {
|
||||
err := pkg.Parse()
|
||||
err := pkg.Parse(includeTests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if compileTestBinary {
|
||||
err := p.SwapTestMain()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,6 +227,83 @@ func (p *Program) Parse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Program) SwapTestMain() error {
|
||||
var tests []string
|
||||
|
||||
isTestFunc := func(f *ast.FuncDecl) bool {
|
||||
// TODO: improve signature check
|
||||
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
mainPkg := p.Packages[p.mainPkg]
|
||||
for _, f := range mainPkg.Files {
|
||||
for i, d := range f.Decls {
|
||||
switch v := d.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if isTestFunc(v) {
|
||||
tests = append(tests, v.Name.Name)
|
||||
}
|
||||
if v.Name.Name == "main" {
|
||||
// Remove main
|
||||
if len(f.Decls) == 1 {
|
||||
f.Decls = make([]ast.Decl, 0)
|
||||
} else {
|
||||
f.Decls[i] = f.Decls[len(f.Decls)-1]
|
||||
f.Decls = f.Decls[:len(f.Decls)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
|
||||
const mainBody = `package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func main () {
|
||||
m := &testing.M{
|
||||
Tests: []testing.TestToCall{
|
||||
{{range .TestFunctions}}
|
||||
{Name: "{{.}}", Func: {{.}}},
|
||||
{{end}}
|
||||
},
|
||||
}
|
||||
|
||||
testing.TestMain(m)
|
||||
}
|
||||
`
|
||||
tmpl := template.Must(template.New("testmain").Parse(mainBody))
|
||||
b := bytes.Buffer{}
|
||||
tmplData := struct {
|
||||
TestFunctions []string
|
||||
}{
|
||||
TestFunctions: tests,
|
||||
}
|
||||
|
||||
err := tmpl.Execute(&b, tmplData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := filepath.Join(p.mainPkg, "$testmain.go")
|
||||
|
||||
if p.fset == nil {
|
||||
p.fset = token.NewFileSet()
|
||||
}
|
||||
|
||||
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mainPkg.Files = append(mainPkg.Files, newMain)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// parseFile is a wrapper around parser.ParseFile.
|
||||
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
if p.fset == nil {
|
||||
@@ -217,7 +328,7 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
// Parse parses and typechecks this package.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Package) Parse() error {
|
||||
func (p *Package) Parse(includeTests bool) error {
|
||||
if len(p.Files) != 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -231,7 +342,7 @@ func (p *Package) Parse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
files, err := p.parseFiles()
|
||||
files, err := p.parseFiles(includeTests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -270,11 +381,21 @@ func (p *Package) Check() error {
|
||||
}
|
||||
|
||||
// parseFiles parses the loaded list of files and returns this list.
|
||||
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
func (p *Package) parseFiles(includeTests bool) ([]*ast.File, error) {
|
||||
// TODO: do this concurrently.
|
||||
var files []*ast.File
|
||||
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)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
@@ -293,16 +414,23 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
err = p.processCgo(path, f, append(p.CFlags, "-I"+p.Package.Dir))
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
continue
|
||||
}
|
||||
files = append(files, f)
|
||||
}
|
||||
if len(p.CgoFiles) != 0 {
|
||||
cflags := append(p.CFlags, "-I"+p.Package.Dir)
|
||||
if p.ClangHeaders != "" {
|
||||
cflags = append(cflags, "-I"+p.ClangHeaders)
|
||||
}
|
||||
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
|
||||
if errs != nil {
|
||||
fileErrs = append(fileErrs, errs...)
|
||||
}
|
||||
files = append(files, generated)
|
||||
}
|
||||
if len(fileErrs) != 0 {
|
||||
return nil, Errors{p, fileErrs}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
@@ -323,11 +451,17 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
||||
// importRecursively() on the imported packages as well.
|
||||
//
|
||||
// Idempotent.
|
||||
func (p *Package) importRecursively() error {
|
||||
func (p *Package) importRecursively(includeTests bool) error {
|
||||
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" {
|
||||
// Do Cgo processing in a later stage.
|
||||
// Do CGo processing in a later stage.
|
||||
continue
|
||||
}
|
||||
if _, ok := p.Imports[to]; ok {
|
||||
@@ -343,7 +477,7 @@ func (p *Package) importRecursively() error {
|
||||
if importedPkg.Importing {
|
||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}, p.ImportPos[to]}
|
||||
}
|
||||
err = importedPkg.importRecursively()
|
||||
err = importedPkg.importRecursively(false)
|
||||
if err != nil {
|
||||
if err, ok := err.(*ImportCycleError); ok {
|
||||
err.Packages = append([]string{p.ImportPath}, err.Packages...)
|
||||
|
||||
@@ -33,16 +33,32 @@ func (e *commandError) Error() string {
|
||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// multiError is a list of multiple errors (actually: diagnostics) returned
|
||||
// during LLVM IR generation.
|
||||
type multiError struct {
|
||||
Errs []error
|
||||
}
|
||||
|
||||
func (e *multiError) Error() string {
|
||||
return e.Errs[0].Error()
|
||||
}
|
||||
|
||||
type BuildConfig struct {
|
||||
opt string
|
||||
gc string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
debug bool
|
||||
printSizes string
|
||||
cFlags []string
|
||||
ldFlags []string
|
||||
wasmAbi string
|
||||
opt string
|
||||
gc string
|
||||
panicStrategy string
|
||||
scheduler string
|
||||
printIR bool
|
||||
dumpSSA bool
|
||||
verifyIR bool
|
||||
debug bool
|
||||
printSizes string
|
||||
cFlags []string
|
||||
ldFlags []string
|
||||
tags string
|
||||
wasmAbi string
|
||||
heapSize int64
|
||||
testConfig compiler.TestConfig
|
||||
}
|
||||
|
||||
// Helper function for Compiler object.
|
||||
@@ -51,23 +67,62 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
config.gc = spec.GC
|
||||
}
|
||||
|
||||
// Append command line passed CFlags and LDFlags
|
||||
spec.CFlags = append(spec.CFlags, config.cFlags...)
|
||||
spec.LDFlags = append(spec.LDFlags, config.ldFlags...)
|
||||
root := sourceDir()
|
||||
|
||||
// Merge and adjust CFlags.
|
||||
cflags := append([]string{}, config.cFlags...)
|
||||
for _, flag := range spec.CFlags {
|
||||
cflags = append(cflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
|
||||
// Merge and adjust LDFlags.
|
||||
ldflags := append([]string{}, config.ldFlags...)
|
||||
for _, flag := range spec.LDFlags {
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
|
||||
goroot := getGoroot()
|
||||
if goroot == "" {
|
||||
return errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
tags := spec.BuildTags
|
||||
major, minor, err := getGorootVersion(goroot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 || (minor != 11 && minor != 12) {
|
||||
return fmt.Errorf("requires go version 1.11 or 1.12, got go%d.%d", major, minor)
|
||||
}
|
||||
for i := 1; i <= minor; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
if extraTags := strings.Fields(config.tags); len(extraTags) != 0 {
|
||||
tags = append(tags, extraTags...)
|
||||
}
|
||||
scheduler := spec.Scheduler
|
||||
if config.scheduler != "" {
|
||||
scheduler = config.scheduler
|
||||
}
|
||||
compilerConfig := compiler.Config{
|
||||
Triple: spec.Triple,
|
||||
CPU: spec.CPU,
|
||||
GOOS: spec.GOOS,
|
||||
GOARCH: spec.GOARCH,
|
||||
GC: config.gc,
|
||||
CFlags: spec.CFlags,
|
||||
LDFlags: spec.LDFlags,
|
||||
Debug: config.debug,
|
||||
DumpSSA: config.dumpSSA,
|
||||
RootDir: sourceDir(),
|
||||
GOPATH: getGopath(),
|
||||
BuildTags: spec.BuildTags,
|
||||
Triple: spec.Triple,
|
||||
CPU: spec.CPU,
|
||||
Features: spec.Features,
|
||||
GOOS: spec.GOOS,
|
||||
GOARCH: spec.GOARCH,
|
||||
GC: config.gc,
|
||||
PanicStrategy: config.panicStrategy,
|
||||
Scheduler: scheduler,
|
||||
CFlags: cflags,
|
||||
LDFlags: ldflags,
|
||||
ClangHeaders: getClangHeaderPath(root),
|
||||
Debug: config.debug,
|
||||
DumpSSA: config.dumpSSA,
|
||||
VerifyIR: config.verifyIR,
|
||||
TINYGOROOT: root,
|
||||
GOROOT: goroot,
|
||||
GOPATH: getGopath(),
|
||||
BuildTags: tags,
|
||||
TestConfig: config.testConfig,
|
||||
}
|
||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||
if err != nil {
|
||||
@@ -75,12 +130,15 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Compile Go code to IR.
|
||||
err = c.Compile(pkgName)
|
||||
if err != nil {
|
||||
return err
|
||||
errs := c.Compile(pkgName)
|
||||
if len(errs) != 0 {
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return &multiError{errs}
|
||||
}
|
||||
if config.printIR {
|
||||
fmt.Println("Generated LLVM IR:")
|
||||
fmt.Println("; Generated LLVM IR:")
|
||||
fmt.Println(c.IR())
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
@@ -98,9 +156,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if spec.GOOS != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after applying function sections")
|
||||
}
|
||||
|
||||
// Browsers cannot handle external functions that have type i64 because it
|
||||
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
||||
@@ -112,9 +167,6 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after running the wasm i64 hack")
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization levels here are roughly the same as Clang, but probably not
|
||||
@@ -191,19 +243,26 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
// Prepare link command.
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(spec.LDFlags, "-o", executable, objfile)
|
||||
ldflags = append(ldflags, "-o", executable, objfile, "-L", root)
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
if spec.GOARCH == "wasm" {
|
||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||
// size).
|
||||
heapSize := (config.heapSize + (65536 - 1)) &^ (65536 - 1)
|
||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
for i, path := range spec.ExtraFiles {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
cmdNames := []string{spec.Compiler}
|
||||
if names, ok := commands[spec.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
@@ -215,11 +274,11 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
for _, file := range pkg.CFiles {
|
||||
path := filepath.Join(pkg.Package.Dir, file)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||
cmd := exec.Command(spec.Compiler, append(spec.CFlags, "-c", "-o", outpath, path)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err := cmd.Run()
|
||||
cmdNames := []string{spec.Compiler}
|
||||
if names, ok := commands[spec.Compiler]; ok {
|
||||
cmdNames = names
|
||||
}
|
||||
err := execCommand(cmdNames, append(cflags, "-c", "-o", outpath, path)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
@@ -228,7 +287,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = Link(sourceDir(), spec.Linker, ldflags...)
|
||||
err = Link(spec.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
@@ -305,6 +364,33 @@ func Build(pkgName, outpath, target string, config *BuildConfig) error {
|
||||
})
|
||||
}
|
||||
|
||||
func Test(pkgName, target string, config *BuildConfig) error {
|
||||
spec, err := LoadTarget(target)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
spec.BuildTags = append(spec.BuildTags, "test")
|
||||
config.testConfig.CompileTestBinary = true
|
||||
return Compile(pkgName, ".elf", spec, config, func(tmppath string) error {
|
||||
cmd := exec.Command(tmppath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
// Propagate the exit code
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
if status, ok := err.Sys().(syscall.WaitStatus); ok {
|
||||
os.Exit(status.ExitStatus())
|
||||
}
|
||||
os.Exit(1)
|
||||
}
|
||||
return &commandError{"failed to run compiled binary", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func Flash(pkgName, target, port string, config *BuildConfig) error {
|
||||
spec, err := LoadTarget(target)
|
||||
if err != nil {
|
||||
@@ -463,6 +549,30 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
})
|
||||
}
|
||||
|
||||
// parseSize converts a human-readable size (with k/m/g suffix) into a plain
|
||||
// number.
|
||||
func parseSize(s string) (int64, error) {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if len(s) == 0 {
|
||||
return 0, errors.New("no size provided")
|
||||
}
|
||||
multiply := int64(1)
|
||||
switch s[len(s)-1] {
|
||||
case 'k':
|
||||
multiply = 1 << 10
|
||||
case 'm':
|
||||
multiply = 1 << 20
|
||||
case 'g':
|
||||
multiply = 1 << 30
|
||||
}
|
||||
if multiply != 1 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
n, err := strconv.ParseInt(s, 0, 64)
|
||||
n *= multiply
|
||||
return n, err
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", version)
|
||||
@@ -470,6 +580,7 @@ func usage() {
|
||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
|
||||
fmt.Fprintln(os.Stderr, " test: test packages")
|
||||
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
||||
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+cacheDir()+")")
|
||||
@@ -480,19 +591,24 @@ func usage() {
|
||||
|
||||
func handleCompilerError(err error) {
|
||||
if err != nil {
|
||||
if errUnsupported, ok := err.(*interp.Unsupported); ok {
|
||||
switch err := err.(type) {
|
||||
case *interp.Unsupported:
|
||||
// hit an unknown/unsupported instruction
|
||||
fmt.Fprintln(os.Stderr, "unsupported instruction during init evaluation:")
|
||||
errUnsupported.Inst.Dump()
|
||||
err.Inst.Dump()
|
||||
fmt.Fprintln(os.Stderr)
|
||||
} else if errCompiler, ok := err.(types.Error); ok {
|
||||
fmt.Fprintln(os.Stderr, errCompiler)
|
||||
} else if errLoader, ok := err.(loader.Errors); ok {
|
||||
fmt.Fprintln(os.Stderr, "#", errLoader.Pkg.ImportPath)
|
||||
for _, err := range errLoader.Errs {
|
||||
case types.Error:
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
case loader.Errors:
|
||||
fmt.Fprintln(os.Stderr, "#", err.Pkg.ImportPath)
|
||||
for _, err := range err.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
} else {
|
||||
case *multiError:
|
||||
for _, err := range err.Errs {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
}
|
||||
os.Exit(1)
|
||||
@@ -502,10 +618,14 @@ func handleCompilerError(err error) {
|
||||
func main() {
|
||||
outpath := flag.String("o", "", "output filename")
|
||||
opt := flag.String("opt", "z", "optimization level: 0, 1, 2, s, z")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, dumb, marksweep)")
|
||||
gc := flag.String("gc", "", "garbage collector to use (none, leaking, conservative)")
|
||||
panicStrategy := flag.String("panic", "print", "panic strategy (print, trap)")
|
||||
scheduler := flag.String("scheduler", "", "which scheduler to use (coroutines, tasks)")
|
||||
printIR := flag.Bool("printir", false, "print LLVM IR")
|
||||
dumpSSA := flag.Bool("dumpssa", false, "dump internal Go SSA")
|
||||
target := flag.String("target", "", "LLVM target")
|
||||
verifyIR := flag.Bool("verifyir", false, "run extra verification steps on LLVM IR")
|
||||
tags := flag.String("tags", "", "a space-separated list of extra build tags")
|
||||
target := flag.String("target", "", "LLVM target | .json file with TargetSpec")
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||
nodebug := flag.Bool("no-debug", false, "disable DWARF debug symbol generation")
|
||||
ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug")
|
||||
@@ -513,6 +633,7 @@ func main() {
|
||||
cFlags := flag.String("cflags", "", "additional cflags for compiler")
|
||||
ldFlags := flag.String("ldflags", "", "additional ldflags for linker")
|
||||
wasmAbi := flag.String("wasm-abi", "js", "WebAssembly ABI conventions: js (no i64 params) or generic")
|
||||
heapSize := flag.String("heap-size", "1M", "default heap size in bytes (only supported by WebAssembly)")
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "No command-line arguments supplied.")
|
||||
@@ -523,13 +644,17 @@ func main() {
|
||||
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
config := &BuildConfig{
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
wasmAbi: *wasmAbi,
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
panicStrategy: *panicStrategy,
|
||||
scheduler: *scheduler,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
verifyIR: *verifyIR,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
tags: *tags,
|
||||
wasmAbi: *wasmAbi,
|
||||
}
|
||||
|
||||
if *cFlags != "" {
|
||||
@@ -540,6 +665,19 @@ func main() {
|
||||
config.ldFlags = strings.Split(*ldFlags, " ")
|
||||
}
|
||||
|
||||
if *panicStrategy != "print" && *panicStrategy != "trap" {
|
||||
fmt.Fprintln(os.Stderr, "Panic strategy must be either print or trap.")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var err error
|
||||
if config.heapSize, err = parseSize(*heapSize); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Could not read heap size:", *heapSize)
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Setenv("CC", "clang -target="+*target)
|
||||
|
||||
switch command {
|
||||
@@ -549,8 +687,11 @@ func main() {
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
if flag.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "No package specified.")
|
||||
pkgName := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkgName = flag.Arg(0)
|
||||
} else if flag.NArg() > 1 {
|
||||
fmt.Fprintln(os.Stderr, "build only accepts a single positional argument: package name, but multiple were specified")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -558,7 +699,7 @@ func main() {
|
||||
if target == "" && filepath.Ext(*outpath) == ".wasm" {
|
||||
target = "wasm"
|
||||
}
|
||||
err := Build(flag.Arg(0), *outpath, target, config)
|
||||
err := Build(pkgName, *outpath, target, config)
|
||||
handleCompilerError(err)
|
||||
case "build-builtins":
|
||||
// Note: this command is only meant to be used while making a release!
|
||||
@@ -600,6 +741,17 @@ func main() {
|
||||
}
|
||||
err := Run(flag.Arg(0), *target, config)
|
||||
handleCompilerError(err)
|
||||
case "test":
|
||||
pkgName := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkgName = flag.Arg(0)
|
||||
} else if flag.NArg() > 1 {
|
||||
fmt.Fprintln(os.Stderr, "test only accepts a single positional argument: package name, but multiple were specified")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
err := Test(pkgName, *target, config)
|
||||
handleCompilerError(err)
|
||||
case "clean":
|
||||
// remove cache directory
|
||||
dir := cacheDir()
|
||||
|
||||
+16
-7
@@ -13,17 +13,19 @@ import (
|
||||
"runtime"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
)
|
||||
|
||||
const TESTDATA = "testdata"
|
||||
|
||||
func TestCompiler(t *testing.T) {
|
||||
matches, err := filepath.Glob(TESTDATA + "/*.go")
|
||||
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read test files:", err)
|
||||
}
|
||||
|
||||
dirMatches, err := filepath.Glob(TESTDATA + "/*/main.go")
|
||||
dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read test packages:", err)
|
||||
}
|
||||
@@ -64,7 +66,7 @@ func TestCompiler(t *testing.T) {
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Log("running tests for linux/arm...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/cgo/" {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
@@ -74,7 +76,7 @@ func TestCompiler(t *testing.T) {
|
||||
|
||||
t.Log("running tests for linux/arm64...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/cgo/" {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
@@ -84,7 +86,7 @@ func TestCompiler(t *testing.T) {
|
||||
|
||||
t.Log("running tests for WebAssembly...")
|
||||
for _, path := range matches {
|
||||
if path == "testdata/gc.go" {
|
||||
if path == filepath.Join("testdata", "gc.go") {
|
||||
continue // known to fail
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
@@ -97,7 +99,7 @@ func TestCompiler(t *testing.T) {
|
||||
func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
// Get the expected output for this test.
|
||||
txtpath := path[:len(path)-3] + ".txt"
|
||||
if path[len(path)-1] == '/' {
|
||||
if path[len(path)-1] == os.PathSeparator {
|
||||
txtpath = path + "out.txt"
|
||||
}
|
||||
f, err := os.Open(txtpath)
|
||||
@@ -114,6 +116,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
opt: "z",
|
||||
printIR: false,
|
||||
dumpSSA: false,
|
||||
verifyIR: true,
|
||||
debug: false,
|
||||
printSizes: "",
|
||||
wasmAbi: "js",
|
||||
@@ -121,7 +124,13 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
binary := filepath.Join(tmpdir, "test")
|
||||
err = Build("./"+path, binary, target, config)
|
||||
if err != nil {
|
||||
t.Log("failed to build:", err)
|
||||
if errLoader, ok := err.(loader.Errors); ok {
|
||||
for _, err := range errLoader.Errs {
|
||||
t.Log("failed to build:", err)
|
||||
}
|
||||
} else {
|
||||
t.Log("failed to build:", err)
|
||||
}
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
+44
-11
@@ -30,6 +30,7 @@
|
||||
package arm
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -69,37 +70,58 @@ func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// Run the following system call (SVCall) with 4 arguments.
|
||||
func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
|
||||
//go:volatile
|
||||
type RegValue uint32
|
||||
|
||||
const (
|
||||
SCS_BASE = 0xE000E000
|
||||
NVIC_BASE = SCS_BASE + 0x0100
|
||||
SCB_BASE = SCS_BASE + 0x0D00
|
||||
)
|
||||
|
||||
const (
|
||||
SCB_AIRCR_VECTKEY_Pos = 16
|
||||
SCB_AIRCR_SYSRESETREQ_Pos = 2
|
||||
SCB_AIRCR_SYSRESETREQ_Msk = 1 << SCB_AIRCR_SYSRESETREQ_Pos
|
||||
)
|
||||
|
||||
// System Control Block (SCB)
|
||||
//
|
||||
// SCB_Type provides the definitions for the System Control Block Registers.
|
||||
type SCB_Type struct {
|
||||
CPUID volatile.Register32 // CPUID Base Register
|
||||
ICSR volatile.Register32 // Interrupt Control and State Register
|
||||
VTOR volatile.Register32 // Vector Table Offset Register
|
||||
AIRCR volatile.Register32 // Application Interrupt and Reset Control Register
|
||||
SCR volatile.Register32 // System Control Register
|
||||
CCR volatile.Register32 // Configuration Control Register
|
||||
_ volatile.Register32 // RESERVED1;
|
||||
SHP [2]volatile.Register32 // System Handlers Priority Registers. [0] is RESERVED
|
||||
SHCSR volatile.Register32 // System Handler Control and State Register
|
||||
}
|
||||
|
||||
var SCB = (*SCB_Type)(unsafe.Pointer(uintptr(SCB_BASE)))
|
||||
|
||||
// Nested Vectored Interrupt Controller (NVIC).
|
||||
//
|
||||
// Source:
|
||||
// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CIHIGCIF.html
|
||||
type NVIC_Type struct {
|
||||
ISER [8]RegValue // Interrupt Set-enable Registers
|
||||
ISER [8]volatile.Register32 // Interrupt Set-enable Registers
|
||||
_ [24]uint32
|
||||
ICER [8]RegValue // Interrupt Clear-enable Registers
|
||||
ICER [8]volatile.Register32 // Interrupt Clear-enable Registers
|
||||
_ [24]uint32
|
||||
ISPR [8]RegValue // Interrupt Set-pending Registers
|
||||
ISPR [8]volatile.Register32 // Interrupt Set-pending Registers
|
||||
_ [24]uint32
|
||||
ICPR [8]RegValue // Interrupt Clear-pending Registers
|
||||
ICPR [8]volatile.Register32 // Interrupt Clear-pending Registers
|
||||
_ [24]uint32
|
||||
IABR [8]RegValue // Interrupt Active Bit Registers
|
||||
IABR [8]volatile.Register32 // Interrupt Active Bit Registers
|
||||
_ [56]uint32
|
||||
IPR [60]RegValue // Interrupt Priority Registers
|
||||
IPR [60]volatile.Register32 // Interrupt Priority Registers
|
||||
}
|
||||
|
||||
var NVIC = (*NVIC_Type)(unsafe.Pointer(uintptr(NVIC_BASE)))
|
||||
|
||||
// Enable the given interrupt number.
|
||||
func EnableIRQ(irq uint32) {
|
||||
NVIC.ISER[irq>>5] = 1 << (irq & 0x1F)
|
||||
NVIC.ISER[irq>>5].Set(1 << (irq & 0x1F))
|
||||
}
|
||||
|
||||
// Set the priority of the given interrupt number.
|
||||
@@ -116,7 +138,7 @@ func SetPriority(irq uint32, priority uint32) {
|
||||
regpos := irq % 4
|
||||
mask := uint32(0xff) << (regpos * 8) // bits to clear
|
||||
priority = priority << (regpos * 8) // bits to set
|
||||
NVIC.IPR[regnum] = RegValue((uint32(NVIC.IPR[regnum]) &^ mask) | priority)
|
||||
NVIC.IPR[regnum].Set((uint32(NVIC.IPR[regnum].Get()) &^ mask) | priority)
|
||||
}
|
||||
|
||||
// DisableInterrupts disables all interrupts, and returns the old state.
|
||||
@@ -136,3 +158,14 @@ func DisableInterrupts() uintptr {
|
||||
func EnableInterrupts(mask uintptr) {
|
||||
Asm("cpsie if")
|
||||
}
|
||||
|
||||
// SystemReset performs a hard system reset.
|
||||
func SystemReset() {
|
||||
// SCB->AIRCR = ((0x5FA << SCB_AIRCR_VECTKEY_Pos) |
|
||||
// SCB_AIRCR_SYSRESETREQ_Msk);
|
||||
SCB.AIRCR.Set((0x5FA << SCB_AIRCR_VECTKEY_Pos) | SCB_AIRCR_SYSRESETREQ_Msk)
|
||||
|
||||
for {
|
||||
Asm("wfi")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
.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
|
||||
@@ -0,0 +1,10 @@
|
||||
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
|
||||
@@ -0,0 +1,13 @@
|
||||
.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() {
|
||||
machine.InitADC()
|
||||
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
sensor := machine.ADC{machine.ADC2}
|
||||
sensor.Configure()
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
@@ -16,8 +16,8 @@ func main() {
|
||||
}
|
||||
|
||||
func led1() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED1
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
println("+")
|
||||
led.Low()
|
||||
@@ -30,8 +30,8 @@ func led1() {
|
||||
}
|
||||
|
||||
func led2() {
|
||||
led := machine.GPIO{machine.LED2}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED2
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
println(" +")
|
||||
led.Low()
|
||||
|
||||
@@ -7,14 +7,14 @@ import (
|
||||
|
||||
// This example assumes that the button is connected to pin 8. Change the value
|
||||
// below to use a different pin.
|
||||
const buttonPin = 8
|
||||
const (
|
||||
led = machine.LED
|
||||
button = machine.Pin(8)
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
|
||||
button := machine.GPIO{buttonPin}
|
||||
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
button.Configure(machine.PinConfig{Mode: machine.PinInput})
|
||||
|
||||
for {
|
||||
if button.Get() {
|
||||
|
||||
@@ -8,29 +8,29 @@ import (
|
||||
// This example assumes that you are using the pca10040 board
|
||||
|
||||
func main() {
|
||||
led1 := machine.GPIO{machine.LED1}
|
||||
led1.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led1 := machine.LED1
|
||||
led1.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
led2 := machine.GPIO{machine.LED2}
|
||||
led2.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led2 := machine.LED2
|
||||
led2.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
led3 := machine.GPIO{machine.LED3}
|
||||
led3.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led3 := machine.LED3
|
||||
led3.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
led4 := machine.GPIO{machine.LED4}
|
||||
led4.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led4 := machine.LED4
|
||||
led4.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
button1 := machine.GPIO{machine.BUTTON1}
|
||||
button1.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||
button1 := machine.BUTTON1
|
||||
button1.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
button2 := machine.GPIO{machine.BUTTON2}
|
||||
button2.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||
button2 := machine.BUTTON2
|
||||
button2.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
button3 := machine.GPIO{machine.BUTTON3}
|
||||
button3.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||
button3 := machine.BUTTON3
|
||||
button3.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
button4 := machine.GPIO{machine.BUTTON4}
|
||||
button4.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT_PULLUP})
|
||||
button4 := machine.BUTTON4
|
||||
button4.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
for {
|
||||
led1.Set(button1.Get())
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
// This program runs on an Arduino that has the following four devices connected:
|
||||
// - Button connected to D2
|
||||
// - Rotary analog dial connected to A0
|
||||
// - RGB LED connected to D3, D5, and D6 used as PWM pins
|
||||
// - BlinkM I2C RGB LED
|
||||
//
|
||||
// Pushing the button switches which color is selected.
|
||||
// Rotating the dial changes the value for the currently selected color.
|
||||
// Changing the color value updates the color displayed on both the
|
||||
// PWM-controlled RGB LED and the I2C-controlled BlinkM.
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
buttonPin = 2
|
||||
redPin = 3
|
||||
greenPin = 5
|
||||
bluePin = 6
|
||||
|
||||
red = 0
|
||||
green = 1
|
||||
blue = 2
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.InitADC()
|
||||
machine.InitPWM()
|
||||
machine.I2C0.Configure(machine.I2CConfig{})
|
||||
|
||||
// Init BlinkM
|
||||
machine.I2C0.WriteTo(0x09, []byte("o"))
|
||||
|
||||
button := machine.GPIO{buttonPin}
|
||||
button.Configure(machine.GPIOConfig{Mode: machine.GPIO_INPUT})
|
||||
|
||||
dial := machine.ADC{machine.ADC0}
|
||||
dial.Configure()
|
||||
|
||||
redLED := machine.PWM{redPin}
|
||||
redLED.Configure()
|
||||
|
||||
greenLED := machine.PWM{greenPin}
|
||||
greenLED.Configure()
|
||||
|
||||
blueLED := machine.PWM{bluePin}
|
||||
blueLED.Configure()
|
||||
|
||||
selectedColor := red
|
||||
colors := []uint16{0, 0, 0}
|
||||
|
||||
for {
|
||||
// If we pushed the button, switch active color.
|
||||
if !button.Get() {
|
||||
if selectedColor == blue {
|
||||
selectedColor = red
|
||||
} else {
|
||||
selectedColor++
|
||||
}
|
||||
}
|
||||
|
||||
// Change the intensity for the currently selected color based on the dial setting.
|
||||
colors[selectedColor] = (dial.Get())
|
||||
|
||||
// Update the RGB LED.
|
||||
redLED.Set(colors[red])
|
||||
greenLED.Set(colors[green])
|
||||
blueLED.Set(colors[blue])
|
||||
|
||||
// Update the BlinkM.
|
||||
machine.I2C0.WriteTo(0x09, []byte("n"))
|
||||
machine.I2C0.WriteTo(0x09, []byte{byte(colors[red] >> 8), byte(colors[green] >> 8), byte(colors[blue] >> 8)})
|
||||
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
|
||||
// change these to test a different UART or pins if available
|
||||
var (
|
||||
uart = machine.UART0
|
||||
tx uint8 = machine.UART_TX_PIN
|
||||
rx uint8 = machine.UART_RX_PIN
|
||||
uart = machine.UART0
|
||||
tx = machine.UART_TX_PIN
|
||||
rx = machine.UART_RX_PIN
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Example using the i2s hardware interface on the Adafruit Circuit Playground Express
|
||||
// to read data from the onboard MEMS microphone.
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.I2S0.Configure(machine.I2SConfig{
|
||||
Mode: machine.I2SModePDM,
|
||||
ClockSource: machine.I2SClockSourceExternal,
|
||||
Stereo: true,
|
||||
})
|
||||
|
||||
data := make([]uint32, 64)
|
||||
|
||||
for {
|
||||
// get the next group of samples
|
||||
machine.I2S0.Read(data)
|
||||
|
||||
println("data", data[0], data[1], data[2], data[4], "...")
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,17 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CS_PIN is the pin used for Chip Select (CS). Change to whatever is in use on your board.
|
||||
const CS_PIN = 3
|
||||
// cs is the pin used for Chip Select (CS). Change to whatever is in use on your board.
|
||||
const cs = machine.Pin(3)
|
||||
|
||||
var (
|
||||
tx []byte
|
||||
rx []byte
|
||||
val, result uint16
|
||||
cs machine.GPIO
|
||||
)
|
||||
|
||||
func main() {
|
||||
cs = machine.GPIO{CS_PIN}
|
||||
cs.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 4000000,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// blink program for the BBC micro:bit
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
// The LED matrix in the micro:bit is a multiplexed display: https://en.wikipedia.org/wiki/Multiplexed_display
|
||||
// Driver for easier control: https://github.com/tinygo-org/drivers/tree/master/microbitmatrix
|
||||
func main() {
|
||||
ledrow := machine.LED_ROW_1
|
||||
ledrow.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
ledcol := machine.LED_COL_1
|
||||
ledcol.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
ledcol.Low()
|
||||
for {
|
||||
ledrow.Low()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
|
||||
ledrow.High()
|
||||
time.Sleep(time.Millisecond * 500)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
html/*
|
||||
@@ -0,0 +1,31 @@
|
||||
invoke: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go
|
||||
cp ./invoke/wasm.js ./html/
|
||||
cp ./invoke/index.html ./html/
|
||||
|
||||
export: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./export/wasm.go
|
||||
cp ./export/wasm.js ./html/
|
||||
cp ./export/index.html ./html/
|
||||
|
||||
callback: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm ./callback/wasm.go
|
||||
cp ./callback/wasm.js ./html/
|
||||
cp ./callback/index.html ./html/
|
||||
|
||||
slices: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./slices/wasm.go
|
||||
cp ./slices/wasm.js ./html/
|
||||
cp ./slices/index.html ./html/
|
||||
|
||||
|
||||
main: clean wasm_exec
|
||||
tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./main/main.go
|
||||
cp ./main/index.html ./html/
|
||||
|
||||
wasm_exec:
|
||||
cp ../../../targets/wasm_exec.js ./html/
|
||||
|
||||
clean:
|
||||
rm -rf ./html
|
||||
mkdir ./html
|
||||
@@ -0,0 +1,133 @@
|
||||
# TinyGo WebAssembly examples
|
||||
|
||||
The examples here show two different ways of using WebAssembly with TinyGo:
|
||||
|
||||
1. Defining and exporting functions via the `//go:export <name>` directive. See
|
||||
[the export folder](./export) for an example of this. Additionally, the Wasm
|
||||
module (which has a default value of `env`) can be specified using
|
||||
`//go:wasm-module <module>`.
|
||||
1. Defining and executing a `func main()`. This is similar to how the Go
|
||||
standard library implementation works. See [the main folder](./main) for an
|
||||
example of this.
|
||||
|
||||
## Building
|
||||
|
||||
Build using the `tinygo` compiler:
|
||||
|
||||
```bash
|
||||
$ tinygo build -o ./wasm.wasm -target wasm ./main/main.go
|
||||
```
|
||||
|
||||
This creates a `wasm.wasm` file, which we can load in JavaScript and execute in
|
||||
a browser.
|
||||
|
||||
This examples folder contains two examples that can be built using `make`:
|
||||
|
||||
```bash
|
||||
$ make export
|
||||
```
|
||||
|
||||
```bash
|
||||
$ make main
|
||||
```
|
||||
|
||||
## Running
|
||||
|
||||
Start the local web server:
|
||||
|
||||
```bash
|
||||
$ go run server.go
|
||||
Serving ./html on http://localhost:8080
|
||||
```
|
||||
|
||||
Use your web browser to visit http://localhost:8080.
|
||||
|
||||
* The wasm "export" example displays a simple math equation using HTML, with
|
||||
the result calculated dynamically using WebAssembly. Changing any of the
|
||||
values on the left hand side triggers the exported wasm `update` function to
|
||||
recalculate the result.
|
||||
* The wasm "main" example uses `println` to write to your browser JavaScript
|
||||
console. You may need to open the browser development tools console to see it.
|
||||
|
||||
## How it works
|
||||
|
||||
Execution of the contents require a few JavaScript helper functions which are
|
||||
called from WebAssembly.
|
||||
|
||||
We have defined these in [wasm_exec.js](../../../targets/wasm_exec.js). It is
|
||||
based on `$GOROOT/misc/wasm/wasm_exec.js` from the standard library, but is
|
||||
slightly different. Ensure you are using the same version of `wasm_exec.js` as
|
||||
the version of `tinygo` you are using to compile.
|
||||
|
||||
The general steps required to run the WebAssembly file in the browser includes
|
||||
loading it into JavaScript with `WebAssembly.instantiateStreaming`, or
|
||||
`WebAssembly.instantiate` in some browsers:
|
||||
|
||||
```js
|
||||
const go = new Go(); // Defined in wasm_exec.js
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
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);
|
||||
})
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
If you have used explicit exports, you can call them by invoking them under the
|
||||
`wasm.exports` namespace. See the [`export`](./export/wasm.js) directory for an
|
||||
example of this.
|
||||
|
||||
In addition to the JavaScript, it is important the wasm file is served with the
|
||||
[`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type)
|
||||
header set to `application/wasm`. Without it, most browsers won't run it.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dir = "./html"
|
||||
|
||||
func main() {
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Add("Cache-Control", "no-cache")
|
||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||
resp.Header().Set("content-type", "application/wasm")
|
||||
}
|
||||
fs.ServeHTTP(resp, req)
|
||||
}))}
|
||||
```
|
||||
|
||||
This simple server serves anything inside the `./html` directory on port
|
||||
`8080`, setting any `*.wasm` files `Content-Type` header appropriately.
|
||||
|
||||
For development purposes (**only!**), it also sets the `Cache-Control` header
|
||||
so your browser doesn't cache the files. This is useful while developing, to
|
||||
ensure your browser displays the newest wasm when you recompile.
|
||||
|
||||
In a production environment you **probably wouldn't** want to set the
|
||||
`Cache-Control` header like this. Caching is generally beneficial for end
|
||||
users.
|
||||
|
||||
Further information on the `Cache-Control` header can be found here:
|
||||
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,27 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
'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();
|
||||
@@ -0,0 +1,20 @@
|
||||
<!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="2" /> + <input type="number" id="b" value="2" /> = <input type="number"
|
||||
id="result" readonly />
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -16,10 +16,10 @@ func add(a, b int) int {
|
||||
//go:export update
|
||||
func update() {
|
||||
document := js.Global().Get("document")
|
||||
a_str := document.Call("getElementById", "a").Get("value").String()
|
||||
b_str := document.Call("getElementById", "b").Get("value").String()
|
||||
a, _ := strconv.Atoi(a_str)
|
||||
b, _ := strconv.Atoi(b_str)
|
||||
result := a + b
|
||||
aStr := document.Call("getElementById", "a").Get("value").String()
|
||||
bStr := document.Call("getElementById", "b").Get("value").String()
|
||||
a, _ := strconv.Atoi(aStr)
|
||||
b, _ := strconv.Atoi(bStr)
|
||||
result := add(a, b)
|
||||
document.Call("getElementById", "result").Set("value", result)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = '../../../wasm.wasm';
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
@@ -14,7 +14,7 @@ function init() {
|
||||
|
||||
const go = new Go();
|
||||
if ('instantiateStreaming' in WebAssembly) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function(obj) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
@@ -23,7 +23,7 @@ function init() {
|
||||
fetch(WASM_URL).then(resp =>
|
||||
resp.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function(obj) {
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,15 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
'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();
|
||||
@@ -0,0 +1,8 @@
|
||||
# WebAssembly main execution example
|
||||
|
||||
A simple hello world that prints to the browser console.
|
||||
|
||||
## License
|
||||
|
||||
Note that `index.html` is copied almost verbatim from the Go 1.12 source at
|
||||
`$GOROOT/misc/wasm/wasm_exec.html`. Its license applies to this file.
|
||||
@@ -0,0 +1,49 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
Copyright 2018 The Go Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style
|
||||
license that can be found in the LICENSE file.
|
||||
-->
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Go wasm</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
Add the following polyfill for Microsoft Edge 17/18 support:
|
||||
<script src="https://cdn.jsdelivr.net/npm/text-encoding@0.7.0/lib/encoding.min.js"></script>
|
||||
(see https://caniuse.com/#feat=textencoder)
|
||||
-->
|
||||
<script src="wasm_exec.js"></script>
|
||||
<script>
|
||||
if (!WebAssembly.instantiateStreaming) { // polyfill
|
||||
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||
const source = await (await resp).arrayBuffer();
|
||||
return await WebAssembly.instantiate(source, importObject);
|
||||
};
|
||||
}
|
||||
|
||||
const go = new Go();
|
||||
let mod, inst;
|
||||
WebAssembly.instantiateStreaming(fetch("wasm.wasm"), go.importObject).then((result) => {
|
||||
mod = result.module;
|
||||
inst = result.instance;
|
||||
document.getElementById("runButton").disabled = false;
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
async function run() {
|
||||
console.clear();
|
||||
await go.run(inst);
|
||||
inst = await WebAssembly.instantiate(mod, go.importObject); // reset instance
|
||||
}
|
||||
</script>
|
||||
|
||||
<button onClick="run();" id="runButton" disabled>Run</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("Hello world!")
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const dir = "./html"
|
||||
|
||||
func main() {
|
||||
fs := http.FileServer(http.Dir(dir))
|
||||
log.Print("Serving " + dir + " on http://localhost:8080")
|
||||
http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Add("Cache-Control", "no-cache")
|
||||
if strings.HasSuffix(req.URL.Path, ".wasm") {
|
||||
resp.Header().Set("content-type", "application/wasm")
|
||||
}
|
||||
fs.ServeHTTP(resp, req)
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<!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>
|
||||
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
'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,16 +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="../../../targets/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="2"/> + <input type="number" id="b" value="2"/> = <input type="number" id="result" readonly/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,24 +1,24 @@
|
||||
// +build avr,arduino
|
||||
// +build arduino
|
||||
|
||||
package machine
|
||||
|
||||
const CPU_FREQUENCY = 16000000
|
||||
|
||||
// LED on the Arduino
|
||||
const LED = 13
|
||||
const LED Pin = 13
|
||||
|
||||
// ADC on the Arduino
|
||||
const (
|
||||
ADC0 = 0
|
||||
ADC1 = 1
|
||||
ADC2 = 2
|
||||
ADC3 = 3
|
||||
ADC4 = 4 // Used by TWI for SDA
|
||||
ADC5 = 5 // Used by TWI for SCL
|
||||
ADC0 Pin = 0
|
||||
ADC1 Pin = 1
|
||||
ADC2 Pin = 2
|
||||
ADC3 Pin = 3
|
||||
ADC4 Pin = 4 // Used by TWI for SDA
|
||||
ADC5 Pin = 5 // Used by TWI for SCL
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_TX_PIN = 1
|
||||
UART_RX_PIN = 0
|
||||
UART_TX_PIN Pin = 1
|
||||
UART_RX_PIN Pin = 0
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user