mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 22:58:41 +00:00
Compare commits
473 Commits
shadowstack
...
v0.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a771e3f89 | |||
| 5ad251b2bd | |||
| 5324fb7a40 | |||
| 7369a0e7f2 | |||
| 8906192690 | |||
| 20a55e7944 | |||
| a2c7112b1f | |||
| 3eec878a11 | |||
| e6cab6c327 | |||
| 2463153a8c | |||
| 2a71aa90bc | |||
| 52bac4d75b | |||
| be9e4f439c | |||
| ba49148644 | |||
| 02facb8568 | |||
| 4164c39a4a | |||
| 832f301a74 | |||
| 2168b1b516 | |||
| 92e07ec8af | |||
| be40c383c8 | |||
| b730590059 | |||
| fb1a476033 | |||
| 4397152108 | |||
| d266e44bc5 | |||
| 8fe126b1f6 | |||
| ab50f823dd | |||
| 3fa926c512 | |||
| 01e58691a1 | |||
| 81c0f9af4e | |||
| b5ecd9cab0 | |||
| 9aace79298 | |||
| c16e07469b | |||
| 582457b81e | |||
| 4ea1559d46 | |||
| da7f7eef00 | |||
| 17ef7a5c32 | |||
| 6c9e55bd06 | |||
| 368abeeb9a | |||
| 076699add8 | |||
| d843ebfe40 | |||
| d17f500c8b | |||
| 65beddafe8 | |||
| cea0d9f864 | |||
| 1913e3e1cd | |||
| 9d47897368 | |||
| a7d00f1b6d | |||
| 7732c6f449 | |||
| cf2a7b5089 | |||
| d40fb5bc46 | |||
| 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 | |||
| 5569cd1b6b | |||
| d6c2d6e301 | |||
| a466dd8f2b | |||
| b1744db2c8 | |||
| bd6a7b69ce | |||
| 051ad07755 | |||
| 09e85b7859 | |||
| 622d0ebde6 | |||
| b7cdf8cd0c | |||
| cfc1a66e8d | |||
| 4ad9bd8643 | |||
| 2a1dd98661 | |||
| 2c03192691 | |||
| 9d6df2b4c7 | |||
| 5939729c45 | |||
| c7b91da8c4 | |||
| c7fdb6741f | |||
| b837c94366 | |||
| 26e7e93478 | |||
| 8e99c3313b | |||
| 28987ae061 | |||
| b594f212fb | |||
| 41e093d7bb | |||
| 665c3bdaa6 | |||
| ea3d232c84 | |||
| 4f932b6e66 | |||
| 3538ba943c | |||
| 543696eafc | |||
| 6e5ae83302 | |||
| 9b4071237f | |||
| 1c68da89af | |||
| 4424fe087d | |||
| 34939ab422 | |||
| c56b2a45fa | |||
| b1c70d85f7 | |||
| 714d98354c | |||
| 6e8df2fc40 | |||
| 902f40867f | |||
| 5438f16fcb | |||
| 7f027ddd33 | |||
| acaf096586 | |||
| 942d4903ce | |||
| 0b212cf2f6 | |||
| 2d5bc836f5 | |||
| 856e5fa179 | |||
| 07733ca056 | |||
| 92d9b780b5 | |||
| da345e8723 | |||
| fab38a0749 | |||
| 0a3dbbd1cb | |||
| 4c29f0fdb6 | |||
| fbc2099ee3 | |||
| 95d895646a | |||
| 35fb594f8f | |||
| 01f6aff422 | |||
| 7dd5839f47 | |||
| 403fee7e06 | |||
| 7657238c24 | |||
| 3cba36f2ba | |||
| 93d5269fef | |||
| 4b477fad55 | |||
| 6360e318a7 | |||
| 0757eb5919 | |||
| 013a71aa3d | |||
| f0091b31b5 | |||
| 709a296150 | |||
| f7b2a2c977 | |||
| 6ae4b43eb2 | |||
| bece6b9648 | |||
| 553f00bdb8 | |||
| a789108926 | |||
| d63ce0646c | |||
| 003211b4ff | |||
| e6720d7ddc | |||
| dfef168139 | |||
| 101f2e519b | |||
| eb34afde4b | |||
| 63f2a3dfe9 | |||
| fb23e9c212 | |||
| 222c4c75b1 | |||
| 5a509f5bfe | |||
| 2e46275c45 | |||
| 1d34f868da | |||
| f0904779a5 | |||
| 70f1064f36 | |||
| 930de54dc5 | |||
| 25cd982263 | |||
| 9bbb233cf0 | |||
| 5b507593d2 | |||
| 05d2c14600 | |||
| e7ad366f20 | |||
| 8aed8d8c56 | |||
| c3a15885f5 | |||
| 914d383a96 | |||
| 504c82a0e7 | |||
| f8a1e5f449 | |||
| 0308c92e67 | |||
| 1db9a162da | |||
| 19b4476cbb | |||
| 7461c298dd | |||
| 9092dbcc53 | |||
| e6d90d89fa | |||
| 4f4d7976c6 | |||
| d820c36c4f | |||
| 85108514df | |||
| 589569fc35 | |||
| 2e4dd09bbc | |||
| 602c264749 | |||
| 072ef603fe | |||
| 54baf48266 | |||
| 072eb590a6 | |||
| c0ab91a263 | |||
| 3ebf464da2 | |||
| 38c5e384af | |||
| 65ea74bd84 | |||
| 683e2a66e1 | |||
| 8cbbbb0e76 | |||
| f89c695c8c | |||
| e2be7ccf76 | |||
| 1f511786d3 | |||
| c9f4e41073 | |||
| 67fbfe6305 |
@@ -0,0 +1,345 @@
|
||||
version: 2.1
|
||||
|
||||
commands:
|
||||
submodules:
|
||||
steps:
|
||||
- run:
|
||||
name: "Pull submodules"
|
||||
command: git submodule update --init
|
||||
apt-dependencies:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
llvm<<parameters.llvm>>-dev \
|
||||
clang<<parameters.llvm>> \
|
||||
libclang<<parameters.llvm>>-dev \
|
||||
lld<<parameters.llvm>> \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
install-node:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install node.js"
|
||||
command: |
|
||||
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
|
||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||
rm node-v10.15.1-linux-x64.tar.xz
|
||||
llvm-source-linux:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-v5
|
||||
- run:
|
||||
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: "-8"
|
||||
- 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
|
||||
- run: go install .
|
||||
- run: go test -v ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest RISCV=0
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
assert-test-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-assert
|
||||
- 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 ASSERT=1 llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-linux-v7-assert
|
||||
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: make ASSERT=1
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make ASSERT=1 test
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo RISCV=0
|
||||
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.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.13.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
|
||||
test-llvm8-go113:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- test-linux
|
||||
assert-test-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- assert-test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
|
||||
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- test-llvm8-go113
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
+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-build
|
||||
llvm-project
|
||||
|
||||
+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
|
||||
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- "1.11"
|
||||
|
||||
before_install:
|
||||
- echo "deb http://apt.llvm.org/trusty/ llvm-toolchain-trusty-7 main" | sudo tee -a /etc/apt/sources.list
|
||||
- echo "deb http://ppa.launchpad.net/ubuntu-toolchain-r/test/ubuntu trusty main" | sudo tee -a /etc/apt/sources.list
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install llvm-7-dev clang-7 libclang-7-dev binutils-arm-none-eabi qemu-system-arm --allow-unauthenticated -y
|
||||
- sudo ln -s /usr/bin/clang-7 /usr/local/bin/cc # work around missing -no-pie in old GCC version
|
||||
|
||||
install:
|
||||
- curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
- dep ensure --vendor-only
|
||||
|
||||
script:
|
||||
- go install github.com/aykevl/tinygo
|
||||
- go test -v .
|
||||
- make gen-device
|
||||
- tinygo build -o blinky1.nrf.elf -target=pca10040 examples/blinky1
|
||||
- tinygo build -o blinky2.nrf.elf -target=pca10040 examples/blinky2
|
||||
- tinygo build -o blinky2 examples/blinky2
|
||||
- tinygo build -o test.nrf.elf -target=pca10040 examples/test
|
||||
- tinygo build -o blinky1.nrf51.elf -target=microbit examples/echo
|
||||
- tinygo build -o test.nrf.elf -target=nrf52840-mdk examples/blinky1
|
||||
- tinygo build -o blinky1.nrf51d.elf -target=pca10031 examples/blinky1
|
||||
- tinygo build -o blinky1.stm32.elf -target=bluepill examples/blinky1
|
||||
- tinygo build -o blinky1.avr.o -target=arduino examples/blinky1 # TODO: avr-as/avr-gcc doesn't work
|
||||
- tinygo build -o blinky1.reel.elf -target=reelboard examples/blinky1
|
||||
- tinygo build -o blinky2.reel.elf -target=reelboard examples/blinky2
|
||||
- tinygo build -o blinky1.pca10056.elf -target=pca10056 examples/blinky1
|
||||
- tinygo build -o blinky2.pca10056.elf -target=pca10056 examples/blinky2
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
# Building TinyGo
|
||||
|
||||
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. 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
|
||||
build a release tarball that includes this binary and all necessary extra files.
|
||||
|
||||
## Dependencies
|
||||
|
||||
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+)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git
|
||||
* CMake
|
||||
* [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 download the TinyGo sources (use `--recursive` if you clone
|
||||
the git repository). Then, inside the directory, download the LLVM source:
|
||||
|
||||
make llvm-source
|
||||
|
||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||
covered by this guide.
|
||||
|
||||
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
|
||||
Go below 1.13), make sure that Go modules are enabled:
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
## Build LLVM, Clang, LLD
|
||||
|
||||
Before starting the build, you may want to set the following environment
|
||||
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++
|
||||
|
||||
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:
|
||||
|
||||
make llvm-build
|
||||
|
||||
This can take over an hour depending on the speed of your system.
|
||||
|
||||
## Build TinyGo
|
||||
|
||||
The last step of course is to build TinyGo itself. This can again be done with
|
||||
make:
|
||||
|
||||
make
|
||||
|
||||
## Verify TinyGo
|
||||
|
||||
Try running TinyGo:
|
||||
|
||||
./build/tinygo help
|
||||
|
||||
Also, make sure the `tinygo` binary really is statically linked. Check this
|
||||
using `ldd` (not to be confused with `lld`):
|
||||
|
||||
ldd ./build/tinygo
|
||||
|
||||
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:
|
||||
|
||||
make release
|
||||
|
||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||
the following command (for example in ~/lib):
|
||||
|
||||
tar -xvf path/to/release.tar.gz
|
||||
|
||||
TinyGo will get extracted to a `tinygo` directory. You can then call it with:
|
||||
|
||||
./tinygo/bin/tinygo
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
0.9.0
|
||||
---
|
||||
* **command line**
|
||||
- implement 1200-baud UART bootloader reset when flashing boards that support
|
||||
it
|
||||
- flash using mass-storage device for boards that support it
|
||||
- implement `tinygo env`
|
||||
- add support for Windows (but not yet producing Windows binaries)
|
||||
- add Go version to `tinygo env`
|
||||
- update SVD files for up-to-date peripheral interfaces
|
||||
* **compiler**
|
||||
- add `//go:align` pragma
|
||||
- fix bug related to type aliases
|
||||
- add support for buffered channels
|
||||
- remove incorrect reflect optimization
|
||||
- implement copying slices in init interpretation
|
||||
- add support for constant indices with a named type
|
||||
- add support for recursive types like linked lists
|
||||
- fix miscompile of function nil panics
|
||||
- fix bug related to goroutines
|
||||
* **standard library**
|
||||
- `machine`: do not check for nil slices in `SPI.Tx`
|
||||
- `reflectlite`: add support for Go 1.13
|
||||
- `runtime`: implement `internal/bytealg.CountString`
|
||||
- `sync`: properly handle nil `New` func in `sync.Pool`
|
||||
* **targets**
|
||||
- `arduino`: fix .bss section initialization
|
||||
- `fe310`: implement `Pin.Get`
|
||||
- `gameboy-advance`: support directly outputting .gba files
|
||||
- `samd`: reduce code size by avoiding reflection
|
||||
- `samd21`: do not hardcode pin numbers for peripherals
|
||||
- `stm32f103`: avoid issue with `time.Sleep` less than 200µs
|
||||
|
||||
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**
|
||||
- fix `objcopy` replacement to include the .data section in the firmware image
|
||||
- use `llvm-ar-7` on Linux to fix the Docker image
|
||||
|
||||
0.4.0
|
||||
---
|
||||
- **compiler**
|
||||
- switch to the hardfloat ABI on ARM, which is more widely used
|
||||
- avoid a dependency on `objcopy` (`arm-none-eabi-objcopy` etc.)
|
||||
- fix a bug in `make([]T, n)` where `n` is 64-bits on a 32-bit platform
|
||||
- adapt to a change in the AVR backend in LLVM 8
|
||||
- directly support the .uf2 firmware format as used on Adafruit boards
|
||||
- fix a bug when calling `panic()` at init time outside of the main package
|
||||
- implement nil checks, which results in a ~5% increase in code size
|
||||
- inline slice bounds checking, which results in a ~1% decrease in code size
|
||||
- **targets**
|
||||
- `samd21`: fix a bug in port B pins
|
||||
- `samd21`: implement SPI peripheral
|
||||
- `samd21`: implement ADC peripheral
|
||||
- `stm32`: fix a bug in timekeeping
|
||||
- `wasm`: fix a bug in `wasm_exec.js` that caused corruption in linear memory
|
||||
when running on Node.js.
|
||||
|
||||
0.3.0
|
||||
---
|
||||
- **compiler**
|
||||
- remove old `-initinterp` flag
|
||||
- add support for macOS
|
||||
- **cgo**
|
||||
- add support for bool/float/complex types
|
||||
- **standard library**
|
||||
- `device/arm`: add support to disable/enable hardware interrupts
|
||||
- `machine`: add CPU frequency for nrf-based boards
|
||||
- `syscall`: add support for darwin/amd64
|
||||
- **targets**
|
||||
- `circuitplay_express`: add support for this board
|
||||
- `microbit`: add regular pin constants
|
||||
- `samd21`: fix time function for goroutine support
|
||||
- `samd21`: add support for USB-CDC (serial over USB)
|
||||
- `samd21`: add support for pins in port B
|
||||
- `samd21`: add support for pullup and pulldown pins
|
||||
- `wasm`: add support for Safari in example
|
||||
|
||||
|
||||
0.2.0
|
||||
---
|
||||
- **command line**
|
||||
- add version subcommand
|
||||
- **compiler**
|
||||
- fix a bug in floating point comparisons with NaN values
|
||||
- fix a bug when calling `panic` in package initialization code
|
||||
- add support for comparing `complex64` and `complex128`
|
||||
- **cgo**
|
||||
- add support for external globals
|
||||
- add support for pointers and function pointers
|
||||
- **standard library**
|
||||
- `fmt`: initial support, `fmt.Println` works
|
||||
- `math`: support for most/all functions
|
||||
- `os`: initial support (only stdin/stdout/stderr)
|
||||
- `reflect`: initial support
|
||||
- `syscall`: add support for amd64, arm, and arm64
|
||||
@@ -0,0 +1,52 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for your interest in improving TinyGo.
|
||||
|
||||
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
|
||||
|
||||
### New to TinyGo
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Something in TinyGo is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
### Something in Go that you want/need does not appear to be in TinyGo
|
||||
|
||||
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.
|
||||
|
||||
## How to use our Github repository
|
||||
|
||||
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||
|
||||
Here is how to contribute back some code or documentation:
|
||||
|
||||
- Fork repo
|
||||
- Create a feature branch off of the `dev` branch
|
||||
- Make some useful change
|
||||
- Make sure the tests still pass
|
||||
- Submit a pull request against the `dev` branch.
|
||||
- Be kind
|
||||
|
||||
## How to run tests
|
||||
|
||||
To run the tests:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
@@ -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
-34
@@ -1,46 +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.13, LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:1.13 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
|
||||
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/aykevl/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/aykevl/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/aykevl/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/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/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/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/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/aykevl/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
|
||||
|
||||
@@ -48,32 +51,32 @@ RUN cd /go/src/github.com/aykevl/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/aykevl/tinygo/src /go/src/github.com/aykevl/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/targets /go/src/github.com/aykevl/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/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/aykevl/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/aykevl/tinygo/Makefile /go/src/github.com/aykevl/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/tools /go/src/github.com/aykevl/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/aykevl/tinygo/lib /go/src/github.com/aykevl/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/aykevl/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
-44
@@ -1,44 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f250e2a6d7e4f9ebc5ba37e5e2ec91b46eb1399ee43f2fdaeb20cd4fd1aeee59"
|
||||
name = "github.com/aykevl/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "d8539684f173a591ea9474d6262ac47ef2277d64"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d1102ae84d8c9318db4ce2ad2673eb2bf54569ab2a4a5d57e70d8aef726b681d"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/buildutil",
|
||||
"go/gcexportdata",
|
||||
"go/internal/cgo",
|
||||
"go/internal/gcimporter",
|
||||
"go/loader",
|
||||
"go/packages",
|
||||
"go/ssa",
|
||||
"go/ssa/ssautil",
|
||||
"go/types/typeutil",
|
||||
"internal/fastwalk",
|
||||
"internal/gopathwalk",
|
||||
"internal/semver",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "3e7aa9e59977626dc60433e9aeadf1bb63d28295"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/aykevl/go-llvm",
|
||||
"golang.org/x/tools/go/loader",
|
||||
"golang.org/x/tools/go/ssa",
|
||||
"golang.org/x/tools/go/ssa/ssautil",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/aykevl/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -1,4 +1,7 @@
|
||||
Copyright (c) 2018 Ayke van Laethem. All rights reserved.
|
||||
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
|
||||
|
||||
@@ -1,113 +1,250 @@
|
||||
|
||||
# aliases
|
||||
all: tgo
|
||||
tgo: build/tgo
|
||||
all: tinygo
|
||||
|
||||
.PHONY: all tgo 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
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
export GOROOT = $(shell $(GO) env GOROOT)
|
||||
|
||||
ifeq ($(TARGET),unix)
|
||||
# Regular *nix system.
|
||||
# md5sum binary
|
||||
MD5SUM = md5sum
|
||||
|
||||
else ifeq ($(TARGET),pca10040)
|
||||
# PCA10040: nRF52832 development board
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
# Python binary
|
||||
PYTHON ?= python
|
||||
|
||||
else ifeq ($(TARGET),microbit)
|
||||
# BBC micro:bit
|
||||
OBJCOPY = arm-none-eabi-objcopy
|
||||
TGOFLAGS += -target $(TARGET)
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
|
||||
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)
|
||||
# Use CCACHE for LLVM if possible
|
||||
ifneq (, $(shell which ccache))
|
||||
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
|
||||
endif
|
||||
|
||||
# Allow enabling LLVM assertions
|
||||
ifeq (1, $(ASSERT))
|
||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
|
||||
else
|
||||
$(error Unknown target)
|
||||
|
||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
||||
endif
|
||||
|
||||
.PHONY: all 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
|
||||
|
||||
run-test: build/test
|
||||
./build/test
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
|
||||
run-blinky: run-blinky2
|
||||
run-blinky2: build/blinky2
|
||||
./build/blinky2
|
||||
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
||||
# Without this flag, linking results in errors like these:
|
||||
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
|
||||
|
||||
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'
|
||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
CGO_LDFLAGS_EXTRA += -lversion
|
||||
|
||||
# Build libclang manually because the CMake-based build system on Windows
|
||||
# doesn't allow building libclang as a static library.
|
||||
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
|
||||
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
|
||||
|
||||
# Add the libclang dependency to the tinygo binary target.
|
||||
tinygo: $(LIBCLANG_PATH)
|
||||
test: $(LIBCLANG_PATH)
|
||||
# Build libclang.
|
||||
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
|
||||
@mkdir -p build
|
||||
ar rcs $(LIBCLANG_PATH) $^
|
||||
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
else
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
CLANG_LIBS = $(START_GROUP) -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.
|
||||
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+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
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 src/internal/reflectlite transform
|
||||
fmt:
|
||||
@go fmt . ./compiler ./interp ./loader ./ir ./src/device/arm ./src/examples/* ./src/machine ./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/
|
||||
./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||
go fmt ./src/device/avr
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||
$(PYTHON) ./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/avr
|
||||
|
||||
gen-device-nrf:
|
||||
./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||
go fmt ./src/device/nrf
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||
|
||||
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
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||
|
||||
gen-device-sifive:
|
||||
$(PYTHON) ./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
|
||||
GO111MODULE=off $(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
|
||||
$(PYTHON) ./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||
GO111MODULE=off $(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 -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tgo: *.go compiler/*.go interp/*.go loader/*.go ir/*.go
|
||||
@mkdir -p build
|
||||
go build -o build/tgo -i .
|
||||
tinygo:
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||
|
||||
# Binary that can run on the host.
|
||||
build/%: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
|
||||
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
test:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./interp ./transform .
|
||||
|
||||
# ELF file that can run on a microcontroller.
|
||||
build/%.elf: src/examples/% src/examples/%/*.go build/tgo src/runtime/*.go
|
||||
./build/tgo build $(TGOFLAGS) -size=short -o $@ $(subst src/,,$<)
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && tinygo test
|
||||
|
||||
# Convert executable to Intel hex file (for flashing).
|
||||
build/%.hex: build/%.elf
|
||||
$(OBJCOPY) -O ihex $^ $@
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
# test all examples
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||
@$(MD5SUM) test.hex
|
||||
# test all targets/boards
|
||||
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
||||
@$(MD5SUM) test.gba
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
ifneq ($(AVR), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(RISCV), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
|
||||
release: 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$(EXE) 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
|
||||
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||
@cp -rp src build/release/tinygo/src
|
||||
@cp -rp targets build/release/tinygo/targets
|
||||
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
|
||||
./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
|
||||
|
||||
@@ -1,144 +1,79 @@
|
||||
# TinyGo - Go compiler for microcontrollers
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://travis-ci.com/aykevl/tinygo)
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
|
||||
|
||||
> We never expected Go to be an embedded language and so it's got serious
|
||||
> problems [...].
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
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.
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a
|
||||
single processor core. It is similar to [emgo](https://github.com/ziutek/emgo)
|
||||
but a major difference is that I want to keep the Go memory model (which implies
|
||||
garbage collection of some sort). Another difference is that TinyGo uses LLVM
|
||||
internally instead of emitting C, which hopefully leads to smaller and more
|
||||
efficient code and certainly leads to more flexibility.
|
||||
|
||||
My original reasoning was: if [Python](https://micropython.org/) can run on
|
||||
microcontrollers, then certainly [Go](https://golang.org/) should be able to and
|
||||
run on even lower level micros.
|
||||
|
||||
Example program (blinky):
|
||||
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
led.High()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Currently supported features:
|
||||
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
|
||||
|
||||
* control flow
|
||||
* many (but not all) basic types: most ints, floats, strings, structs
|
||||
* function calling
|
||||
* interfaces for basic types (with type switches and asserts)
|
||||
* goroutines (very initial support)
|
||||
* function pointers (non-blocking)
|
||||
* interface methods
|
||||
* standard library (but most packages won't work due to missing language
|
||||
features)
|
||||
* slices (partially)
|
||||
* maps (very rough, unfinished)
|
||||
* defer
|
||||
* closures
|
||||
* bound methods
|
||||
* complex numbers (except for arithmetic)
|
||||
|
||||
Not yet supported:
|
||||
|
||||
* complex arithmetic
|
||||
* garbage collection
|
||||
* recover
|
||||
* channels
|
||||
* introspection (if it ever gets implemented)
|
||||
* ...
|
||||
```shell
|
||||
tinygo flash -target arduino examples/blinky1
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/).
|
||||
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
|
||||
|
||||
### Running with Docker
|
||||
## Supported boards/targets
|
||||
|
||||
A docker container exists for easy access to the `tinygo` CLI:
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
```sh
|
||||
$ docker run --rm -v $(pwd):/src tinygo/tinygo tinygo build -o /src/wasm.wasm -target wasm examples/wasm
|
||||
```
|
||||
The following 19 microcontroller boards are currently supported:
|
||||
|
||||
Note that you cannot run `tinygo flash` from inside the docker container,
|
||||
so it is less useful for microcontroller development.
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC micro:bit](https://microbit.org/)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
|
||||
## Supported targets
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
The following architectures/systems are currently supported:
|
||||
## Currently supported features:
|
||||
|
||||
* ARM (Cortex-M)
|
||||
* AVR (Arduino Uno)
|
||||
* Linux
|
||||
* WebAssembly
|
||||
|
||||
For more information, see [this list of targets and
|
||||
boards](https://tinygo.org/targets/). Pull requests for
|
||||
broader support are welcome!
|
||||
|
||||
## Analysis and optimizations
|
||||
|
||||
The goal is to reduce code size (and increase performance) by performing all
|
||||
kinds of whole-program analysis passes. The official Go compiler doesn't do a
|
||||
whole lot of analysis (except for escape analysis) because it needs to be fast,
|
||||
but embedded programs are necessarily smaller so it becomes practical. And I
|
||||
think especially program size can be reduced by a large margin when actually
|
||||
trying to optimize for it.
|
||||
|
||||
Implemented compiler passes:
|
||||
|
||||
* Analyse which functions are blocking. Blocking functions are functions that
|
||||
call sleep, chan send, etc. Its parents are also blocking.
|
||||
* Analyse whether the scheduler is needed. It is only needed when there are
|
||||
`go` statements for blocking functions.
|
||||
* Analyse whether a given type switch or type assert is possible with
|
||||
[type-based alias analysis](https://en.wikipedia.org/wiki/Alias_analysis#Type-based_alias_analysis).
|
||||
I would like to use flow-based alias analysis in the future, if feasible.
|
||||
* Do basic dead code elimination of functions. This pass makes later passes
|
||||
better and probably improves compile time as well.
|
||||
|
||||
## Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without
|
||||
modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine
|
||||
support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing
|
||||
certain things so TinyGo might actually turn out to be faster for number
|
||||
crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
For a description of currently supported Go language features, please see [https://tinygo.org/lang-support/](https://tinygo.org/lang-support/).
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is currently maintained on a dedicated web site located at [https://tinygo.org/](https://tinygo.org/).
|
||||
Documentation is located on our web site at [https://tinygo.org/](https://tinygo.org/).
|
||||
|
||||
You can find the web site code at [https://github.com/tinygo-org/tinygo-site](https://github.com/tinygo-org/tinygo-site).
|
||||
|
||||
@@ -153,26 +88,37 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
|
||||
|
||||
## Contributing
|
||||
|
||||
Patches are welcome!
|
||||
Your contributions are welcome!
|
||||
|
||||
If you want to contribute, here are some suggestions:
|
||||
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
|
||||
|
||||
* 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.
|
||||
* 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/aykevl/tinygo/wiki/Adding-a-new-board).
|
||||
* Microcontrollers have lots of peripherals and many don't have an
|
||||
implementation yet in the `machine` package. Adding support for new
|
||||
peripherals is very useful.
|
||||
* Just raising bugs for things you'd like to see implemented is also a form of
|
||||
contributing! It helps prioritization.
|
||||
## Project Scope
|
||||
|
||||
Goals:
|
||||
|
||||
* Have very small binary sizes. Don't pay for what you don't use.
|
||||
* Support for most common microcontroller boards.
|
||||
* Be usable on the web using WebAssembly.
|
||||
* Good CGo support, with no more overhead than a regular function call.
|
||||
* Support most standard library packages and compile most Go code without modification.
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
|
||||
## Why this project exists
|
||||
|
||||
> We never expected Go to be an embedded language and so its got serious problems...
|
||||
|
||||
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
|
||||
|
||||
TinyGo is a project to bring Go to microcontrollers and small systems with a single processor core. It is similar to [emgo](https://github.com/ziutek/emgo) but a major difference is that we want to keep the Go memory model (which implies garbage collection of some sort). Another difference is that TinyGo uses LLVM internally instead of emitting C, which hopefully leads to smaller and more efficient code and certainly leads to more flexibility.
|
||||
|
||||
The original reasoning was: if [Python](https://micropython.org/) can run on microcontrollers, then certainly [Go](https://golang.org/) should be able to run on even lower level micros.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the
|
||||
[Go project](https://golang.org/LICENSE) itself.
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 180
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
- checkout: self
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-8-windows-v0
|
||||
path: llvm-project
|
||||
- task: Bash@3
|
||||
displayName: Download LLVM source
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: make llvm-source
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM build
|
||||
inputs:
|
||||
key: llvm-build-8-windows-v1
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
choco install ninja
|
||||
make llvm-build
|
||||
fi
|
||||
- task: Bash@3
|
||||
displayName: Install QEMU
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: choco install qemu
|
||||
- task: Bash@3
|
||||
displayName: Test TinyGo
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make test
|
||||
- task: Bash@3
|
||||
displayName: Build TinyGo release tarball
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make release -j4
|
||||
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
|
||||
displayName: Publish tarball as artifact
|
||||
artifact: tinygo.windows-amd64.tar.gz
|
||||
- task: Bash@3
|
||||
displayName: Smoke tests
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
make smoketest TINYGO=build/tinygo AVR=0 RISCV=0
|
||||
+42
-32
@@ -5,14 +5,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
func cacheDir() string {
|
||||
home := getHomeDir()
|
||||
dir := filepath.Join(home, ".cache", "tinygo")
|
||||
return dir
|
||||
}
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
// for stale caches.
|
||||
@@ -39,8 +34,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
|
||||
// TODO: the configKey is currently ignored. It is supposed to be used as extra
|
||||
// data for the cache key, like the compiler version and arguments.
|
||||
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||
dir := cacheDir()
|
||||
cachepath := filepath.Join(dir, name)
|
||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||
cacheStat, err := os.Stat(cachepath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil // does not exist
|
||||
@@ -74,35 +68,51 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
|
||||
|
||||
// TODO: check the config key
|
||||
|
||||
dir := cacheDir()
|
||||
dir := goenv.Get("GOCACHE")
|
||||
err := os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cachepath := filepath.Join(dir, name)
|
||||
err = os.Rename(tmppath, cachepath)
|
||||
err = moveFile(tmppath, cachepath)
|
||||
if err != nil {
|
||||
inf, err := os.Open(tmppath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer inf.Close()
|
||||
outf, err := os.Create(cachepath + ".tmp")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outf, inf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
err = os.Rename(cachepath+".tmp", cachepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return cachepath, outf.Close()
|
||||
return "", err
|
||||
}
|
||||
return cachepath, nil
|
||||
}
|
||||
|
||||
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||
// the old file is removed.
|
||||
func moveFile(src, dst string) error {
|
||||
err := os.Rename(src, dst)
|
||||
if err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
// Failed to move, probably a different filesystem.
|
||||
// Do a copy + remove.
|
||||
inf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
outpath := dst + ".tmp"
|
||||
outf, err := os.Create(outpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outf, inf)
|
||||
if err != nil {
|
||||
os.Remove(outpath)
|
||||
return err
|
||||
}
|
||||
|
||||
err = outf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(dst+".tmp", dst)
|
||||
}
|
||||
|
||||
+94
-19
@@ -1,11 +1,16 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -157,16 +162,29 @@ var aeabiBuiltins = []string{
|
||||
|
||||
func builtinFiles(target string) []string {
|
||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||
if target[:3] == "arm" {
|
||||
if strings.HasPrefix(target, "arm") {
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
}
|
||||
return builtins
|
||||
}
|
||||
|
||||
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||
func builtinsDir() string {
|
||||
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
|
||||
}
|
||||
|
||||
// Get the builtins archive, possibly generating it as needed.
|
||||
func loadBuiltins(target string) (path string, err error) {
|
||||
// Try to load a precompiled compiler-rt library.
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled compiler-rt for this OS/architecture. Return the
|
||||
// path directly.
|
||||
return precompiledPath, nil
|
||||
}
|
||||
|
||||
outfile := "librt-" + target + ".a"
|
||||
builtinsDir := filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
@@ -174,13 +192,37 @@ 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
|
||||
}
|
||||
|
||||
dir, err := ioutil.TempDir("", "tinygo-builtins")
|
||||
var cachepath string
|
||||
err = compileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
})
|
||||
return cachepath, err
|
||||
}
|
||||
|
||||
// compileBuiltins compiles builtins from compiler-rt into a static library.
|
||||
// When it succeeds, it will call the callback with the resulting path. The path
|
||||
// will be removed after callback returns. If callback returns an error, this is
|
||||
// passed through to the return value of this function.
|
||||
func compileBuiltins(target string, callback func(path string) error) error {
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
for i, name := range builtins {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
dirPrefix := "tinygo-builtins"
|
||||
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
@@ -195,26 +237,59 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
objpath := filepath.Join(dir, objname+".o")
|
||||
objs = append(objs, objpath)
|
||||
srcpath := filepath.Join(builtinsDir, name)
|
||||
cmd := exec.Command(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-o", objpath, srcpath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = dir
|
||||
err = cmd.Run()
|
||||
// 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.
|
||||
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 "", err
|
||||
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 "", 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)
|
||||
}
|
||||
}
|
||||
|
||||
return cacheStore(arpath, outfile, commands["clang"], srcs)
|
||||
// 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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// +build !byollvm
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#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-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)
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
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")
|
||||
}
|
||||
// Add the path for when LLVM was installed with the installer from
|
||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
||||
// variable.
|
||||
if runtime.GOOS == "windows" {
|
||||
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
||||
}
|
||||
}
|
||||
|
||||
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 || err.Err.Error() == "file does not exist") {
|
||||
// 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, " "))
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package compiler
|
||||
|
||||
// This file implements functions that do certain safety checks that are
|
||||
// required by the Go programming language.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||
// slice. This is required by the Go language spec: an index out of bounds must
|
||||
// cause a panic.
|
||||
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
}
|
||||
|
||||
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||
// correctly extend that type.
|
||||
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||
}
|
||||
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||
// The index is bigger than the array length type, so extend it.
|
||||
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: index >= arrayLen
|
||||
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("lookupPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||
// sure it is within bounds.
|
||||
//
|
||||
// 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.
|
||||
return
|
||||
}
|
||||
|
||||
// Extend the capacity integer to be at least as wide as low and high.
|
||||
capacityType := capacity.Type()
|
||||
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = low.Type()
|
||||
}
|
||||
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, "")
|
||||
}
|
||||
|
||||
// Extend low and high to be the same size as capacity.
|
||||
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if lowType.Info()&types.IsUnsigned != 0 {
|
||||
low = c.builder.CreateZExt(low, capacityType, "")
|
||||
} else {
|
||||
low = c.builder.CreateSExt(low, capacityType, "")
|
||||
}
|
||||
}
|
||||
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if highType.Info()&types.IsUnsigned != 0 {
|
||||
high = c.builder.CreateZExt(high, capacityType, "")
|
||||
} else {
|
||||
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")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
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.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
|
||||
// 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.
|
||||
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.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
+49
-7
@@ -1,8 +1,9 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"fmt"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// For a description of the calling convention in prose, see:
|
||||
@@ -20,8 +21,12 @@ func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name stri
|
||||
panic("trying to call runtime." + fnName)
|
||||
}
|
||||
fn := c.ir.GetFunction(member.(*ssa.Function))
|
||||
if fn.LLVMFn.IsNil() {
|
||||
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
|
||||
}
|
||||
if !fn.IsExported() {
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||
}
|
||||
return c.createCall(fn.LLVMFn, args, name)
|
||||
}
|
||||
@@ -37,7 +42,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:
|
||||
@@ -54,6 +59,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() {
|
||||
@@ -91,6 +115,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 {
|
||||
@@ -122,10 +167,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
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
package compiler
|
||||
|
||||
// This file lowers channel operations (make/send/recv/close) to runtime calls
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
|
||||
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
||||
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
|
||||
bufSize := c.getValue(frame, expr.Size)
|
||||
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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)
|
||||
|
||||
// Do the send.
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{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 {
|
||||
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.
|
||||
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.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
|
||||
} else {
|
||||
return received
|
||||
}
|
||||
}
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
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)
|
||||
}
|
||||
}
|
||||
+1203
-1670
File diff suppressed because it is too large
Load Diff
+42
-64
@@ -14,9 +14,9 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/ir"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// deferInitFunc sets up this function for future deferred calls. It must be
|
||||
@@ -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)
|
||||
}
|
||||
@@ -243,23 +224,19 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// with a strict calling convention.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
fnPtr, _, err := c.getInvokeCall(frame, callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
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")
|
||||
@@ -268,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)
|
||||
}
|
||||
@@ -277,20 +254,19 @@ func (c *Compiler) emitRunDefers(frame *Frame) error {
|
||||
// function, but we have to pass one anyway.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Call real function.
|
||||
c.createCall(callback.LLVMFn, forwardParams, "")
|
||||
|
||||
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)
|
||||
@@ -300,11 +276,14 @@ 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)
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Call deferred function.
|
||||
c.createCall(fn.LLVMFn, forwardParams, "")
|
||||
|
||||
@@ -325,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,935 @@
|
||||
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
|
||||
//
|
||||
// For example, take the following code:
|
||||
//
|
||||
// func main() {
|
||||
// go foo()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// i := bar()
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
// for {
|
||||
// println("foo!")
|
||||
// time.Sleep(time.Second)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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
|
||||
// pseudo-Go code:
|
||||
//
|
||||
// func main() {
|
||||
// fn := runtime.makeGoroutine(foo)
|
||||
// fn()
|
||||
// time.Sleep(2 * time.Second)
|
||||
// println("some other operation")
|
||||
// i := bar() // imagine an 'await' keyword in front of this call
|
||||
// println("done", *i)
|
||||
// }
|
||||
//
|
||||
// func foo() {
|
||||
// for {
|
||||
// println("foo!")
|
||||
// time.Sleep(time.Second)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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
|
||||
// async/await style pseudocode:
|
||||
//
|
||||
// func main(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
|
||||
// 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", *i)
|
||||
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||
// }
|
||||
//
|
||||
// func foo(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// for {
|
||||
// println("foo!")
|
||||
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func bar(parent) {
|
||||
// hdl := llvm.makeCoroutine()
|
||||
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||
// llvm.suspend(hdl) // suspend point
|
||||
// println("blocking operation completed)
|
||||
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
|
||||
// }
|
||||
//
|
||||
// The real LLVM code is more complicated, but this is the general idea.
|
||||
//
|
||||
// The LLVM coroutine passes will then process this file further transforming
|
||||
// these three functions into coroutines. Most of the actual work is done by the
|
||||
// scheduler, which runs in the background scheduling all coroutines.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// setting this to true will cause the compiler to spew tons of information about coroutine transformations
|
||||
// this can be useful when debugging coroutine lowering or looking for potential missed optimizations
|
||||
const coroDebug = false
|
||||
|
||||
type asyncFunc struct {
|
||||
taskHandle llvm.Value
|
||||
cleanupBlock llvm.BasicBlock
|
||||
suspendBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
// LowerGoroutines performs some IR transformations necessary to support
|
||||
// goroutines. It does something different based on whether it uses the
|
||||
// coroutine or the tasks implementation of goroutines, and whether goroutines
|
||||
// are necessary at all.
|
||||
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 || len(getUses(c.mod.NamedFunction("runtime.yield"))) != 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, "")
|
||||
} 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, "")
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
// Replace call of runtime.callMain() with a real call to main.main(),
|
||||
// optionally followed by a call to runtime.scheduler().
|
||||
c.builder.SetInsertPointBefore(mainCall)
|
||||
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||
var ph llvm.Value
|
||||
if needsScheduler {
|
||||
ph = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, "")
|
||||
} else {
|
||||
ph = llvm.Undef(c.i8ptrType)
|
||||
}
|
||||
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), ph}, "")
|
||||
if needsScheduler {
|
||||
c.createRuntimeCall("scheduler", nil, "")
|
||||
}
|
||||
mainCall.EraseFromParentAsInstruction()
|
||||
|
||||
if !needsScheduler {
|
||||
go_scheduler := c.mod.NamedFunction("go_scheduler")
|
||||
if !go_scheduler.IsNil() {
|
||||
// This is the WebAssembly backend.
|
||||
// There is no need to export the go_scheduler function, but it is
|
||||
// still exported. Make sure it is optimized away.
|
||||
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func coroDebugPrintln(s ...interface{}) {
|
||||
if coroDebug {
|
||||
fmt.Println(s...)
|
||||
}
|
||||
}
|
||||
|
||||
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
|
||||
// determines whether a scheduler is needed, and if it is, it transforms
|
||||
// blocking operations into goroutines and blocking calls into await calls.
|
||||
//
|
||||
// It does the following operations:
|
||||
// * Find all blocking functions.
|
||||
// * Determine whether a scheduler is necessary. If not, it skips the
|
||||
// following operations.
|
||||
// * Transform call instructions into await calls.
|
||||
// * Transform return instructions into final suspends.
|
||||
// * Set up the coroutine frames for async functions.
|
||||
// * Transform blocking calls into their async equivalents.
|
||||
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||
var worklist []llvm.Value
|
||||
|
||||
yield := c.mod.NamedFunction("runtime.yield")
|
||||
if !yield.IsNil() {
|
||||
worklist = append(worklist, yield)
|
||||
}
|
||||
|
||||
if len(worklist) == 0 {
|
||||
// There are no blocking operations, so no need to transform anything.
|
||||
return false, c.lowerMakeGoroutineCalls(false)
|
||||
}
|
||||
|
||||
// Find all async functions.
|
||||
// Keep reducing this worklist by marking a function as recursively async
|
||||
// from the worklist and pushing all its parents that are non-async.
|
||||
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
|
||||
// the work items are then grey objects.
|
||||
asyncFuncs := make(map[llvm.Value]*asyncFunc)
|
||||
asyncList := make([]llvm.Value, 0, 4)
|
||||
for len(worklist) != 0 {
|
||||
// Pick the topmost.
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
if _, ok := asyncFuncs[f]; ok {
|
||||
continue // already processed
|
||||
}
|
||||
if f.Name() == "resume" {
|
||||
continue
|
||||
}
|
||||
// Add to set of async functions.
|
||||
asyncFuncs[f] = &asyncFunc{}
|
||||
asyncList = append(asyncList, f)
|
||||
|
||||
// Add all callees to the worklist.
|
||||
for _, use := range getUses(f) {
|
||||
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 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
|
||||
// pointers which is not yet supported.
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer")
|
||||
}
|
||||
parent := use.InstructionParent().Parent()
|
||||
for i := 0; i < use.OperandsCount()-1; i++ {
|
||||
if use.Operand(i) == f {
|
||||
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
|
||||
}
|
||||
}
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether a scheduler is needed.
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
if strings.HasPrefix(c.Triple, "avr") {
|
||||
needsScheduler = false
|
||||
getCoroutine := c.mod.NamedFunction("runtime.getCoroutine")
|
||||
for _, inst := range getUses(getCoroutine) {
|
||||
inst.ReplaceAllUsesWith(llvm.Undef(inst.Type()))
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
yield := c.mod.NamedFunction("runtime.yield")
|
||||
for _, inst := range getUses(yield) {
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
sleep := c.mod.NamedFunction("time.Sleep")
|
||||
for _, inst := range getUses(sleep) {
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.createRuntimeCall("avrSleep", []llvm.Value{inst.Operand(0)}, "")
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
} else {
|
||||
// Only use a scheduler when an async goroutine is started. When the
|
||||
// goroutine is not async (does not do any blocking operation), no
|
||||
// scheduler is necessary as it can be called directly.
|
||||
for _, use := range getUses(makeGoroutine) {
|
||||
// Input param must be const ptrtoint of function.
|
||||
ptrtoint := use.Operand(0)
|
||||
if !ptrtoint.IsConstant() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||
panic("expected const ptrtoint operand of runtime.makeGoroutine")
|
||||
}
|
||||
goroutine := ptrtoint.Operand(0)
|
||||
if goroutine.Name() == "runtime.fakeCoroutine" {
|
||||
continue
|
||||
}
|
||||
if _, ok := asyncFuncs[goroutine]; ok {
|
||||
needsScheduler = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, ok := asyncFuncs[c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path()+".main")]; ok {
|
||||
needsScheduler = true
|
||||
}
|
||||
}
|
||||
|
||||
if !needsScheduler {
|
||||
// on wasm, we may still have calls to deadlock
|
||||
// replace these with an abort
|
||||
abort := c.mod.NamedFunction("runtime.abort")
|
||||
if deadlock := c.mod.NamedFunction("runtime.deadlock"); !deadlock.IsNil() {
|
||||
deadlock.ReplaceAllUsesWith(abort)
|
||||
}
|
||||
|
||||
// No scheduler is needed. Do not transform all functions here.
|
||||
// However, make sure that all go calls (which are all non-async) are
|
||||
// transformed into regular calls.
|
||||
return false, c.lowerMakeGoroutineCalls(false)
|
||||
}
|
||||
|
||||
if noret := c.mod.NamedFunction("runtime.noret"); noret.IsNil() {
|
||||
panic("missing noret")
|
||||
}
|
||||
|
||||
// replace indefinitely blocking yields
|
||||
getCoroutine := c.mod.NamedFunction("runtime.getCoroutine")
|
||||
coroDebugPrintln("replace indefinitely blocking yields")
|
||||
nonReturning := map[llvm.Value]bool{}
|
||||
for _, f := range asyncList {
|
||||
if f == yield {
|
||||
continue
|
||||
}
|
||||
coroDebugPrintln("scanning", f.Name())
|
||||
|
||||
var callsAsyncNotYield bool
|
||||
var callsYield bool
|
||||
var getsCoroutine bool
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() {
|
||||
callee := inst.CalledValue()
|
||||
if callee == yield {
|
||||
callsYield = true
|
||||
} else if callee == getCoroutine {
|
||||
getsCoroutine = true
|
||||
} else if _, ok := asyncFuncs[callee]; ok {
|
||||
callsAsyncNotYield = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
coroDebugPrintln("result", f.Name(), callsYield, getsCoroutine, callsAsyncNotYield)
|
||||
|
||||
if callsYield && !getsCoroutine && !callsAsyncNotYield {
|
||||
coroDebugPrintln("optimizing", f.Name())
|
||||
// calls yield without registering for a wakeup
|
||||
// this actually could otherwise wake up, but only in the case of really messed up undefined behavior
|
||||
// so everything after a yield is unreachable, so we can just inject a fake return
|
||||
delQueue := []llvm.Value{}
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
var broken bool
|
||||
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !broken && !inst.IsACallInst().IsNil() && inst.CalledValue() == yield {
|
||||
coroDebugPrintln("broke", f.Name(), bb.AsValue().Name())
|
||||
broken = true
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.createRuntimeCall("noret", []llvm.Value{}, "")
|
||||
if f.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateRetVoid()
|
||||
} else {
|
||||
c.builder.CreateRet(llvm.Undef(f.Type().ElementType().ReturnType()))
|
||||
}
|
||||
}
|
||||
if broken {
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
inst.ReplaceAllUsesWith(llvm.Undef(inst.Type()))
|
||||
}
|
||||
delQueue = append(delQueue, inst)
|
||||
}
|
||||
}
|
||||
if !broken {
|
||||
coroDebugPrintln("did not break", f.Name(), bb.AsValue().Name())
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range delQueue {
|
||||
v.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
nonReturning[f] = true
|
||||
}
|
||||
}
|
||||
|
||||
// convert direct calls into an async call followed by a yield operation
|
||||
coroDebugPrintln("convert direct calls into an async call followed by a yield operation")
|
||||
for _, f := range asyncList {
|
||||
if f == yield {
|
||||
continue
|
||||
}
|
||||
coroDebugPrintln("scanning", f.Name())
|
||||
|
||||
// Rewrite async calls
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() {
|
||||
callee := inst.CalledValue()
|
||||
if _, ok := asyncFuncs[callee]; !ok || callee == yield {
|
||||
continue
|
||||
}
|
||||
|
||||
uses := getUses(inst)
|
||||
next := llvm.NextInstruction(inst)
|
||||
switch {
|
||||
case nonReturning[callee]:
|
||||
// callee blocks forever
|
||||
coroDebugPrintln("optimizing indefinitely blocking call", f.Name(), callee.Name())
|
||||
|
||||
// never calls getCoroutine - coroutine handle is irrelevant
|
||||
inst.SetOperand(inst.OperandsCount()-2, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// insert return
|
||||
c.builder.SetInsertPointBefore(next)
|
||||
c.createRuntimeCall("noret", []llvm.Value{}, "")
|
||||
var retInst llvm.Value
|
||||
if f.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
retInst = c.builder.CreateRetVoid()
|
||||
} else {
|
||||
retInst = c.builder.CreateRet(llvm.Undef(f.Type().ElementType().ReturnType()))
|
||||
}
|
||||
|
||||
// delete everything after return
|
||||
for next := llvm.NextInstruction(retInst); !next.IsNil(); next = llvm.NextInstruction(retInst) {
|
||||
if next.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
next.ReplaceAllUsesWith(llvm.Undef(next.Type()))
|
||||
}
|
||||
next.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
continue
|
||||
case next.IsAReturnInst().IsNil():
|
||||
// not a return instruction
|
||||
coroDebugPrintln("not a return instruction", f.Name(), callee.Name())
|
||||
case callee.Type().ElementType().ReturnType() != f.Type().ElementType().ReturnType():
|
||||
// return types do not match
|
||||
coroDebugPrintln("return types do not match", f.Name(), callee.Name())
|
||||
case callee.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind:
|
||||
fallthrough
|
||||
case next.Operand(0) == inst:
|
||||
// async tail call optimization - just pass parent handle
|
||||
coroDebugPrintln("doing async tail call opt", f.Name())
|
||||
|
||||
// insert before call
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
// get parent handle
|
||||
parentHandle := c.createRuntimeCall("getParentHandle", []llvm.Value{}, "")
|
||||
|
||||
// pass parent handle directly into function
|
||||
inst.SetOperand(inst.OperandsCount()-2, parentHandle)
|
||||
|
||||
if callee.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind {
|
||||
// delete return value
|
||||
uses[0].SetOperand(0, llvm.Undef(callee.Type().ElementType().ReturnType()))
|
||||
}
|
||||
|
||||
c.builder.SetInsertPointBefore(next)
|
||||
c.createRuntimeCall("yield", []llvm.Value{}, "")
|
||||
c.createRuntimeCall("noret", []llvm.Value{}, "")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
coroDebugPrintln("inserting regular call", f.Name(), callee.Name())
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
// insert call to getCoroutine, this will be lowered later
|
||||
coro := c.createRuntimeCall("getCoroutine", []llvm.Value{}, "")
|
||||
|
||||
// provide coroutine handle to function
|
||||
inst.SetOperand(inst.OperandsCount()-2, coro)
|
||||
|
||||
// Allocate space for the return value.
|
||||
var retvalAlloca llvm.Value
|
||||
if callee.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind {
|
||||
// allocate return value buffer
|
||||
retvalAlloca = c.createInstructionAlloca(callee.Type().ElementType().ReturnType(), inst, "coro.retvalAlloca")
|
||||
|
||||
// call before function
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
|
||||
// cast buffer pointer to *i8
|
||||
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||
|
||||
// set state pointer to return value buffer so it can be written back
|
||||
c.createRuntimeCall("setTaskStatePtr", []llvm.Value{coro, data}, "")
|
||||
}
|
||||
|
||||
// insert yield after starting function
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(inst))
|
||||
yieldCall := c.createRuntimeCall("yield", []llvm.Value{}, "")
|
||||
|
||||
if !retvalAlloca.IsNil() && !inst.FirstUse().IsNil() {
|
||||
// Load the return value from the alloca.
|
||||
// The callee has written the return value to it.
|
||||
c.builder.SetInsertPointBefore(llvm.NextInstruction(yieldCall))
|
||||
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||
inst.ReplaceAllUsesWith(retval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ditch unnecessary tail yields
|
||||
coroDebugPrintln("ditch unnecessary tail yields")
|
||||
noret := c.mod.NamedFunction("runtime.noret")
|
||||
for _, f := range asyncList {
|
||||
if f == yield {
|
||||
continue
|
||||
}
|
||||
coroDebugPrintln("scanning", f.Name())
|
||||
|
||||
// we can only ditch a yield if we can ditch all yields
|
||||
var yields []llvm.Value
|
||||
var canDitch bool
|
||||
scanYields:
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsACallInst().IsNil() || inst.CalledValue() != yield {
|
||||
continue
|
||||
}
|
||||
|
||||
yields = append(yields, inst)
|
||||
|
||||
// we can only ditch the yield if the next instruction is a void return *or* noret
|
||||
next := llvm.NextInstruction(inst)
|
||||
ditchable := false
|
||||
switch {
|
||||
case !next.IsACallInst().IsNil() && next.CalledValue() == noret:
|
||||
coroDebugPrintln("ditching yield with noret", f.Name())
|
||||
ditchable = true
|
||||
case !next.IsAReturnInst().IsNil() && f.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind:
|
||||
coroDebugPrintln("ditching yield with void return", f.Name())
|
||||
ditchable = true
|
||||
case !next.IsAReturnInst().IsNil():
|
||||
coroDebugPrintln("not ditching because return is not void", f.Name(), f.Type().ElementType().ReturnType().String())
|
||||
default:
|
||||
coroDebugPrintln("not ditching", f.Name())
|
||||
}
|
||||
if !ditchable {
|
||||
// unditchable yield
|
||||
canDitch = false
|
||||
break scanYields
|
||||
}
|
||||
|
||||
// ditchable yield
|
||||
canDitch = true
|
||||
}
|
||||
}
|
||||
|
||||
if canDitch {
|
||||
coroDebugPrintln("ditching all in", f.Name())
|
||||
for _, inst := range yields {
|
||||
if !llvm.NextInstruction(inst).IsAReturnInst().IsNil() {
|
||||
// insert noret
|
||||
coroDebugPrintln("insering noret", f.Name())
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.createRuntimeCall("noret", []llvm.Value{}, "")
|
||||
}
|
||||
|
||||
// delete original yield
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate return reactivations
|
||||
coroDebugPrintln("generate return reactivations")
|
||||
for _, f := range asyncList {
|
||||
if f == yield {
|
||||
continue
|
||||
}
|
||||
coroDebugPrintln("scanning", f.Name())
|
||||
|
||||
var retPtr llvm.Value
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
block:
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
switch {
|
||||
case !inst.IsACallInst().IsNil() && inst.CalledValue() == noret:
|
||||
// does not return normally - skip this basic block
|
||||
coroDebugPrintln("noret found - skipping", f.Name(), bb.AsValue().Name())
|
||||
break block
|
||||
case !inst.IsAReturnInst().IsNil():
|
||||
// return instruction - rewrite to reactivation
|
||||
coroDebugPrintln("adding return reactivation", f.Name(), bb.AsValue().Name())
|
||||
if f.Type().ElementType().ReturnType().TypeKind() != llvm.VoidTypeKind {
|
||||
// returns something
|
||||
if retPtr.IsNil() {
|
||||
coroDebugPrintln("adding return pointer get", f.Name())
|
||||
|
||||
// get return pointer in entry block
|
||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||
parentHandle := c.createRuntimeCall("getParentHandle", []llvm.Value{}, "")
|
||||
ptr := c.createRuntimeCall("getTaskStatePtr", []llvm.Value{parentHandle}, "")
|
||||
retPtr = c.builder.CreateBitCast(ptr, llvm.PointerType(f.Type().ElementType().ReturnType(), 0), "retPtr")
|
||||
}
|
||||
|
||||
coroDebugPrintln("adding return store", f.Name(), bb.AsValue().Name())
|
||||
|
||||
// store result into return pointer
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.builder.CreateStore(inst.Operand(0), retPtr)
|
||||
|
||||
// delete return value
|
||||
inst.SetOperand(0, llvm.Undef(f.Type().ElementType().ReturnType()))
|
||||
}
|
||||
|
||||
// insert reactivation call
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
parentHandle := c.createRuntimeCall("getParentHandle", []llvm.Value{}, "")
|
||||
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||
|
||||
// mark as noret
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.createRuntimeCall("noret", []llvm.Value{}, "")
|
||||
break block
|
||||
|
||||
// DO NOT ERASE THE RETURN!!!!!!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a few LLVM intrinsics for coroutine support.
|
||||
|
||||
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
|
||||
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
|
||||
|
||||
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
|
||||
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
|
||||
|
||||
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||
|
||||
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||
|
||||
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
|
||||
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
|
||||
|
||||
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
|
||||
|
||||
// split blocks and add LLVM coroutine intrinsics
|
||||
coroDebugPrintln("split blocks and add LLVM coroutine intrinsics")
|
||||
for _, f := range asyncList {
|
||||
if f == yield {
|
||||
continue
|
||||
}
|
||||
|
||||
// find calls to yield
|
||||
var yieldCalls []llvm.Value
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() && inst.CalledValue() == yield {
|
||||
yieldCalls = append(yieldCalls, inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(yieldCalls) == 0 {
|
||||
// no yields - we do not have to LLVM-ify this
|
||||
coroDebugPrintln("skipping", f.Name())
|
||||
deleteQueue := []llvm.Value{}
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() && inst.CalledValue() == getCoroutine {
|
||||
// no seperate local task - replace getCoroutine with getParentHandle
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
inst.ReplaceAllUsesWith(c.createRuntimeCall("getParentHandle", []llvm.Value{}, ""))
|
||||
deleteQueue = append(deleteQueue, inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range deleteQueue {
|
||||
v.EraseFromParentAsInstruction()
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
coroDebugPrintln("converting", f.Name())
|
||||
|
||||
// get frame data to mess with
|
||||
frame := asyncFuncs[f]
|
||||
|
||||
// add basic blocks to put cleanup and suspend code
|
||||
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
|
||||
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
|
||||
|
||||
// at start of function
|
||||
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||
taskState := c.builder.CreateAlloca(c.getLLVMRuntimeType("taskState"), "task.state")
|
||||
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||
|
||||
// get LLVM-assigned coroutine ID
|
||||
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
stateI8,
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
llvm.ConstNull(c.i8ptrType),
|
||||
}, "task.token")
|
||||
|
||||
// allocate buffer for task struct
|
||||
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
|
||||
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
|
||||
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
|
||||
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)
|
||||
}
|
||||
|
||||
// invoke llvm.coro.begin intrinsic and save task pointer
|
||||
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||
|
||||
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
|
||||
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
|
||||
c.createRuntimeCall("free", []llvm.Value{mem}, "")
|
||||
c.builder.CreateBr(frame.suspendBlock)
|
||||
|
||||
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
|
||||
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
|
||||
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
|
||||
returnType := f.Type().ElementType().ReturnType()
|
||||
if returnType.TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateRetVoid()
|
||||
} else {
|
||||
c.builder.CreateRet(llvm.Undef(returnType))
|
||||
}
|
||||
|
||||
for _, inst := range yieldCalls {
|
||||
// Replace call to yield with a suspension of the coroutine.
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||
llvm.ConstNull(c.ctx.TokenType()),
|
||||
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||
}, "")
|
||||
wakeup := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
ditchQueue := []llvm.Value{}
|
||||
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if !inst.IsACallInst().IsNil() && inst.CalledValue() == getCoroutine {
|
||||
// replace getCoroutine calls with the task handle
|
||||
inst.ReplaceAllUsesWith(frame.taskHandle)
|
||||
ditchQueue = append(ditchQueue, inst)
|
||||
}
|
||||
if !inst.IsACallInst().IsNil() && inst.CalledValue() == noret {
|
||||
// replace tail yield with jump to cleanup, otherwise we end up with undefined behavior
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
c.builder.CreateBr(frame.cleanupBlock)
|
||||
ditchQueue = append(ditchQueue, inst, llvm.NextInstruction(inst))
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, v := range ditchQueue {
|
||||
v.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
|
||||
// check for leftover calls to getCoroutine
|
||||
if uses := getUses(getCoroutine); len(uses) > 0 {
|
||||
useNames := make([]string, 0, len(uses))
|
||||
for _, u := range uses {
|
||||
if u.InstructionParent().Parent().Name() == "runtime.llvmCoroRefHolder" {
|
||||
continue
|
||||
}
|
||||
useNames = append(useNames, u.InstructionParent().Parent().Name())
|
||||
}
|
||||
if len(useNames) > 0 {
|
||||
panic("bad use of getCoroutine: " + strings.Join(useNames, ","))
|
||||
}
|
||||
}
|
||||
|
||||
// rewrite calls to getParentHandle
|
||||
for _, inst := range getUses(c.mod.NamedFunction("runtime.getParentHandle")) {
|
||||
f := inst.InstructionParent().Parent()
|
||||
var parentHandle llvm.Value
|
||||
parentHandle = f.LastParam()
|
||||
if parentHandle.IsNil() || parentHandle.Name() != "parentHandle" {
|
||||
// sanity check
|
||||
panic("trying to make exported function async: " + f.Name())
|
||||
}
|
||||
inst.ReplaceAllUsesWith(parentHandle)
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
// ditch invalid function attributes
|
||||
bads := []llvm.Value{c.mod.NamedFunction("runtime.setTaskStatePtr")}
|
||||
for _, f := range append(bads, asyncList...) {
|
||||
// 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)
|
||||
n := f.ParamsCount()
|
||||
for i := 0; i <= n; i++ {
|
||||
f.RemoveEnumAttributeAtIndex(i, kindID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate noret
|
||||
for _, inst := range getUses(noret) {
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return true, c.lowerMakeGoroutineCalls(true)
|
||||
}
|
||||
|
||||
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
|
||||
// after the regular goroutine transformations. The started goroutines are
|
||||
// either non-blocking (in which case they can be called directly) or blocking,
|
||||
// in which case they will ask the scheduler themselves to be rescheduled.
|
||||
func (c *Compiler) lowerMakeGoroutineCalls(sched bool) error {
|
||||
// The following Go code:
|
||||
// go startedGoroutine()
|
||||
//
|
||||
// Is translated to the following during IR construction, to preserve the
|
||||
// fact that this function should be called as a new goroutine.
|
||||
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
|
||||
// %1 = bitcast i8* %0 to void (i8*, i8*)*
|
||||
// call void %1(i8* undef, i8* undef)
|
||||
//
|
||||
// This function rewrites it to a direct call:
|
||||
// call void @main.startedGoroutine(i8* undef, i8* null)
|
||||
|
||||
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||
for _, goroutine := range getUses(makeGoroutine) {
|
||||
ptrtointIn := goroutine.Operand(0)
|
||||
origFunc := ptrtointIn.Operand(0)
|
||||
uses := getUses(goroutine)
|
||||
if len(uses) != 1 || uses[0].IsAIntToPtrInst().IsNil() {
|
||||
return errors.New("expected exactly 1 inttoptr use of runtime.makeGoroutine")
|
||||
}
|
||||
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")
|
||||
}
|
||||
realCall := uses[0]
|
||||
|
||||
// Create call instruction.
|
||||
var params []llvm.Value
|
||||
for i := 0; i < realCall.OperandsCount()-1; i++ {
|
||||
params = append(params, realCall.Operand(i))
|
||||
}
|
||||
c.builder.SetInsertPointBefore(realCall)
|
||||
if (!sched) || goroutine.InstructionParent().Parent() == c.mod.NamedFunction("runtime.getFakeCoroutine") {
|
||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||
} else {
|
||||
params[len(params)-1] = c.createRuntimeCall("getFakeCoroutine", []llvm.Value{}, "") // parent coroutine handle (must not be nil)
|
||||
}
|
||||
c.builder.CreateCall(origFunc, params, "")
|
||||
realCall.EraseFromParentAsInstruction()
|
||||
inttoptrOut.EraseFromParentAsInstruction()
|
||||
goroutine.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
if !sched && len(getUses(c.mod.NamedFunction("runtime.getFakeCoroutine"))) > 0 {
|
||||
panic("getFakeCoroutine used without scheduler")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+106
-113
@@ -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
|
||||
@@ -49,7 +45,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// signatureInfo is a Go signature of an interface method. It does not represent
|
||||
@@ -104,15 +100,6 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||
panic("could not find method")
|
||||
}
|
||||
|
||||
// id returns the fully-qualified type name including import path, removing the
|
||||
// $type suffix.
|
||||
func (t *typeInfo) id() string {
|
||||
if !strings.HasSuffix(t.name, "$type") {
|
||||
panic("concrete type does not have $type suffix: " + t.name)
|
||||
}
|
||||
return t.name[:len(t.name)-len("$type")]
|
||||
}
|
||||
|
||||
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||
// first.
|
||||
type typeInfoSlice []*typeInfo
|
||||
@@ -175,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
|
||||
@@ -203,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++
|
||||
}
|
||||
|
||||
@@ -295,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 {
|
||||
@@ -313,7 +298,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
// interface value should already have returned false.
|
||||
// Replace the function pointer with undef (which will then be
|
||||
// called), indicating to the optimizer this code is unreachable.
|
||||
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType))
|
||||
use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
|
||||
use.EraseFromParentAsInstruction()
|
||||
} else if len(itf.types) == 1 {
|
||||
// There is only one implementation of the given type.
|
||||
@@ -323,12 +308,12 @@ func (p *lowerInterfacesPass) run() {
|
||||
// There are multiple types implementing this interface, thus there
|
||||
// are multiple possible functions to call. Delegate calling the
|
||||
// right function to a special wrapper function.
|
||||
bitcasts := getUses(use)
|
||||
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||
inttoptrs := getUses(use)
|
||||
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||
}
|
||||
bitcast := bitcasts[0]
|
||||
calls := getUses(bitcast)
|
||||
inttoptr := inttoptrs[0]
|
||||
calls := getUses(inttoptr)
|
||||
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
|
||||
panic("expected exactly one call use of runtime.interfaceMethod")
|
||||
}
|
||||
@@ -349,14 +334,14 @@ func (p *lowerInterfacesPass) run() {
|
||||
// call, after selecting the right concrete type.
|
||||
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
|
||||
|
||||
// Replace the old lookup/bitcast/call with the new call.
|
||||
// Replace the old lookup/inttoptr/call with the new call.
|
||||
p.builder.SetInsertPointBefore(call)
|
||||
retval := p.builder.CreateCall(redirector, params, "")
|
||||
if retval.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
call.ReplaceAllUsesWith(retval)
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
@@ -365,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()]
|
||||
@@ -415,7 +386,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
for _, t := range p.types {
|
||||
typeSlice = append(typeSlice, t)
|
||||
}
|
||||
sort.Sort(typeSlice)
|
||||
sort.Sort(sort.Reverse(typeSlice))
|
||||
|
||||
// A type code must fit in 16 bits.
|
||||
if len(typeSlice) >= 1<<16 {
|
||||
@@ -423,16 +394,16 @@ func (p *lowerInterfacesPass) run() {
|
||||
}
|
||||
|
||||
// Assign a type code for each type.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
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
|
||||
@@ -444,7 +415,7 @@ func (p *lowerInterfacesPass) run() {
|
||||
var commaOk llvm.Value
|
||||
if t.countMakeInterfaces == 0 {
|
||||
// impossible type assert: optimize accordingly
|
||||
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
|
||||
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||
} else {
|
||||
// regular type assert
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
@@ -469,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 {
|
||||
@@ -485,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.
|
||||
@@ -553,22 +517,51 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
||||
return p.signatures[name]
|
||||
}
|
||||
|
||||
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a
|
||||
// replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
|
||||
// concrete method. This can be done when only one type implements the
|
||||
// interface.
|
||||
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
|
||||
bitcasts := getUses(use)
|
||||
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
|
||||
panic("expected exactly one bitcast use of runtime.interfaceMethod")
|
||||
inttoptrs := getUses(use)
|
||||
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||
}
|
||||
bitcast := bitcasts[0]
|
||||
inttoptr := inttoptrs[0]
|
||||
function := typ.getMethod(signature).function
|
||||
if bitcast.Type() != function.Type() {
|
||||
p.builder.SetInsertPointBefore(use)
|
||||
function = p.builder.CreateBitCast(function, bitcast.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()
|
||||
}
|
||||
}
|
||||
bitcast.ReplaceAllUsesWith(function)
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
@@ -610,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)
|
||||
@@ -631,9 +624,9 @@ func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo)
|
||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||
}
|
||||
|
||||
// getInterfaceMethodFunc return a function that returns a function pointer for
|
||||
// calling a method on an interface. It only declares the function,
|
||||
// createInterfaceMethodFunc actually defines the function.
|
||||
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
||||
// It only declares the function, createInterfaceMethodFunc actually defines the
|
||||
// function.
|
||||
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
||||
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||
// This function has already been created.
|
||||
@@ -668,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()
|
||||
|
||||
@@ -691,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.id())
|
||||
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.
|
||||
|
||||
+226
-163
@@ -8,10 +8,12 @@ package compiler
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/ir"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
||||
@@ -20,91 +22,232 @@ 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, global string, pos token.Pos) (llvm.Value, error) {
|
||||
var itfValue llvm.Value
|
||||
size := c.targetData.TypeAllocSize(val.Type())
|
||||
if size > c.targetData.TypeAllocSize(c.i8ptrType) {
|
||||
if global != "" {
|
||||
// Allocate in a global variable.
|
||||
global := llvm.AddGlobal(c.mod, val.Type(), global+"$itfvalue")
|
||||
global.SetInitializer(val)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
itfValueRaw := llvm.ConstInBoundsGEP(global, []llvm.Value{zero, zero})
|
||||
itfValue = llvm.ConstBitCast(itfValueRaw, c.i8ptrType)
|
||||
} else {
|
||||
// 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:
|
||||
// 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 {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$type")
|
||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
|
||||
// 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 {
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
return "named:" + t.String()
|
||||
case *types.Array:
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
case types.Bool:
|
||||
kind = "bool"
|
||||
case types.Int:
|
||||
kind = "int"
|
||||
case types.Int8:
|
||||
kind = "int8"
|
||||
case types.Int16:
|
||||
kind = "int16"
|
||||
case types.Int32:
|
||||
kind = "int32"
|
||||
case types.Int64:
|
||||
kind = "int64"
|
||||
case types.Uint:
|
||||
kind = "uint"
|
||||
case types.Uint8:
|
||||
kind = "uint8"
|
||||
case types.Uint16:
|
||||
kind = "uint16"
|
||||
case types.Uint32:
|
||||
kind = "uint32"
|
||||
case types.Uint64:
|
||||
kind = "uint64"
|
||||
case types.Uintptr:
|
||||
kind = "uintptr"
|
||||
case types.Float32:
|
||||
kind = "float32"
|
||||
case types.Float64:
|
||||
kind = "float64"
|
||||
case types.Complex64:
|
||||
kind = "complex64"
|
||||
case types.Complex128:
|
||||
kind = "complex128"
|
||||
case types.String:
|
||||
kind = "string"
|
||||
case types.UnsafePointer:
|
||||
kind = "unsafeptr"
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + kind
|
||||
case *types.Chan:
|
||||
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:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
params[i] = getTypeCodeName(t.Params().At(i).Type())
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
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")
|
||||
}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
@@ -113,13 +256,10 @@ 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.ConstBitCast(fn, c.i8ptrType),
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
})
|
||||
methods[i] = methodInfo
|
||||
}
|
||||
@@ -129,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
|
||||
@@ -178,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{}
|
||||
@@ -243,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{
|
||||
@@ -316,22 +410,20 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
|
||||
c.getMethodSignature(instr.Method),
|
||||
}
|
||||
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
||||
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
|
||||
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.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
// 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
|
||||
@@ -347,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?
|
||||
@@ -368,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
|
||||
@@ -381,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
|
||||
@@ -396,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{})
|
||||
}
|
||||
|
||||
@@ -407,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, "")
|
||||
@@ -437,6 +502,4 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) er
|
||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||
c.builder.CreateRet(ret)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This file contains helper functions for LLVM that are not exposed in the Go
|
||||
// bindings.
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
var uses []llvm.Value
|
||||
use := value.FirstUse()
|
||||
for !use.IsNil() {
|
||||
uses = append(uses, use.User())
|
||||
use = use.NextUse()
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// createInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
||||
func (c *Compiler) createInstructionAlloca(t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
||||
alloca := c.createEntryBlockAlloca(t, name)
|
||||
c.builder.SetInsertPointBefore(inst)
|
||||
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}, "")
|
||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||
c.builder.SetInsertPointBefore(next)
|
||||
} else {
|
||||
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
}
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{size, bitcast}, "")
|
||||
return alloca
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||
oldBlock := afterInst.InstructionParent()
|
||||
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
|
||||
var nextInstructions []llvm.Value // values to move
|
||||
|
||||
// Collect to-be-moved instructions.
|
||||
inst := afterInst
|
||||
for {
|
||||
inst = llvm.NextInstruction(inst)
|
||||
if inst.IsNil() {
|
||||
break
|
||||
}
|
||||
nextInstructions = append(nextInstructions, inst)
|
||||
}
|
||||
|
||||
// Move instructions.
|
||||
c.builder.SetInsertPointAtEnd(newBlock)
|
||||
for _, inst := range nextInstructions {
|
||||
inst.RemoveFromParentAsInstruction()
|
||||
c.builder.Insert(inst)
|
||||
}
|
||||
|
||||
// Find PHI nodes to update.
|
||||
var phiNodes []llvm.Value // PHI nodes to update
|
||||
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAPHINode().IsNil() {
|
||||
continue
|
||||
}
|
||||
needsUpdate := false
|
||||
incomingCount := inst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
if inst.IncomingBlock(i) == oldBlock {
|
||||
needsUpdate = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !needsUpdate {
|
||||
// PHI node has no incoming edge from the old block.
|
||||
continue
|
||||
}
|
||||
phiNodes = append(phiNodes, inst)
|
||||
}
|
||||
}
|
||||
|
||||
// Update PHI nodes.
|
||||
for _, phi := range phiNodes {
|
||||
c.builder.SetInsertPointBefore(phi)
|
||||
newPhi := c.builder.CreatePHI(phi.Type(), "")
|
||||
incomingCount := phi.IncomingCount()
|
||||
incomingVals := make([]llvm.Value, incomingCount)
|
||||
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
value := phi.IncomingValue(i)
|
||||
block := phi.IncomingBlock(i)
|
||||
if block == oldBlock {
|
||||
block = newBlock
|
||||
}
|
||||
incomingVals[i] = value
|
||||
incomingBlocks[i] = block
|
||||
}
|
||||
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
||||
phi.ReplaceAllUsesWith(newPhi)
|
||||
phi.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+38
-23
@@ -6,16 +6,18 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
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, "todo: map lookup key type: "+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, "todo: map update key type: "+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, "todo: map delete key type: "+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()
|
||||
@@ -120,6 +131,10 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *types.Array:
|
||||
return hashmapIsBinaryKey(keyType.Elem())
|
||||
case *types.Named:
|
||||
return hashmapIsBinaryKey(keyType.Underlying())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
+79
-260
@@ -3,7 +3,8 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Run the LLVM optimizer over the module.
|
||||
@@ -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()
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
transform.OptimizeMaps(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
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,11 +61,37 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
c.OptimizeAllocs()
|
||||
c.OptimizeStringToBytes()
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
|
||||
// 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
|
||||
}
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("optimizations caused a verification failure")
|
||||
@@ -70,272 +107,54 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
}
|
||||
|
||||
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
||||
for _, name := range c.getFunctionsUsedInTransforms() {
|
||||
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) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
// Run module passes.
|
||||
modPasses := llvm.NewPassManager()
|
||||
defer modPasses.Dispose()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
if !unknownUses {
|
||||
// This map can be entirely removed, as it is only created but never
|
||||
// used.
|
||||
for _, inst := range updateInsts {
|
||||
inst.EraseFromParentAsInstruction()
|
||||
}
|
||||
makeInst.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
||||
// possible. This optimizes the following pattern:
|
||||
// w.Write([]byte("foo"))
|
||||
// where Write does not store to the slice.
|
||||
func (c *Compiler) OptimizeStringToBytes() {
|
||||
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
|
||||
if stringToBytes.IsNil() {
|
||||
// nothing to optimize
|
||||
return
|
||||
}
|
||||
|
||||
for _, call := range getUses(stringToBytes) {
|
||||
strptr := call.Operand(0)
|
||||
strlen := call.Operand(1)
|
||||
|
||||
// strptr is always constant because strings are always constant.
|
||||
|
||||
convertedAllUses := true
|
||||
for _, use := range getUses(call) {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAExtractValueInst() == nilValue {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
switch use.Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
// A length (len or cap). Propagate the length value.
|
||||
use.ReplaceAllUsesWith(strlen)
|
||||
use.EraseFromParentAsInstruction()
|
||||
case llvm.PointerTypeKind:
|
||||
// The string pointer itself.
|
||||
if !c.isReadOnly(use) {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
use.ReplaceAllUsesWith(strptr)
|
||||
use.EraseFromParentAsInstruction()
|
||||
default:
|
||||
// should not happen
|
||||
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
||||
}
|
||||
}
|
||||
if convertedAllUses {
|
||||
// Call to runtime.stringToBytes can be eliminated: both the input
|
||||
// and the output is constant.
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
}
|
||||
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()
|
||||
for _, use := range getUses(fn) {
|
||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||
panic("expected use of a panic function to be a call")
|
||||
}
|
||||
heapalloc.EraseFromParentAsInstruction()
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
c.builder.CreateCall(trap, nil, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
for _, use := range uses {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAGetElementPtrInst() != nilValue {
|
||||
if !c.isReadOnly(use) {
|
||||
return false
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
if !c.hasFlag(use, value, "readonly") {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// Unknown instruction, might not be readonly.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether all uses of this param as parameter to the call have the given
|
||||
// flag. In most cases, there will only be one use but a function could take the
|
||||
// same parameter twice, in which case both must have the flag.
|
||||
// A flag can be any enum flag, like "readonly".
|
||||
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||
fn := call.CalledValue()
|
||||
nilValue := llvm.Value{}
|
||||
if fn.IsAFunction() == nilValue {
|
||||
// This is not a function but something else, like a function pointer.
|
||||
return false
|
||||
}
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
for i := 0; i < fn.ParamsCount(); i++ {
|
||||
if call.Operand(i) != param {
|
||||
// This is not the parameter we're checking.
|
||||
continue
|
||||
}
|
||||
index := i + 1 // param attributes start at 1
|
||||
attr := fn.GetEnumAttributeAtIndex(index, kindID)
|
||||
nilAttribute := llvm.Attribute{}
|
||||
if attr == nilAttribute {
|
||||
// At least one parameter doesn't have the flag (there may be
|
||||
// multiple).
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
var uses []llvm.Value
|
||||
use := value.FirstUse()
|
||||
for !use.IsNil() {
|
||||
uses = append(uses, use.User())
|
||||
use = use.NextUse()
|
||||
}
|
||||
return uses
|
||||
}
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
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,
|
||||
"int8": 3,
|
||||
"int16": 4,
|
||||
"int32": 5,
|
||||
"int64": 6,
|
||||
"uint": 7,
|
||||
"uint8": 8,
|
||||
"uint16": 9,
|
||||
"uint32": 10,
|
||||
"uint64": 11,
|
||||
"uintptr": 12,
|
||||
"float32": 13,
|
||||
"float64": 14,
|
||||
"complex64": 15,
|
||||
"complex128": 16,
|
||||
"string": 17,
|
||||
"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) {
|
||||
// if reflect were not used, we could skip generating the sidetable
|
||||
// this does not help in practice, and is difficult to do correctly
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
state := typeCodeAssignmentState{
|
||||
fallbackIndex: 1,
|
||||
uintptrLen: c.uintptrType.IntTypeWidth(),
|
||||
namedBasicTypes: make(map[string]int),
|
||||
namedNonBasicTypes: make(map[string]int),
|
||||
arrayTypes: make(map[string]int),
|
||||
structTypes: make(map[string]int),
|
||||
structNames: make(map[string]int),
|
||||
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
|
||||
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
|
||||
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||
needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||
}
|
||||
for _, t := range typeSlice {
|
||||
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.
|
||||
// Particularly important on systems with 16-bit pointers (e.g.
|
||||
// AVR).
|
||||
panic("compiler: could not store type code number inside interface type code")
|
||||
}
|
||||
t.num = num.Uint64()
|
||||
}
|
||||
|
||||
// Only create this sidetable when it is necessary.
|
||||
if state.needsNamedNonBasicTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsArrayTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructTypesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
if state.needsStructNamesSidetable {
|
||||
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetUnnamedAddr(true)
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
class, value := getClassAndValueFromTypeCode(typecode)
|
||||
name := ""
|
||||
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:
|
||||
// ...xxxxx0
|
||||
// where xxxxx is allocated for the 18 possible basic types and all the
|
||||
// upper bits are used to indicate the named type.
|
||||
num, ok := basicTypes[value]
|
||||
if !ok {
|
||||
panic("invalid basic type: " + value)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(state.getBasicNamedTypeNum(name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Non-baisc types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the non-basic type. The upper bits contain
|
||||
// whatever the type contains. Types that wrap a single other type
|
||||
// (channel, interface, pointer, slice) just contain the bits of the
|
||||
// wrapped type. Other types (like struct) need more fields and thus
|
||||
// cannot be encoded as a simple prefix.
|
||||
var classNumber int64
|
||||
if n, ok := nonBasicTypes[class]; ok {
|
||||
classNumber = n
|
||||
} else {
|
||||
panic("unknown type kind: " + class)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 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,117 @@
|
||||
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"
|
||||
"strconv"
|
||||
"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
|
||||
align int // go:align
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
if info.align > c.targetData.ABITypeAlignment(llvmType) {
|
||||
llvmGlobal.SetAlignment(info.align)
|
||||
}
|
||||
}
|
||||
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]
|
||||
}
|
||||
case "//go:align":
|
||||
align, err := strconv.Atoi(parts[1])
|
||||
if err == nil {
|
||||
info.align = align
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package compiler
|
||||
|
||||
// This file implements the syscall.Syscall and syscall.Syscall6 instructions as
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num := c.getValue(frame, call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64":
|
||||
if c.GOOS == "darwin" {
|
||||
// Darwin adds this magic number to system call numbers:
|
||||
//
|
||||
// > Syscall classes for 64-bit system call entry.
|
||||
// > For 64-bit users, the 32-bit syscall number is partitioned
|
||||
// > with the high-order bits representing the class and low-order
|
||||
// > bits being the syscall number within that class.
|
||||
// > The high-order 32-bits of the 64-bit syscall number are unused.
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
constraints := "={rax},0"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"{rdi}",
|
||||
"{rsi}",
|
||||
"{rdx}",
|
||||
"{r10}",
|
||||
"{r8}",
|
||||
"{r9}",
|
||||
"{r11}",
|
||||
"{r12}",
|
||||
"{r13}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
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.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={r0},0,{r1},{r2},{r7},~{r3}
|
||||
constraints := "={r0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{r1}",
|
||||
"{r2}",
|
||||
"{r3}",
|
||||
"{r4}",
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm64" && c.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
||||
constraints := "={x0}"
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
"0", // tie to output
|
||||
"{x1}",
|
||||
"{x2}",
|
||||
"{x3}",
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
// calling convention.
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
}
|
||||
switch c.GOOS {
|
||||
case "linux":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
// if syscallResult < 0 && syscallResult > -4096 {
|
||||
// err = -syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "darwin":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
// if syscallResult != 0 {
|
||||
// err = syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
}
|
||||
}
|
||||
@@ -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,13 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.11
|
||||
|
||||
require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/creack/goselect v0.1.0 // indirect
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add
|
||||
)
|
||||
@@ -0,0 +1,22 @@
|
||||
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/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||
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=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
|
||||
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/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
// Package goenv returns environment variables that are used in various parts of
|
||||
// the compiler. You can query it manually with the `tinygo env` subcommand.
|
||||
package goenv
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// Keys is a slice of all available environment variable keys.
|
||||
var Keys = []string{
|
||||
"GOOS",
|
||||
"GOARCH",
|
||||
"GOROOT",
|
||||
"GOPATH",
|
||||
"GOCACHE",
|
||||
"TINYGOROOT",
|
||||
}
|
||||
|
||||
// TINYGOROOT is the path to the final location for checking tinygo files. If
|
||||
// unset (by a -X ldflag), then sourceDir() will fallback to the original build
|
||||
// directory.
|
||||
var TINYGOROOT string
|
||||
|
||||
// Get returns a single environment variable, possibly calculating it on-demand.
|
||||
// The empty string is returned for unknown environment variables.
|
||||
func Get(name string) string {
|
||||
switch name {
|
||||
case "GOOS":
|
||||
if dir := os.Getenv("GOOS"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
return runtime.GOOS
|
||||
case "GOARCH":
|
||||
if dir := os.Getenv("GOARCH"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
return runtime.GOARCH
|
||||
case "GOROOT":
|
||||
return getGoroot()
|
||||
case "GOPATH":
|
||||
if dir := os.Getenv("GOPATH"); dir != "" {
|
||||
return dir
|
||||
}
|
||||
|
||||
// fallback
|
||||
home := getHomeDir()
|
||||
return filepath.Join(home, "go")
|
||||
case "GOCACHE":
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
case "TINYGOROOT":
|
||||
return sourceDir()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Return the TINYGOROOT, or exit with an error.
|
||||
func sourceDir() string {
|
||||
// Use $TINYGOROOT as root, if available.
|
||||
root := os.Getenv("TINYGOROOT")
|
||||
if root != "" {
|
||||
if !isSourceDir(root) {
|
||||
fmt.Fprintln(os.Stderr, "error: $TINYGOROOT was not set to the correct root")
|
||||
os.Exit(1)
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
if TINYGOROOT != "" {
|
||||
if !isSourceDir(TINYGOROOT) {
|
||||
fmt.Fprintln(os.Stderr, "error: TINYGOROOT was not set to the correct root")
|
||||
os.Exit(1)
|
||||
}
|
||||
return TINYGOROOT
|
||||
}
|
||||
|
||||
// Find root from executable path.
|
||||
path, err := os.Executable()
|
||||
if err != nil {
|
||||
// Very unlikely. Bail out if it happens.
|
||||
panic("could not get executable path: " + err.Error())
|
||||
}
|
||||
root = filepath.Dir(filepath.Dir(path))
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
// Fallback: use the original directory from where it was built
|
||||
// https://stackoverflow.com/a/32163888/559350
|
||||
_, path, _, _ = runtime.Caller(0)
|
||||
root = filepath.Dir(filepath.Dir(path))
|
||||
if isSourceDir(root) {
|
||||
return root
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "error: could not autodetect root directory, set the TINYGOROOT environment variable to override")
|
||||
os.Exit(1)
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
// isSourceDir returns true if the directory looks like a TinyGo source directory.
|
||||
func isSourceDir(root string) bool {
|
||||
_, err := os.Stat(filepath.Join(root, "src/runtime/internal/sys/zversion.go"))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = os.Stat(filepath.Join(root, "src/device/arm/arm.go"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getHomeDir() string {
|
||||
u, err := user.Current()
|
||||
if err != nil {
|
||||
panic("cannot get current user: " + err.Error())
|
||||
}
|
||||
if u.HomeDir == "" {
|
||||
// This is very unlikely, so panic here.
|
||||
// Not the nicest solution, however.
|
||||
panic("could not find home directory")
|
||||
}
|
||||
return u.HomeDir
|
||||
}
|
||||
|
||||
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
|
||||
// found, it returns an empty string.
|
||||
func getGoroot() string {
|
||||
goroot := os.Getenv("GOROOT")
|
||||
if goroot != "" {
|
||||
// An explicitly set GOROOT always has preference.
|
||||
return goroot
|
||||
}
|
||||
|
||||
// Check for the location of the 'go' binary and base GOROOT on that.
|
||||
binpath, err := exec.LookPath("go")
|
||||
if err == nil {
|
||||
binpath, err = filepath.EvalSymlinks(binpath)
|
||||
if err == nil {
|
||||
goroot := filepath.Dir(filepath.Dir(binpath))
|
||||
if isGoroot(goroot) {
|
||||
return goroot
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check what GOROOT was at compile time.
|
||||
if isGoroot(runtime.GOROOT()) {
|
||||
return runtime.GOROOT()
|
||||
}
|
||||
|
||||
// Check for some standard locations, as a last resort.
|
||||
var candidates []string
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
candidates = []string{
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/lib/go", // from the distribution
|
||||
}
|
||||
case "darwin":
|
||||
candidates = []string{
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/local/opt/go/libexec", // from Homebrew
|
||||
}
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if isGoroot(candidate) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
|
||||
// Can't find GOROOT...
|
||||
return ""
|
||||
}
|
||||
|
||||
// isGoroot checks whether the given path looks like a GOROOT.
|
||||
func isGoroot(goroot string) bool {
|
||||
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
|
||||
return err == nil
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@ package interp
|
||||
// This file provides useful types for errors encountered during IR evaluation.
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Unsupported struct {
|
||||
|
||||
+157
-30
@@ -7,7 +7,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
@@ -84,15 +84,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
|
||||
// Memory operators
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
fr.locals[inst] = &AllocaValue{
|
||||
Underlying: getZeroValue(inst.Type().ElementType()),
|
||||
Dirty: false,
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0))
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() {
|
||||
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()
|
||||
@@ -173,17 +176,47 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
value := fr.getLocal(operand)
|
||||
if bc, ok := value.(*PointerCastValue); ok {
|
||||
value = bc.Underlying // avoid double bitcasts
|
||||
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")
|
||||
}
|
||||
fr.locals[inst] = &PointerCastValue{Eval: fr.Eval, Underlying: value, CastType: inst.Type()}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
// Other operators
|
||||
case !inst.IsAICmpInst().IsNil():
|
||||
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,9 +253,9 @@ 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 := &GlobalValue{
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
@@ -244,13 +277,18 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
keyBuf := fr.getLocal(inst.Operand(1))
|
||||
keyLen := fr.getLocal(inst.Operand(2))
|
||||
valPtr := fr.getLocal(inst.Operand(3))
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
// TODO: unimplemented
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
buf1Ptr := fr.getLocal(inst.Operand(0))
|
||||
@@ -274,10 +312,53 @@ 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}
|
||||
case callee.Name() == "runtime.sliceCopy":
|
||||
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
|
||||
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
|
||||
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
|
||||
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
|
||||
// Slice data pointers are created by adding a global array
|
||||
// and getting the address of the first element using a GEP.
|
||||
// However, before the compiler can pass it to
|
||||
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
|
||||
// to make it a unsafe.Pointer. Now, when the IR builder
|
||||
// sees a bitcast of a GEP with zero indices, it will make
|
||||
// a bitcast of the original array instead of the GEP,
|
||||
// which breaks our assumptions.
|
||||
// Re-add this GEP, in the hope that it it is then of the correct type...
|
||||
dstArray = dstArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
srcArray = srcArray.GetElementPtr([]uint32{0, 0}).(*LocalValue)
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice dst element size does not match pointer type")
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, errors.New("interp: slice src element size does not match pointer type")
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, errors.New("interp: slice element types don't match")
|
||||
}
|
||||
length := dstLen.Value().SExtValue()
|
||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||
length = srcLength
|
||||
}
|
||||
if length < 0 {
|
||||
return nil, nil, errors.New("interp: trying to copy a slice with negative length?")
|
||||
}
|
||||
for i := int64(0); i < length; i++ {
|
||||
// *dst = *src
|
||||
dstArray.Store(srcArray.Load())
|
||||
// dst++
|
||||
dstArray = dstArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
// src++
|
||||
srcArray = srcArray.GetElementPtr([]uint32{1}).(*LocalValue)
|
||||
}
|
||||
case callee.Name() == "runtime.stringToBytes":
|
||||
// convert a string to a []byte
|
||||
bufPtr := fr.getLocal(inst.Operand(0))
|
||||
@@ -297,13 +378,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":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
|
||||
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.
|
||||
@@ -416,17 +539,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)
|
||||
|
||||
+17
-14
@@ -10,7 +10,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Eval struct {
|
||||
@@ -18,14 +18,13 @@ 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
|
||||
}
|
||||
|
||||
// Run evaluates the function with the given name and then eliminates all
|
||||
// callers.
|
||||
func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
if debug {
|
||||
println("\ncompile-time evaluation:")
|
||||
}
|
||||
@@ -33,19 +32,26 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
name := "runtime.initAll"
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: targetData,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
Debug: debug,
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
e.dibuilder = llvm.NewDIBuilder(mod)
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
bb := initAll.EntryBasicBlock()
|
||||
e.builder.SetInsertPointBefore(bb.LastInstruction())
|
||||
e.builder.SetInstDebugLocation(bb.FirstInstruction())
|
||||
// Create a dummy alloca in the entry block that we can set the insert point
|
||||
// to. This is necessary because otherwise we might be removing the
|
||||
// instruction (init call) that we are removing after successful
|
||||
// interpretation.
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
continue
|
||||
}
|
||||
if !inst.IsAReturnInst().IsNil() {
|
||||
break // ret void
|
||||
}
|
||||
@@ -63,14 +69,15 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
_, err := e.Function(call.CalledValue(), []Value{&LocalValue{e, undefPtr}}, pkgName)
|
||||
fn := call.CalledValue()
|
||||
call.EraseFromParentAsInstruction()
|
||||
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
||||
if err == ErrUnreachable {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -114,11 +121,7 @@ func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (
|
||||
// getValue determines what kind of LLVM value it gets and returns the
|
||||
// appropriate Value type.
|
||||
func (e *Eval) getValue(v llvm.Value) Value {
|
||||
if !v.IsAGlobalVariable().IsNil() {
|
||||
return &GlobalValue{e, v}
|
||||
} else {
|
||||
return &LocalValue{e, v}
|
||||
}
|
||||
return &LocalValue{e, v}
|
||||
}
|
||||
|
||||
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func TestInterp(t *testing.T) {
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"slice-copy",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runTest(t, "testdata/"+name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, pathPrefix string) {
|
||||
// Read the input IR.
|
||||
ctx := llvm.NewContext()
|
||||
buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
|
||||
os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
|
||||
if err != nil {
|
||||
t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
|
||||
}
|
||||
mod, err := ctx.ParseIR(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("could not load module:\n%v", err)
|
||||
}
|
||||
|
||||
// Perform the transform.
|
||||
err = Run(mod, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Run some cleanup passes to get easy-to-read outputs.
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
pm.AddGlobalOptimizerPass()
|
||||
pm.AddDeadStoreEliminationPass()
|
||||
pm.Run(mod)
|
||||
|
||||
// Read the expected output IR.
|
||||
out, err := ioutil.ReadFile(pathPrefix + ".out.ll")
|
||||
if err != nil {
|
||||
t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
|
||||
}
|
||||
|
||||
// See whether the transform output matches with the expected output IR.
|
||||
expected := string(out)
|
||||
actual := mod.String()
|
||||
if !fuzzyEqualIR(expected, actual) {
|
||||
t.Logf("output does not match expected output:\n%s", actual)
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
for i, line := range lines1 {
|
||||
if line != lines2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
||||
// stripped out.
|
||||
func filterIrrelevantIRLines(lines []string) []string {
|
||||
var out []string
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line) // drop '\r' on Windows
|
||||
if line == "" || line[0] == ';' {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "source_filename = ") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return out
|
||||
}
|
||||
+41
-12
@@ -1,7 +1,7 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type sideEffectSeverity int
|
||||
@@ -24,6 +24,24 @@ type sideEffectResult struct {
|
||||
// returns whether this function has side effects and if it does, which globals
|
||||
// it mentions anywhere in this function or any called functions.
|
||||
func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
switch fn.Name() {
|
||||
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.sliceCopy":
|
||||
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)
|
||||
}
|
||||
@@ -73,27 +91,38 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
result.updateSeverity(sideEffectAll)
|
||||
continue
|
||||
}
|
||||
name := child.Name()
|
||||
if child.IsDeclaration() {
|
||||
if name == "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 result.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
continue
|
||||
}
|
||||
childSideEffects := e.hasSideEffects(fn)
|
||||
childSideEffects := e.hasSideEffects(child)
|
||||
switch childSideEffects.severity {
|
||||
case sideEffectInProgress, sideEffectNone:
|
||||
// no side effects or recursive function - continue scanning
|
||||
case sideEffectLimited:
|
||||
// The return value may be problematic.
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
case sideEffectAll:
|
||||
result.updateSeverity(sideEffectAll)
|
||||
default:
|
||||
result.update(childSideEffects)
|
||||
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)
|
||||
}
|
||||
@@ -118,7 +147,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||
// or return instruction, in which case the whole function must be marked as
|
||||
// having side effects and be called at runtime.
|
||||
func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
if _, ok := dirtyLocals[inst]; ok {
|
||||
// It is already known that this local is dirty.
|
||||
return true
|
||||
@@ -156,7 +185,7 @@ func (r *sideEffectResult) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct
|
||||
// For a list:
|
||||
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
|
||||
dirtyLocals[user] = struct{}{}
|
||||
if r.hasLocalSideEffects(dirtyLocals, user) {
|
||||
if e.hasLocalSideEffects(dirtyLocals, user) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+42
@@ -0,0 +1,42 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.v1 = internal global i64 0
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printnl() unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.init()
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
%0 = load i64, i64* @main.v1
|
||||
call void @runtime.printint64(i64 %0)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @runtime.init() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
store i64 3, i64* @main.v1
|
||||
call void @"main.init#1"()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @"main.init#1"() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printnl() unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 3)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.uint8SliceSrc.buf = internal global [2 x i8] c"\03d"
|
||||
@main.uint8SliceSrc = internal unnamed_addr global { i8*, i64, i64 } { i8* getelementptr inbounds ([2 x i8], [2 x i8]* @main.uint8SliceSrc.buf, i32 0, i32 0), i64 2, i64 2 }
|
||||
@main.uint8SliceDst = internal unnamed_addr global { i8*, i64, i64 } zeroinitializer
|
||||
@main.int16SliceSrc.buf = internal global [3 x i16] [i16 5, i16 123, i16 1024]
|
||||
@main.int16SliceSrc = internal unnamed_addr global { i16*, i64, i64 } { i16* getelementptr inbounds ([3 x i16], [3 x i16]* @main.int16SliceSrc.buf, i32 0, i32 0), i64 3, i64 3 }
|
||||
@main.int16SliceDst = internal unnamed_addr global { i16*, i64, i64 } zeroinitializer
|
||||
|
||||
declare i64 @runtime.sliceCopy(i8* %dst, i8* %src, i64 %dstLen, i64 %srcLen, i64 %elemSize) unnamed_addr
|
||||
|
||||
declare i8* @runtime.alloc(i64) unnamed_addr
|
||||
|
||||
declare void @runtime.printuint8(i8)
|
||||
|
||||
declare void @runtime.printint16(i16)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
; print(uintSliceSrc[0])
|
||||
%uint8SliceSrc.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc, i64 0, i32 0)
|
||||
%uint8SliceSrc.val = load i8, i8* %uint8SliceSrc.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceSrc.val)
|
||||
|
||||
; print(uintSliceDst[0])
|
||||
%uint8SliceDst.buf = load i8*, i8** getelementptr inbounds ({ i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceDst, i64 0, i32 0)
|
||||
%uint8SliceDst.val = load i8, i8* %uint8SliceDst.buf
|
||||
call void @runtime.printuint8(i8 %uint8SliceDst.val)
|
||||
|
||||
; print(int16SliceSrc[0])
|
||||
%int16SliceSrc.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc, i64 0, i32 0)
|
||||
%int16SliceSrc.val = load i16, i16* %int16SliceSrc.buf
|
||||
call void @runtime.printint16(i16 %int16SliceSrc.val)
|
||||
|
||||
; print(int16SliceDst[0])
|
||||
%int16SliceDst.buf = load i16*, i16** getelementptr inbounds ({ i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceDst, i64 0, i32 0)
|
||||
%int16SliceDst.val = load i16, i16* %int16SliceDst.buf
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; equivalent of:
|
||||
; uint8SliceDst = make([]uint8, len(uint8SliceSrc))
|
||||
%uint8SliceSrc = load { i8*, i64, i64 }, { i8*, i64, i64 }* @main.uint8SliceSrc
|
||||
%uint8SliceSrc.len = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 1
|
||||
%uint8SliceDst.buf = call i8* @runtime.alloc(i64 %uint8SliceSrc.len)
|
||||
%0 = insertvalue { i8*, i64, i64 } undef, i8* %uint8SliceDst.buf, 0
|
||||
%1 = insertvalue { i8*, i64, i64 } %0, i64 %uint8SliceSrc.len, 1
|
||||
%2 = insertvalue { i8*, i64, i64 } %1, i64 %uint8SliceSrc.len, 2
|
||||
store { i8*, i64, i64 } %2, { i8*, i64, i64 }* @main.uint8SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(uint8SliceDst, uint8SliceSrc)
|
||||
%uint8SliceSrc.buf = extractvalue { i8*, i64, i64 } %uint8SliceSrc, 0
|
||||
%copy.n = call i64 @runtime.sliceCopy(i8* %uint8SliceDst.buf, i8* %uint8SliceSrc.buf, i64 %uint8SliceSrc.len, i64 %uint8SliceSrc.len, i64 1)
|
||||
|
||||
; equivalent of:
|
||||
; int16SliceDst = make([]int16, len(int16SliceSrc))
|
||||
%int16SliceSrc = load { i16*, i64, i64 }, { i16*, i64, i64 }* @main.int16SliceSrc
|
||||
%int16SliceSrc.len = extractvalue { i16*, i64, i64 } %int16SliceSrc, 1
|
||||
%int16SliceSrc.len.bytes = mul i64 %int16SliceSrc.len, 2
|
||||
%int16SliceDst.buf.raw = call i8* @runtime.alloc(i64 %int16SliceSrc.len.bytes)
|
||||
%int16SliceDst.buf = bitcast i8* %int16SliceDst.buf.raw to i16*
|
||||
%3 = insertvalue { i16*, i64, i64 } undef, i16* %int16SliceDst.buf, 0
|
||||
%4 = insertvalue { i16*, i64, i64 } %3, i64 %int16SliceSrc.len, 1
|
||||
%5 = insertvalue { i16*, i64, i64 } %4, i64 %int16SliceSrc.len, 2
|
||||
store { i16*, i64, i64 } %5, { i16*, i64, i64 }* @main.int16SliceDst
|
||||
|
||||
; equivalent of:
|
||||
; copy(int16SliceDst, int16SliceSrc)
|
||||
%int16SliceSrc.buf = extractvalue { i16*, i64, i64 } %int16SliceSrc, 0
|
||||
%int16SliceSrc.buf.i8ptr = bitcast i16* %int16SliceSrc.buf to i8*
|
||||
%int16SliceDst.buf.i8ptr = bitcast i16* %int16SliceDst.buf to i8*
|
||||
%copy.n2 = call i64 @runtime.sliceCopy(i8* %int16SliceDst.buf.i8ptr, i8* %int16SliceSrc.buf.i8ptr, i64 %int16SliceSrc.len, i64 %int16SliceSrc.len, i64 2)
|
||||
|
||||
ret void
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @runtime.printuint8(i8) local_unnamed_addr
|
||||
|
||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printint16(i16 5)
|
||||
ret void
|
||||
}
|
||||
+30
-45
@@ -1,7 +1,7 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
@@ -16,53 +16,12 @@ 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 {
|
||||
if !strLen.IsConstant() {
|
||||
panic("getStringBytes with a non-constant length")
|
||||
}
|
||||
buf := make([]byte, strLen.ZExtValue())
|
||||
for i := range buf {
|
||||
c := strPtr.GetElementPtr([]uint32{uint32(i)}).Load()
|
||||
@@ -91,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
|
||||
}
|
||||
|
||||
+122
-300
@@ -5,7 +5,7 @@ package interp
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A Value is a LLVM value with some extra methods attached for easier
|
||||
@@ -36,11 +36,17 @@ func (v *LocalValue) Type() llvm.Type {
|
||||
}
|
||||
|
||||
func (v *LocalValue) IsConstant() bool {
|
||||
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
|
||||
return false
|
||||
}
|
||||
return v.Underlying.IsConstant()
|
||||
}
|
||||
|
||||
// Load loads a constant value if this is a constant GEP, otherwise it panics.
|
||||
// Load loads a constant value if this is a constant pointer.
|
||||
func (v *LocalValue) Load() llvm.Value {
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
return v.Underlying.Initializer()
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr:
|
||||
indices := v.getConstGEPIndices()
|
||||
@@ -50,21 +56,32 @@ func (v *LocalValue) Load() llvm.Value {
|
||||
global := v.Eval.getValue(v.Underlying.Operand(0))
|
||||
agg := global.Load()
|
||||
return llvm.ConstExtractValue(agg, indices[1:])
|
||||
case llvm.BitCast:
|
||||
panic("interp: load from a bitcast")
|
||||
default:
|
||||
panic("interp: load from a constant")
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores to the underlying value if the value type is a constant GEP,
|
||||
// Store stores to the underlying value if the value type is a pointer type,
|
||||
// otherwise it panics.
|
||||
func (v *LocalValue) Store(value llvm.Value) {
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
if !value.IsConstant() {
|
||||
v.MarkDirty()
|
||||
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||
} else {
|
||||
v.Underlying.SetInitializer(value)
|
||||
}
|
||||
return
|
||||
}
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr:
|
||||
indices := v.getConstGEPIndices()
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
global := &GlobalValue{v.Eval, v.Underlying.Operand(0)}
|
||||
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
|
||||
agg := global.Load()
|
||||
agg = llvm.ConstInsertValue(agg, value, indices[1:])
|
||||
global.Store(agg)
|
||||
@@ -74,17 +91,40 @@ func (v *LocalValue) Store(value llvm.Value) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a constant GEP when the underlying value is also a
|
||||
// constant GEP. It panics when the underlying value is not a constant GEP:
|
||||
// getting the pointer to a constant is not possible.
|
||||
// GetElementPtr returns a GEP when the underlying value is of pointer type.
|
||||
func (v *LocalValue) GetElementPtr(indices []uint32) Value {
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr:
|
||||
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
default:
|
||||
panic("interp: GEP on a constant")
|
||||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
if !v.Underlying.IsAConstantExpr().IsNil() {
|
||||
switch v.Underlying.Opcode() {
|
||||
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}
|
||||
}
|
||||
}
|
||||
panic("interp: unknown GEP")
|
||||
}
|
||||
|
||||
// stripPointerCasts removes all const bitcasts from pointer values, if there
|
||||
// are any.
|
||||
func (v *LocalValue) stripPointerCasts() *LocalValue {
|
||||
value := v.Underlying
|
||||
for {
|
||||
if !value.IsAConstantExpr().IsNil() {
|
||||
switch value.Opcode() {
|
||||
case llvm.BitCast:
|
||||
value = value.Operand(0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return &LocalValue{
|
||||
Eval: v.Eval,
|
||||
Underlying: value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,283 +147,18 @@ func (v *LocalValue) getConstGEPIndices() []uint32 {
|
||||
return indices
|
||||
}
|
||||
|
||||
// GlobalValue wraps a LLVM global variable.
|
||||
type GlobalValue struct {
|
||||
Eval *Eval
|
||||
Underlying llvm.Value
|
||||
}
|
||||
|
||||
// Value returns the initializer for this global variable.
|
||||
func (v *GlobalValue) Value() llvm.Value {
|
||||
return v.Underlying
|
||||
}
|
||||
|
||||
// Type returns the type of this global variable, which is a pointer type. Use
|
||||
// Type().ElementType() to get the actual global variable type.
|
||||
func (v *GlobalValue) Type() llvm.Type {
|
||||
return v.Underlying.Type()
|
||||
}
|
||||
|
||||
// IsConstant returns true if this global is not dirty, false otherwise.
|
||||
func (v *GlobalValue) IsConstant() bool {
|
||||
if _, ok := v.Eval.dirtyGlobals[v.Underlying]; ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Load returns the initializer of the global variable.
|
||||
func (v *GlobalValue) Load() llvm.Value {
|
||||
return v.Underlying.Initializer()
|
||||
}
|
||||
|
||||
// Store sets the initializer of the global variable.
|
||||
func (v *GlobalValue) Store(value llvm.Value) {
|
||||
if !value.IsConstant() {
|
||||
v.MarkDirty()
|
||||
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||
} else {
|
||||
v.Underlying.SetInitializer(value)
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a constant GEP on this global, which can be used in
|
||||
// load and store instructions.
|
||||
func (v *GlobalValue) GetElementPtr(indices []uint32) Value {
|
||||
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||
return &LocalValue{v.Eval, gep}
|
||||
}
|
||||
|
||||
func (v *GlobalValue) String() string {
|
||||
return "&GlobalValue{" + v.Underlying.Name() + "}"
|
||||
}
|
||||
|
||||
// MarkDirty marks this global as dirty, meaning that every load from and store
|
||||
// to this global (from now on) must be performed at runtime.
|
||||
func (v *GlobalValue) MarkDirty() {
|
||||
func (v *LocalValue) MarkDirty() {
|
||||
if v.Underlying.IsAGlobalVariable().IsNil() {
|
||||
panic("trying to mark a non-global as dirty")
|
||||
}
|
||||
if !v.IsConstant() {
|
||||
return // already dirty
|
||||
}
|
||||
v.Eval.dirtyGlobals[v.Underlying] = struct{}{}
|
||||
}
|
||||
|
||||
// An alloca represents a local alloca, which is a stack allocated variable.
|
||||
// It is emulated by storing the constant of the alloca.
|
||||
type AllocaValue struct {
|
||||
Eval *Eval
|
||||
Underlying llvm.Value // the constant value itself if not dirty, otherwise the alloca instruction
|
||||
Dirty bool // this value must be evaluated at runtime
|
||||
}
|
||||
|
||||
// Value turns this alloca into a runtime alloca instead of a compile-time
|
||||
// constant (if not already converted), and returns the alloca itself.
|
||||
func (v *AllocaValue) Value() llvm.Value {
|
||||
if !v.Dirty {
|
||||
// Mark this alloca a dirty, meaning it is run at runtime instead of
|
||||
// compile time.
|
||||
alloca := v.Eval.builder.CreateAlloca(v.Underlying.Type(), "")
|
||||
v.Eval.builder.CreateStore(v.Underlying, alloca)
|
||||
v.Dirty = true
|
||||
v.Underlying = alloca
|
||||
}
|
||||
return v.Underlying
|
||||
}
|
||||
|
||||
// Type returns the type of this alloca, which is always a pointer.
|
||||
func (v *AllocaValue) Type() llvm.Type {
|
||||
if v.Dirty {
|
||||
return v.Underlying.Type()
|
||||
} else {
|
||||
return llvm.PointerType(v.Underlying.Type(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *AllocaValue) IsConstant() bool {
|
||||
return !v.Dirty
|
||||
}
|
||||
|
||||
// Load returns the value this alloca contains, which may be evaluated at
|
||||
// runtime.
|
||||
func (v *AllocaValue) Load() llvm.Value {
|
||||
if v.Dirty {
|
||||
ret := v.Eval.builder.CreateLoad(v.Underlying, "")
|
||||
if ret.IsNil() {
|
||||
panic("alloca is nil")
|
||||
}
|
||||
return ret
|
||||
} else {
|
||||
if v.Underlying.IsNil() {
|
||||
panic("alloca is nil")
|
||||
}
|
||||
return v.Underlying
|
||||
}
|
||||
}
|
||||
|
||||
// Store updates the value of this alloca.
|
||||
func (v *AllocaValue) Store(value llvm.Value) {
|
||||
if v.Underlying.Type() != value.Type() {
|
||||
panic("interp: trying to store to an alloca with a different type")
|
||||
}
|
||||
if v.Dirty || !value.IsConstant() {
|
||||
v.Eval.builder.CreateStore(value, v.Value())
|
||||
} else {
|
||||
v.Underlying = value
|
||||
}
|
||||
}
|
||||
|
||||
// GetElementPtr returns a value (a *GetElementPtrValue) that keeps a reference
|
||||
// to this alloca, so that Load() and Store() continue to work.
|
||||
func (v *AllocaValue) GetElementPtr(indices []uint32) Value {
|
||||
return &GetElementPtrValue{v, indices}
|
||||
}
|
||||
|
||||
func (v *AllocaValue) String() string {
|
||||
return "&AllocaValue{Type: " + v.Type().String() + "}"
|
||||
}
|
||||
|
||||
// GetElementPtrValue wraps an alloca, keeping track of what the GEP points to
|
||||
// so it can be used as a pointer value (with Load() and Store()).
|
||||
type GetElementPtrValue struct {
|
||||
Alloca *AllocaValue
|
||||
Indices []uint32
|
||||
}
|
||||
|
||||
// Type returns the type of this GEP, which is always of type pointer.
|
||||
func (v *GetElementPtrValue) Type() llvm.Type {
|
||||
if v.Alloca.Dirty {
|
||||
return v.Value().Type()
|
||||
} else {
|
||||
return llvm.PointerType(v.Load().Type(), 0)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) IsConstant() bool {
|
||||
return v.Alloca.IsConstant()
|
||||
}
|
||||
|
||||
// Value creates the LLVM GEP instruction of this GetElementPtrValue wrapper and
|
||||
// returns it.
|
||||
func (v *GetElementPtrValue) Value() llvm.Value {
|
||||
if v.Alloca.Dirty {
|
||||
alloca := v.Alloca.Value()
|
||||
int32Type := v.Alloca.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, v.Indices)
|
||||
return v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
|
||||
} else {
|
||||
panic("interp: todo: pointer to alloca gep")
|
||||
}
|
||||
}
|
||||
|
||||
// Load deferences the pointer this GEP points to. For a constant GEP, it
|
||||
// extracts the value from the underlying alloca.
|
||||
func (v *GetElementPtrValue) Load() llvm.Value {
|
||||
if v.Alloca.Dirty {
|
||||
gep := v.Value()
|
||||
return v.Alloca.Eval.builder.CreateLoad(gep, "")
|
||||
} else {
|
||||
underlying := v.Alloca.Load()
|
||||
indices := v.Indices
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
return llvm.ConstExtractValue(underlying, indices[1:])
|
||||
}
|
||||
}
|
||||
|
||||
// Store stores to the pointer this GEP points to. For a constant GEP, it
|
||||
// updates the underlying allloca.
|
||||
func (v *GetElementPtrValue) Store(value llvm.Value) {
|
||||
if v.Alloca.Dirty || !value.IsConstant() {
|
||||
alloca := v.Alloca.Value()
|
||||
int32Type := v.Alloca.Type().Context().Int32Type()
|
||||
llvmIndices := getLLVMIndices(int32Type, v.Indices)
|
||||
gep := v.Alloca.Eval.builder.CreateGEP(alloca, llvmIndices, "")
|
||||
v.Alloca.Eval.builder.CreateStore(value, gep)
|
||||
} else {
|
||||
underlying := v.Alloca.Load()
|
||||
indices := v.Indices
|
||||
if indices[0] != 0 {
|
||||
panic("invalid GEP")
|
||||
}
|
||||
underlying = llvm.ConstInsertValue(underlying, value, indices[1:])
|
||||
v.Alloca.Store(underlying)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) GetElementPtr(indices []uint32) Value {
|
||||
if v.Alloca.Dirty {
|
||||
panic("interp: todo: gep on a dirty gep")
|
||||
} else {
|
||||
combined := append([]uint32{}, v.Indices...)
|
||||
combined[len(combined)-1] += indices[0]
|
||||
combined = append(combined, indices[1:]...)
|
||||
return &GetElementPtrValue{v.Alloca, combined}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *GetElementPtrValue) String() string {
|
||||
indices := ""
|
||||
for _, n := range v.Indices {
|
||||
if indices != "" {
|
||||
indices += ", "
|
||||
}
|
||||
indices += strconv.Itoa(int(n))
|
||||
}
|
||||
return "&GetElementPtrValue{Alloca: " + v.Alloca.String() + ", Indices: [" + indices + "]}"
|
||||
}
|
||||
|
||||
// PointerCastValue represents a bitcast operation on a pointer.
|
||||
type PointerCastValue struct {
|
||||
Eval *Eval
|
||||
Underlying Value
|
||||
CastType llvm.Type
|
||||
}
|
||||
|
||||
// Value returns a constant bitcast value.
|
||||
func (v *PointerCastValue) Value() llvm.Value {
|
||||
from := v.Underlying.Value()
|
||||
return llvm.ConstBitCast(from, v.CastType)
|
||||
}
|
||||
|
||||
// Type returns the type this pointer has been cast to.
|
||||
func (v *PointerCastValue) Type() llvm.Type {
|
||||
return v.CastType
|
||||
}
|
||||
|
||||
func (v *PointerCastValue) IsConstant() bool {
|
||||
return v.Underlying.IsConstant()
|
||||
}
|
||||
|
||||
// Load tries to load and bitcast the given value. If this value cannot be
|
||||
// bitcasted, Load panics.
|
||||
func (v *PointerCastValue) Load() llvm.Value {
|
||||
if v.Underlying.IsConstant() {
|
||||
typeFrom := v.Underlying.Type().ElementType()
|
||||
typeTo := v.CastType.ElementType()
|
||||
if isScalar(typeFrom) && isScalar(typeTo) && v.Eval.TargetData.TypeAllocSize(typeFrom) == v.Eval.TargetData.TypeAllocSize(typeTo) {
|
||||
return llvm.ConstBitCast(v.Underlying.Load(), v.CastType.ElementType())
|
||||
}
|
||||
}
|
||||
|
||||
panic("interp: load from a pointer bitcast: " + v.String())
|
||||
}
|
||||
|
||||
// Store panics: it is not (yet) possible to store directly to a bitcast.
|
||||
func (v *PointerCastValue) Store(value llvm.Value) {
|
||||
panic("interp: store on a pointer bitcast")
|
||||
}
|
||||
|
||||
// GetElementPtr panics: it is not (yet) possible to do a GEP operation on a
|
||||
// bitcast.
|
||||
func (v *PointerCastValue) GetElementPtr(indices []uint32) Value {
|
||||
panic("interp: GEP on a pointer bitcast")
|
||||
}
|
||||
|
||||
func (v *PointerCastValue) String() string {
|
||||
return "&PointerCastValue{Value: " + v.Underlying.String() + ", CastType: " + v.CastType.String() + "}"
|
||||
}
|
||||
|
||||
// MapValue implements a Go map which is created at compile time and stored as a
|
||||
// global variable.
|
||||
type MapValue struct {
|
||||
@@ -407,7 +182,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)
|
||||
@@ -451,6 +226,13 @@ func (v *MapValue) Value() llvm.Value {
|
||||
keyBuf[i] = byte(n)
|
||||
n >>= 8
|
||||
}
|
||||
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
|
||||
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
|
||||
key.Type().ElementType().IntTypeWidth() == 8 {
|
||||
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
|
||||
for i := range keyBuf {
|
||||
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
|
||||
}
|
||||
} else {
|
||||
panic("interp: map key type not implemented: " + key.Type().String())
|
||||
}
|
||||
@@ -527,32 +309,29 @@ func (v *MapValue) GetElementPtr(indices []uint32) Value {
|
||||
|
||||
// PutString does a map assign operation, assuming that the map is of type
|
||||
// map[string]T.
|
||||
func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
|
||||
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
||||
if !v.Underlying.IsNil() {
|
||||
panic("map already created")
|
||||
}
|
||||
|
||||
var value llvm.Value
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *PointerCastValue:
|
||||
value = valPtr.Underlying.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||
}
|
||||
value := valPtr.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
default:
|
||||
panic("interp: todo: handle map value pointer")
|
||||
}
|
||||
|
||||
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})
|
||||
|
||||
@@ -561,6 +340,49 @@ func (v *MapValue) PutString(keyBuf, keyLen, valPtr Value) {
|
||||
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||
}
|
||||
|
||||
// PutBinary does a map assign operation.
|
||||
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
|
||||
if !v.Underlying.IsNil() {
|
||||
panic("map already created")
|
||||
}
|
||||
|
||||
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||
}
|
||||
value := valPtr.Load()
|
||||
if v.ValueType.IsNil() {
|
||||
v.ValueType = value.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||
panic("interp: map store value type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if value.Type() != v.ValueType {
|
||||
panic("interp: map store value type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
if keyPtr.Underlying.Opcode() == llvm.BitCast {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
|
||||
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||
}
|
||||
key := keyPtr.Load()
|
||||
if v.KeyType.IsNil() {
|
||||
v.KeyType = key.Type()
|
||||
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
|
||||
panic("interp: map store key type has the wrong size")
|
||||
}
|
||||
} else {
|
||||
if key.Type() != v.KeyType {
|
||||
panic("interp: map store key type is inconsistent")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: avoid duplicate keys
|
||||
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
|
||||
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||
}
|
||||
|
||||
// Get FNV-1a hash of this string.
|
||||
//
|
||||
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
|
||||
|
||||
@@ -1,522 +0,0 @@
|
||||
package ir
|
||||
|
||||
// This file provides functionality to interpret very basic Go SSA, for
|
||||
// compile-time initialization of globals.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
var ErrCGoWrapper = errors.New("tinygo internal: cgo wrapper") // a signal, not an error
|
||||
|
||||
// Ignore these calls (replace with a zero return value) when encountered during
|
||||
// interpretation.
|
||||
var ignoreInitCalls = map[string]struct{}{
|
||||
"syscall.runtime_envs": struct{}{},
|
||||
"syscall/js.predefValue": struct{}{},
|
||||
"(syscall/js.Value).Get": struct{}{},
|
||||
"(syscall/js.Value).New": struct{}{},
|
||||
"(syscall/js.Value).Int": struct{}{},
|
||||
"os.init$1": struct{}{},
|
||||
}
|
||||
|
||||
// Interpret instructions as far as possible, and drop those instructions from
|
||||
// the basic block.
|
||||
func (p *Program) Interpret(block *ssa.BasicBlock, dumpSSA bool) error {
|
||||
if dumpSSA {
|
||||
fmt.Printf("\ninterpret: %s\n", block.Parent().Pkg.Pkg.Path())
|
||||
}
|
||||
for {
|
||||
i, err := p.interpret(block.Instrs, nil, nil, nil, dumpSSA)
|
||||
if err == ErrCGoWrapper {
|
||||
// skip this instruction
|
||||
block.Instrs = block.Instrs[i+1:]
|
||||
continue
|
||||
}
|
||||
block.Instrs = block.Instrs[i:]
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Interpret instructions as far as possible, and return the index of the first
|
||||
// unknown instruction.
|
||||
func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter, paramValues []Value, results []Value, dumpSSA bool) (int, error) {
|
||||
locals := map[ssa.Value]Value{}
|
||||
for i, key := range paramKeys {
|
||||
locals[key] = paramValues[i]
|
||||
}
|
||||
for i, instr := range instrs {
|
||||
if _, ok := instr.(*ssa.DebugRef); ok {
|
||||
continue
|
||||
}
|
||||
if dumpSSA {
|
||||
if val, ok := instr.(ssa.Value); ok && val.Name() != "" {
|
||||
fmt.Printf("\t%s: %s = %s\n", instr.Parent().RelString(nil), val.Name(), val.String())
|
||||
} else {
|
||||
fmt.Printf("\t%s: %s\n", instr.Parent().RelString(nil), instr.String())
|
||||
}
|
||||
}
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Alloc:
|
||||
alloc, err := p.getZeroValue(instr.Type().Underlying().(*types.Pointer).Elem())
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
locals[instr] = &PointerValue{nil, &alloc}
|
||||
case *ssa.BinOp:
|
||||
if typ, ok := instr.Type().(*types.Basic); ok && typ.Kind() == types.String {
|
||||
// Concatenate two strings.
|
||||
// This happens in the time package, for example.
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
y, err := p.getValue(instr.Y, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
xstr := constant.StringVal(x.(*ConstValue).Expr.Value)
|
||||
ystr := constant.StringVal(y.(*ConstValue).Expr.Value)
|
||||
locals[instr] = &ConstValue{ssa.NewConst(constant.MakeString(xstr+ystr), types.Typ[types.String])}
|
||||
} else {
|
||||
return i, errors.New("init: unknown binop: " + instr.String())
|
||||
}
|
||||
case *ssa.Call:
|
||||
common := instr.Common()
|
||||
callee := common.StaticCallee()
|
||||
if callee == nil {
|
||||
return i, nil // don't understand dynamic dispatch
|
||||
}
|
||||
if _, ok := ignoreInitCalls[callee.String()]; ok {
|
||||
// These calls are not needed and can be ignored, for the time
|
||||
// being.
|
||||
results := make([]Value, callee.Signature.Results().Len())
|
||||
for i := range results {
|
||||
var err error
|
||||
results[i], err = p.getZeroValue(callee.Signature.Results().At(i).Type())
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
if len(results) == 1 {
|
||||
locals[instr] = results[0]
|
||||
} else if len(results) > 1 {
|
||||
locals[instr] = &StructValue{Fields: results}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if callee.String() == "os.NewFile" {
|
||||
// Emulate the creation of os.Stdin, os.Stdout and os.Stderr.
|
||||
resultPtrType := callee.Signature.Results().At(0).Type().(*types.Pointer)
|
||||
resultStructOuterType := resultPtrType.Elem().Underlying().(*types.Struct)
|
||||
if resultStructOuterType.NumFields() != 1 {
|
||||
panic("expected 1 field in os.File struct")
|
||||
}
|
||||
fileInnerPtrType := resultStructOuterType.Field(0).Type().(*types.Pointer)
|
||||
fileInnerType := fileInnerPtrType.Elem().(*types.Named)
|
||||
fileInnerStructType := fileInnerType.Underlying().(*types.Struct)
|
||||
fileInner, err := p.getZeroValue(fileInnerType) // os.file
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
for fieldIndex := 0; fieldIndex < fileInnerStructType.NumFields(); fieldIndex++ {
|
||||
field := fileInnerStructType.Field(fieldIndex)
|
||||
if field.Name() == "name" {
|
||||
// Set the 'name' field.
|
||||
name, err := p.getValue(common.Args[1], locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
fileInner.(*StructValue).Fields[fieldIndex] = name
|
||||
} else if field.Type().String() == "internal/poll.FD" {
|
||||
// Set the file descriptor field.
|
||||
field := field.Type().Underlying().(*types.Struct)
|
||||
for subfieldIndex := 0; subfieldIndex < field.NumFields(); subfieldIndex++ {
|
||||
subfield := field.Field(subfieldIndex)
|
||||
if subfield.Name() == "Sysfd" {
|
||||
sysfd, err := p.getValue(common.Args[0], locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
sysfd = &ConstValue{Expr: ssa.NewConst(sysfd.(*ConstValue).Expr.Value, subfield.Type())}
|
||||
fileInner.(*StructValue).Fields[fieldIndex].(*StructValue).Fields[subfieldIndex] = sysfd
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fileInnerPtr := &PointerValue{fileInnerPtrType, &fileInner} // *os.file
|
||||
var fileOuter Value = &StructValue{Type: resultPtrType.Elem(), Fields: []Value{fileInnerPtr}} // os.File
|
||||
result := &PointerValue{resultPtrType.Elem(), &fileOuter} // *os.File
|
||||
locals[instr] = result
|
||||
continue
|
||||
}
|
||||
if canInterpret(callee) {
|
||||
params := make([]Value, len(common.Args))
|
||||
for i, arg := range common.Args {
|
||||
val, err := p.getValue(arg, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
params[i] = val
|
||||
}
|
||||
results := make([]Value, callee.Signature.Results().Len())
|
||||
subi, err := p.interpret(callee.Blocks[0].Instrs, callee.Params, params, results, dumpSSA)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if subi != len(callee.Blocks[0].Instrs) {
|
||||
return i, errors.New("init: could not interpret all instructions of subroutine")
|
||||
}
|
||||
if len(results) == 1 {
|
||||
locals[instr] = results[0]
|
||||
} else {
|
||||
panic("unimplemented: not exactly 1 result")
|
||||
}
|
||||
continue
|
||||
}
|
||||
if callee.Object() == nil || callee.Object().Name() == "init" {
|
||||
return i, nil // arrived at the init#num functions
|
||||
}
|
||||
return i, errors.New("todo: init call: " + callee.String())
|
||||
case *ssa.ChangeType:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
// The only case when we need to bitcast is when casting between named
|
||||
// struct types, as those are actually different in LLVM. Let's just
|
||||
// bitcast all struct types for ease of use.
|
||||
if _, ok := instr.Type().Underlying().(*types.Struct); ok {
|
||||
return i, errors.New("todo: init: " + instr.String())
|
||||
}
|
||||
locals[instr] = x
|
||||
case *ssa.Convert:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
typeFrom := instr.X.Type().Underlying()
|
||||
switch typeTo := instr.Type().Underlying().(type) {
|
||||
case *types.Basic:
|
||||
if typeTo.Kind() == types.String {
|
||||
return i, nil
|
||||
}
|
||||
|
||||
if _, ok := typeFrom.(*types.Pointer); ok && typeTo.Kind() == types.UnsafePointer {
|
||||
locals[instr] = &PointerBitCastValue{typeTo, x}
|
||||
} else if typeFrom, ok := typeFrom.(*types.Basic); ok {
|
||||
if typeFrom.Kind() == types.UnsafePointer && typeTo.Kind() == types.Uintptr {
|
||||
locals[instr] = &PointerToUintptrValue{x}
|
||||
} else if typeFrom.Info()&types.IsInteger != 0 && typeTo.Info()&types.IsInteger != 0 {
|
||||
locals[instr] = &ConstValue{Expr: ssa.NewConst(x.(*ConstValue).Expr.Value, typeTo)}
|
||||
} else {
|
||||
return i, nil
|
||||
}
|
||||
} else {
|
||||
return i, nil
|
||||
}
|
||||
case *types.Pointer:
|
||||
if typeFrom, ok := typeFrom.(*types.Basic); ok && typeFrom.Kind() == types.UnsafePointer {
|
||||
locals[instr] = &PointerBitCastValue{typeTo, x}
|
||||
} else {
|
||||
panic("expected unsafe pointer conversion")
|
||||
}
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.Extract:
|
||||
tuple, err := p.getValue(instr.Tuple, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
locals[instr] = tuple.(*StructValue).Fields[instr.Index]
|
||||
case *ssa.FieldAddr:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
var structVal *StructValue
|
||||
switch x := x.(type) {
|
||||
case *GlobalValue:
|
||||
structVal = x.Global.initializer.(*StructValue)
|
||||
case *PointerValue:
|
||||
structVal = (*x.Elem).(*StructValue)
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
locals[instr] = &PointerValue{nil, &structVal.Fields[instr.Field]}
|
||||
case *ssa.IndexAddr:
|
||||
x, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if cnst, ok := instr.Index.(*ssa.Const); ok {
|
||||
index, _ := constant.Int64Val(cnst.Value)
|
||||
switch xPtr := x.(type) {
|
||||
case *GlobalValue:
|
||||
x = xPtr.Global.initializer
|
||||
case *PointerValue:
|
||||
x = *xPtr.Elem
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
switch x := x.(type) {
|
||||
case *ArrayValue:
|
||||
locals[instr] = &PointerValue{nil, &x.Elems[index]}
|
||||
default:
|
||||
return i, errors.New("todo: init IndexAddr not on an array or struct")
|
||||
}
|
||||
} else {
|
||||
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
|
||||
}
|
||||
case *ssa.MakeMap:
|
||||
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
|
||||
case *ssa.MapUpdate:
|
||||
// Assume no duplicate keys exist. This is most likely true for
|
||||
// autogenerated code, but may not be true when trying to interpret
|
||||
// user code.
|
||||
key, err := p.getValue(instr.Key, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
value, err := p.getValue(instr.Value, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
x := locals[instr.Map].(*MapValue)
|
||||
x.Keys = append(x.Keys, key)
|
||||
x.Values = append(x.Values, value)
|
||||
case *ssa.Return:
|
||||
for i, r := range instr.Results {
|
||||
val, err := p.getValue(r, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
results[i] = val
|
||||
}
|
||||
case *ssa.Slice:
|
||||
// Turn a just-allocated array into a slice.
|
||||
if instr.Low != nil || instr.High != nil || instr.Max != nil {
|
||||
return i, errors.New("init: slice expression with bounds")
|
||||
}
|
||||
source, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
switch source := source.(type) {
|
||||
case *PointerValue: // pointer to array
|
||||
array := (*source.Elem).(*ArrayValue)
|
||||
locals[instr] = &SliceValue{instr.Type().Underlying().(*types.Slice), array}
|
||||
default:
|
||||
return i, errors.New("init: unknown slice type")
|
||||
}
|
||||
case *ssa.Store:
|
||||
if addr, ok := instr.Addr.(*ssa.Global); ok {
|
||||
if strings.HasPrefix(instr.Addr.Name(), "__cgofn__cgo_") || strings.HasPrefix(instr.Addr.Name(), "_cgo_") {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
continue
|
||||
}
|
||||
value, err := p.getValue(instr.Val, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
p.GetGlobal(addr).initializer = value
|
||||
} else if addr, ok := locals[instr.Addr]; ok {
|
||||
value, err := p.getValue(instr.Val, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
if addr, ok := addr.(*PointerValue); ok {
|
||||
*(addr.Elem) = value
|
||||
} else {
|
||||
panic("store to non-pointer")
|
||||
}
|
||||
} else {
|
||||
return i, errors.New("todo: init Store: " + instr.String())
|
||||
}
|
||||
case *ssa.UnOp:
|
||||
if instr.Op != token.MUL || instr.CommaOk {
|
||||
return i, errors.New("init: unknown unop: " + instr.String())
|
||||
}
|
||||
valPtr, err := p.getValue(instr.X, locals)
|
||||
if err != nil {
|
||||
return i, err
|
||||
}
|
||||
switch valPtr := valPtr.(type) {
|
||||
case *GlobalValue:
|
||||
locals[instr] = valPtr.Global.initializer
|
||||
case *PointerValue:
|
||||
locals[instr] = *valPtr.Elem
|
||||
default:
|
||||
panic("expected a pointer")
|
||||
}
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return len(instrs), nil
|
||||
}
|
||||
|
||||
// Check whether this function can be interpreted at compile time. For that, it
|
||||
// needs to only contain relatively simple instructions (for example, no control
|
||||
// flow).
|
||||
func canInterpret(callee *ssa.Function) bool {
|
||||
if len(callee.Blocks) != 1 || callee.Signature.Results().Len() != 1 {
|
||||
// No control flow supported so only one basic block.
|
||||
// Only exactly one return value supported right now so check that as
|
||||
// well.
|
||||
return false
|
||||
}
|
||||
for _, instr := range callee.Blocks[0].Instrs {
|
||||
switch instr.(type) {
|
||||
// Ignore all functions fully supported by Program.interpret()
|
||||
// above.
|
||||
case *ssa.Alloc:
|
||||
case *ssa.ChangeType:
|
||||
case *ssa.DebugRef:
|
||||
case *ssa.Extract:
|
||||
case *ssa.FieldAddr:
|
||||
case *ssa.IndexAddr:
|
||||
case *ssa.MakeMap:
|
||||
case *ssa.MapUpdate:
|
||||
case *ssa.Return:
|
||||
case *ssa.Slice:
|
||||
case *ssa.Store:
|
||||
case *ssa.UnOp:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Program) getValue(value ssa.Value, locals map[ssa.Value]Value) (Value, error) {
|
||||
switch value := value.(type) {
|
||||
case *ssa.Const:
|
||||
return &ConstValue{value}, nil
|
||||
case *ssa.Function:
|
||||
return &FunctionValue{value.Type(), value}, nil
|
||||
case *ssa.Global:
|
||||
if strings.HasPrefix(value.Name(), "__cgofn__cgo_") || strings.HasPrefix(value.Name(), "_cgo_") {
|
||||
// Ignore CGo global variables which we don't use.
|
||||
return nil, ErrCGoWrapper
|
||||
}
|
||||
g := p.GetGlobal(value)
|
||||
if g.initializer == nil {
|
||||
value, err := p.getZeroValue(value.Type().Underlying().(*types.Pointer).Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.initializer = value
|
||||
}
|
||||
return &GlobalValue{g}, nil
|
||||
default:
|
||||
if local, ok := locals[value]; ok {
|
||||
return local, nil
|
||||
} else {
|
||||
return nil, errors.New("todo: init: unknown value: " + value.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Program) getZeroValue(t types.Type) (Value, error) {
|
||||
switch typ := t.Underlying().(type) {
|
||||
case *types.Array:
|
||||
elems := make([]Value, typ.Len())
|
||||
for i := range elems {
|
||||
elem, err := p.getZeroValue(typ.Elem())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = elem
|
||||
}
|
||||
return &ArrayValue{typ.Elem(), elems}, nil
|
||||
case *types.Basic:
|
||||
return &ZeroBasicValue{typ}, nil
|
||||
case *types.Signature:
|
||||
return &FunctionValue{typ, nil}, nil
|
||||
case *types.Map:
|
||||
return &MapValue{typ, nil, nil}, nil
|
||||
case *types.Pointer:
|
||||
return &PointerValue{typ, nil}, nil
|
||||
case *types.Struct:
|
||||
elems := make([]Value, typ.NumFields())
|
||||
for i := range elems {
|
||||
elem, err := p.getZeroValue(typ.Field(i).Type())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
elems[i] = elem
|
||||
}
|
||||
return &StructValue{t, elems}, nil
|
||||
case *types.Slice:
|
||||
return &SliceValue{typ, nil}, nil
|
||||
default:
|
||||
return nil, errors.New("todo: init: unknown global type: " + typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Boxed value for interpreter.
|
||||
type Value interface {
|
||||
}
|
||||
|
||||
type ConstValue struct {
|
||||
Expr *ssa.Const
|
||||
}
|
||||
|
||||
type ZeroBasicValue struct {
|
||||
Type *types.Basic
|
||||
}
|
||||
|
||||
type PointerValue struct {
|
||||
Type types.Type
|
||||
Elem *Value
|
||||
}
|
||||
|
||||
type FunctionValue struct {
|
||||
Type types.Type
|
||||
Elem *ssa.Function
|
||||
}
|
||||
|
||||
type PointerBitCastValue struct {
|
||||
Type types.Type
|
||||
Elem Value
|
||||
}
|
||||
|
||||
type PointerToUintptrValue struct {
|
||||
Elem Value
|
||||
}
|
||||
|
||||
type GlobalValue struct {
|
||||
Global *Global
|
||||
}
|
||||
|
||||
type ArrayValue struct {
|
||||
ElemType types.Type
|
||||
Elems []Value
|
||||
}
|
||||
|
||||
type StructValue struct {
|
||||
Type types.Type // types.Struct or types.Named
|
||||
Fields []Value
|
||||
}
|
||||
|
||||
type SliceValue struct {
|
||||
Type *types.Slice
|
||||
Array *ArrayValue
|
||||
}
|
||||
|
||||
type MapValue struct {
|
||||
Type *types.Map
|
||||
Keys []Value
|
||||
Values []Value
|
||||
}
|
||||
@@ -2,14 +2,13 @@ package ir
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-llvm"
|
||||
"github.com/aykevl/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This file provides a wrapper around go/ssa values and adds extra
|
||||
@@ -18,54 +17,24 @@ import (
|
||||
// View on all functions, types, and globals in a program, with analysis
|
||||
// results.
|
||||
type Program struct {
|
||||
Program *ssa.Program
|
||||
LoaderProgram *loader.Program
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
Globals []*Global
|
||||
globalMap map[*ssa.Global]*Global
|
||||
comments map[string]*ast.CommentGroup
|
||||
NamedTypes []*NamedType
|
||||
needsScheduler bool
|
||||
goCalls []*ssa.Go
|
||||
Program *ssa.Program
|
||||
LoaderProgram *loader.Program
|
||||
mainPkg *ssa.Package
|
||||
Functions []*Function
|
||||
functionMap map[*ssa.Function]*Function
|
||||
}
|
||||
|
||||
// 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
|
||||
blocking bool // calculated by AnalyseBlockingRecursive
|
||||
flag bool // used by dead code elimination
|
||||
interrupt bool // go:interrupt
|
||||
parents []*Function // calculated by AnalyseCallgraph
|
||||
children []*Function // calculated by AnalyseCallgraph
|
||||
}
|
||||
|
||||
// Global variable, possibly constant.
|
||||
type Global struct {
|
||||
*ssa.Global
|
||||
program *Program
|
||||
LLVMGlobal llvm.Value
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
initializer Value
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -75,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()
|
||||
|
||||
@@ -177,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 {
|
||||
@@ -193,9 +150,6 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
|
||||
func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
memberNames := make([]string, 0)
|
||||
for name := range pkg.Members {
|
||||
if isCGoInternal(name) {
|
||||
continue
|
||||
}
|
||||
memberNames = append(memberNames, name)
|
||||
}
|
||||
sort.Strings(memberNames)
|
||||
@@ -204,13 +158,8 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
member := pkg.Members[name]
|
||||
switch member := member.(type) {
|
||||
case *ssa.Function:
|
||||
if isCGoInternal(member.Name()) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
@@ -219,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:
|
||||
@@ -235,6 +178,9 @@ func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||
}
|
||||
|
||||
func (p *Program) addFunction(ssaFn *ssa.Function) {
|
||||
if _, ok := p.functionMap[ssaFn]; ok {
|
||||
return
|
||||
}
|
||||
f := &Function{Function: ssaFn}
|
||||
f.parsePragmas()
|
||||
p.Functions = append(p.Functions, f)
|
||||
@@ -259,10 +205,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
|
||||
}
|
||||
@@ -274,10 +216,16 @@ func (f *Function) parsePragmas() {
|
||||
}
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
for _, comment := range decl.Doc.List {
|
||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(comment.Text)
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
@@ -285,6 +233,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
|
||||
@@ -327,7 +285,7 @@ func (f *Function) IsNoBounds() bool {
|
||||
|
||||
// Return true iff this function is externally visible.
|
||||
func (f *Function) IsExported() bool {
|
||||
return f.exported
|
||||
return f.exported || f.CName() != ""
|
||||
}
|
||||
|
||||
// Return true for functions annotated with //go:interrupt. The function name is
|
||||
@@ -335,7 +293,17 @@ func (f *Function) IsExported() bool {
|
||||
//
|
||||
// On some platforms (like AVR), interrupts need a special compiler flag.
|
||||
func (f *Function) IsInterrupt() bool {
|
||||
return f.exported
|
||||
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.
|
||||
@@ -372,74 +340,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
|
||||
}
|
||||
return g.RelString(nil)
|
||||
}
|
||||
|
||||
func (g *Global) IsExtern() bool {
|
||||
return g.extern
|
||||
}
|
||||
|
||||
func (g *Global) Initializer() Value {
|
||||
return g.initializer
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Return true if this is a CGo-internal function that can be ignored.
|
||||
func isCGoInternal(name string) bool {
|
||||
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {
|
||||
// _Cgo_ptr, _Cgo_use, _cgoCheckResult, _cgo_runtime_cgocall
|
||||
return true // CGo-internal functions
|
||||
}
|
||||
if strings.HasPrefix(name, "__cgofn__cgo_") {
|
||||
return true // CGo function pointer in global scope
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
|
||||
+4
-125
@@ -56,110 +56,6 @@ func signature(sig *types.Signature) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// Fill in parents of all functions.
|
||||
//
|
||||
// All packages need to be added before this pass can run, or it will produce
|
||||
// incorrect results.
|
||||
func (p *Program) AnalyseCallgraph() {
|
||||
for _, f := range p.Functions {
|
||||
// Clear, if AnalyseCallgraph has been called before.
|
||||
f.children = nil
|
||||
f.parents = nil
|
||||
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Call:
|
||||
if instr.Common().IsInvoke() {
|
||||
continue
|
||||
}
|
||||
switch call := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
// ignore
|
||||
case *ssa.Function:
|
||||
if isCGoInternal(call.Name()) {
|
||||
continue
|
||||
}
|
||||
child := p.GetFunction(call)
|
||||
if child.CName() != "" {
|
||||
continue // assume non-blocking
|
||||
}
|
||||
if child.RelString(nil) == "time.Sleep" {
|
||||
f.blocking = true
|
||||
}
|
||||
f.children = append(f.children, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range p.Functions {
|
||||
for _, child := range f.children {
|
||||
child.parents = append(child.parents, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Analyse which functions are recursively blocking.
|
||||
//
|
||||
// Depends on AnalyseCallgraph.
|
||||
func (p *Program) AnalyseBlockingRecursive() {
|
||||
worklist := make([]*Function, 0)
|
||||
|
||||
// Fill worklist with directly blocking functions.
|
||||
for _, f := range p.Functions {
|
||||
if f.blocking {
|
||||
worklist = append(worklist, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep reducing this worklist by marking a function as recursively blocking
|
||||
// from the worklist and pushing all its parents that are non-blocking.
|
||||
// This is somewhat similar to a worklist in a mark-sweep garbage collector.
|
||||
// The work items are then grey objects.
|
||||
for len(worklist) != 0 {
|
||||
// Pick the topmost.
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
for _, parent := range f.parents {
|
||||
if !parent.blocking {
|
||||
parent.blocking = true
|
||||
worklist = append(worklist, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether we need a scheduler. A scheduler is only necessary when there
|
||||
// are go calls that start blocking functions (if they're not blocking, the go
|
||||
// function can be turned into a regular function call).
|
||||
//
|
||||
// Depends on AnalyseBlockingRecursive.
|
||||
func (p *Program) AnalyseGoCalls() {
|
||||
p.goCalls = nil
|
||||
for _, f := range p.Functions {
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
switch instr := instr.(type) {
|
||||
case *ssa.Go:
|
||||
p.goCalls = append(p.goCalls, instr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, instr := range p.goCalls {
|
||||
switch instr := instr.Call.Value.(type) {
|
||||
case *ssa.Builtin:
|
||||
case *ssa.Function:
|
||||
if p.functionMap[instr].blocking {
|
||||
p.needsScheduler = true
|
||||
}
|
||||
default:
|
||||
panic("unknown go call function type")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Simple pass that removes dead code. This pass makes later analysis passes
|
||||
// more useful.
|
||||
func (p *Program) SimpleDCE() {
|
||||
@@ -172,11 +68,12 @@ func (p *Program) SimpleDCE() {
|
||||
// functions.
|
||||
main := p.mainPkg.Members["main"].(*ssa.Function)
|
||||
runtimePkg := p.Program.ImportedPackage("runtime")
|
||||
mathPkg := p.Program.ImportedPackage("math")
|
||||
p.GetFunction(main).flag = true
|
||||
worklist := []*ssa.Function{main}
|
||||
for _, f := range p.Functions {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg {
|
||||
if f.flag || isCGoInternal(f.Name()) {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
||||
if f.flag {
|
||||
continue
|
||||
}
|
||||
f.flag = true
|
||||
@@ -206,7 +103,7 @@ func (p *Program) SimpleDCE() {
|
||||
}
|
||||
}
|
||||
for _, operand := range instr.Operands(nil) {
|
||||
if operand == nil || *operand == nil || isCGoInternal((*operand).Name()) {
|
||||
if operand == nil || *operand == nil {
|
||||
continue
|
||||
}
|
||||
switch operand := (*operand).(type) {
|
||||
@@ -239,21 +136,3 @@ func (p *Program) SimpleDCE() {
|
||||
}
|
||||
p.Functions = livefunctions
|
||||
}
|
||||
|
||||
// Whether this function needs a scheduler.
|
||||
//
|
||||
// Depends on AnalyseGoCalls.
|
||||
func (p *Program) NeedsScheduler() bool {
|
||||
return p.needsScheduler
|
||||
}
|
||||
|
||||
// Whether this function blocks. Builtins are also accepted for convenience.
|
||||
// They will always be non-blocking.
|
||||
//
|
||||
// Depends on AnalyseBlockingRecursive.
|
||||
func (p *Program) IsBlocking(f *Function) bool {
|
||||
if !p.needsScheduler {
|
||||
return false
|
||||
}
|
||||
return f.blocking
|
||||
}
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: 2ab163c2ae...7d9b416701
+1
-1
Submodule lib/compiler-rt updated: a4cbb02bca...5bc79797e1
@@ -0,0 +1,71 @@
|
||||
// +build byollvm
|
||||
|
||||
package main
|
||||
|
||||
// This file provides a Link() function that uses the bundled lld if possible.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
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(linker string, flags ...string) error {
|
||||
switch linker {
|
||||
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)))
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in ld.lld")
|
||||
}
|
||||
return nil
|
||||
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)))
|
||||
defer C.free(buf)
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in wasm-ld")
|
||||
}
|
||||
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 = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// +build !byollvm
|
||||
|
||||
package main
|
||||
|
||||
// This file provides a Link() function that always runs an external command. It
|
||||
// is provided for when tinygo is built without linking to liblld.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Link invokes a linker with the given name and arguments.
|
||||
//
|
||||
// This version always runs the linker as an external command.
|
||||
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 = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// +build byollvm
|
||||
|
||||
// This file provides C wrappers for liblld.
|
||||
|
||||
#include <lld/Common/Driver.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool tinygo_link_elf(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::elf::link(args, false);
|
||||
}
|
||||
|
||||
bool tinygo_link_wasm(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::wasm::link(args, false);
|
||||
}
|
||||
|
||||
} // external "C"
|
||||
-362
@@ -1,362 +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"
|
||||
)
|
||||
|
||||
// fileInfo holds all Cgo-related information of a given *ast.File.
|
||||
type fileInfo struct {
|
||||
*ast.File
|
||||
filename string
|
||||
functions []*functionInfo
|
||||
typedefs []*typedefInfo
|
||||
importCPos token.Pos
|
||||
}
|
||||
|
||||
// functionInfo stores some information about a Cgo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
name string
|
||||
args []paramInfo
|
||||
result string
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a Cgo function (see functionInfo).
|
||||
type paramInfo struct {
|
||||
name string
|
||||
typeName string
|
||||
}
|
||||
|
||||
// typedefInfo contains information about a single typedef in C.
|
||||
type typedefInfo struct {
|
||||
newName string // newly defined type name
|
||||
oldName string // old type name, may be something like "unsigned long long"
|
||||
size int // size in bytes
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
// 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.
|
||||
ast.Inspect(f, info.walker)
|
||||
|
||||
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.
|
||||
for _, fn := range info.functions {
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: mapCgoType(fn.name),
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.name),
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: info.importCPos,
|
||||
Params: &ast.FieldList{
|
||||
Opening: info.importCPos,
|
||||
List: args,
|
||||
Closing: info.importCPos,
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(fn.result),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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: mapCgoType(arg.name),
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: mapCgoType(arg.typeName),
|
||||
},
|
||||
}
|
||||
}
|
||||
info.Decls = append(info.Decls, decl)
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
for _, typedef := range info.typedefs {
|
||||
newType := mapCgoType(typedef.newName)
|
||||
oldType := mapCgoType(typedef.oldName)
|
||||
switch oldType {
|
||||
// TODO: plain char (may be signed or unsigned)
|
||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "int8"
|
||||
case 2:
|
||||
oldType = "int16"
|
||||
case 4:
|
||||
oldType = "int32"
|
||||
case 8:
|
||||
oldType = "int64"
|
||||
}
|
||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
||||
switch typedef.size {
|
||||
case 1:
|
||||
oldType = "uint8"
|
||||
case 2:
|
||||
oldType = "uint16"
|
||||
case 4:
|
||||
oldType = "uint32"
|
||||
case 8:
|
||||
oldType = "uint64"
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(newType, "C._Cgo_") {
|
||||
newType = "C." + newType[len("C._Cgo_"):]
|
||||
}
|
||||
if _, ok := cgoAliases[newType]; ok {
|
||||
// This is a type that also exists in Go (defined in stdint.h).
|
||||
continue
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: newType,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: newType,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: info.importCPos,
|
||||
Name: oldType,
|
||||
},
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
gen.Specs = append(gen.Specs, typeSpec)
|
||||
}
|
||||
info.Decls = append(info.Decls, gen)
|
||||
}
|
||||
|
||||
// walker replaces all "C".<something> call expressions to literal
|
||||
// "C.<something>" expressions. This is impossible to write in Go (a dot cannot
|
||||
// be used in the middle of a name) so is used as a new namespace for C call
|
||||
// expressions.
|
||||
func (info *fileInfo) walker(node ast.Node) bool {
|
||||
switch node := 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 x.Name == "C" {
|
||||
node.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(fun.Sel.Name),
|
||||
}
|
||||
}
|
||||
case *ast.ValueSpec:
|
||||
typ, ok := node.Type.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := typ.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if x.Name == "C" {
|
||||
node.Type = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: mapCgoType(typ.Sel.Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// mapCgoType converts a C type name into a Go type name with a "C." prefix.
|
||||
func mapCgoType(t string) string {
|
||||
switch t {
|
||||
case "signed char":
|
||||
return "C.schar"
|
||||
case "long long":
|
||||
return "C.longlong"
|
||||
case "unsigned char":
|
||||
return "C.schar"
|
||||
case "unsigned short":
|
||||
return "C.ushort"
|
||||
case "unsigned int":
|
||||
return "C.uint"
|
||||
case "unsigned long":
|
||||
return "C.ulong"
|
||||
case "unsigned long long":
|
||||
return "C.ulonglong"
|
||||
default:
|
||||
return "C." + t
|
||||
}
|
||||
}
|
||||
+17
-5
@@ -1,5 +1,10 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Errors contains a list of parser errors or a list of typechecker errors for
|
||||
// the given package.
|
||||
type Errors struct {
|
||||
@@ -15,13 +20,20 @@ func (e Errors) Error() string {
|
||||
// packages is a list from the root package to the leaf package that imports one
|
||||
// of the packages in the list.
|
||||
type ImportCycleError struct {
|
||||
Packages []string
|
||||
Packages []string
|
||||
ImportPositions []token.Position
|
||||
}
|
||||
|
||||
func (e *ImportCycleError) Error() string {
|
||||
msg := "import cycle: " + e.Packages[0]
|
||||
for _, path := range e.Packages[1:] {
|
||||
msg += " → " + path
|
||||
var msg strings.Builder
|
||||
msg.WriteString("import cycle:\n\t")
|
||||
msg.WriteString(strings.Join(e.Packages, "\n\t"))
|
||||
msg.WriteString("\n at ")
|
||||
for i, pos := range e.ImportPositions {
|
||||
if i > 0 {
|
||||
msg.WriteString(", ")
|
||||
}
|
||||
msg.WriteString(pos.String())
|
||||
}
|
||||
return msg
|
||||
return msg.String()
|
||||
}
|
||||
|
||||
@@ -1,126 +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"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -I/usr/lib/llvm-7/include
|
||||
#cgo LDFLAGS: -L/usr/lib/llvm-7/lib -lclang
|
||||
#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<<30 - 1]*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 := C.clang_Cursor_getNumArguments(c)
|
||||
fn := &functionInfo{name: name}
|
||||
info.functions = append(info.functions, fn)
|
||||
for i := C.int(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))
|
||||
argTypeName := getString(C.clang_getTypeSpelling(argType))
|
||||
fn.args = append(fn.args, paramInfo{argName, argTypeName})
|
||||
}
|
||||
resultType := C.clang_getCursorResultType(c)
|
||||
resultTypeName := getString(C.clang_getTypeSpelling(resultType))
|
||||
fn.result = resultTypeName
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typedefType := C.clang_getCursorType(c)
|
||||
name := getString(C.clang_getTypedefName(typedefType))
|
||||
underlyingType := C.clang_getTypedefDeclUnderlyingType(c)
|
||||
underlyingTypeName := getString(C.clang_getTypeSpelling(underlyingType))
|
||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
||||
info.typedefs = append(info.typedefs, &typedefInfo{
|
||||
newName: name,
|
||||
oldName: underlyingTypeName,
|
||||
size: int(typeSize),
|
||||
})
|
||||
}
|
||||
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
|
||||
}
|
||||
+168
-29
@@ -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,13 +185,17 @@ 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 {
|
||||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
|
||||
if pkg.ImportPath != err.Packages[0] {
|
||||
err.Packages = append([]string{pkg.ImportPath}, err.Packages...)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -174,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
|
||||
}
|
||||
@@ -191,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 {
|
||||
@@ -204,9 +317,9 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||
defer rd.Close()
|
||||
relpath := path
|
||||
if filepath.IsAbs(path) {
|
||||
relpath, err = filepath.Rel(p.Dir, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
rp, err := filepath.Rel(p.Dir, path)
|
||||
if err == nil {
|
||||
relpath = rp
|
||||
}
|
||||
}
|
||||
return parser.ParseFile(p.fset, relpath, rd, mode)
|
||||
@@ -215,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
|
||||
}
|
||||
@@ -229,7 +342,7 @@ func (p *Package) Parse() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
files, err := p.parseFiles()
|
||||
files, err := p.parseFiles(includeTests)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -268,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)
|
||||
@@ -291,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
|
||||
}
|
||||
|
||||
@@ -321,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 {
|
||||
@@ -339,10 +475,13 @@ func (p *Package) importRecursively() error {
|
||||
return err
|
||||
}
|
||||
if importedPkg.Importing {
|
||||
return &ImportCycleError{[]string{p.ImportPath, importedPkg.ImportPath}}
|
||||
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...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
p.Imports[to] = importedPkg
|
||||
|
||||
@@ -11,31 +11,58 @@ import (
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/aykevl/tinygo/compiler"
|
||||
"github.com/aykevl/tinygo/interp"
|
||||
"github.com/aykevl/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/interp"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
|
||||
serial "go.bug.st/serial.v1"
|
||||
)
|
||||
|
||||
var commands = map[string]string{
|
||||
"ar": "ar",
|
||||
"clang": "clang-7",
|
||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
||||
// some more information regarding what went wrong while running a command.
|
||||
type commandError struct {
|
||||
Msg string
|
||||
File string
|
||||
Err error
|
||||
}
|
||||
|
||||
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
|
||||
initInterp bool
|
||||
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.
|
||||
@@ -44,24 +71,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 := goenv.Get("TINYGOROOT")
|
||||
|
||||
// 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 := goenv.Get("GOROOT")
|
||||
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 && minor != 13) {
|
||||
return fmt.Errorf("requires go version 1.11, 1.12, or 1.13, 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,
|
||||
InitInterp: config.initInterp,
|
||||
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: goenv.Get("GOPATH"),
|
||||
BuildTags: tags,
|
||||
TestConfig: config.testConfig,
|
||||
}
|
||||
c, err := compiler.NewCompiler(pkgName, compilerConfig)
|
||||
if err != nil {
|
||||
@@ -69,31 +134,31 @@ 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 {
|
||||
return errors.New("verification error after IR construction")
|
||||
}
|
||||
|
||||
if config.initInterp {
|
||||
err = interp.Run(c.Module(), c.TargetData(), config.dumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
err = interp.Run(c.Module(), config.dumpSSA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after applying function sections")
|
||||
if spec.GOOS != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
}
|
||||
|
||||
// Browsers cannot handle external functions that have type i64 because it
|
||||
@@ -106,9 +171,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
|
||||
@@ -174,33 +236,39 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
|
||||
// Load builtins library from the cache, possibly compiling it on the
|
||||
// fly.
|
||||
var cachePath string
|
||||
var librt string
|
||||
if spec.RTLib == "compiler-rt" {
|
||||
librt, err := loadBuiltins(spec.Triple)
|
||||
librt, err = loadBuiltins(spec.Triple)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cachePath, _ = filepath.Split(librt)
|
||||
}
|
||||
|
||||
// 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, "-L", cachePath, "-lrt-"+spec.Triple)
|
||||
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 err
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
@@ -210,26 +278,22 @@ 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 err
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
cmd := exec.Command(spec.Linker, ldflags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
err = cmd.Run()
|
||||
err = Link(spec.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return err
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
if config.printSizes == "short" || config.printSizes == "full" {
|
||||
@@ -251,17 +315,17 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
|
||||
}
|
||||
}
|
||||
|
||||
if outext == ".hex" || outext == ".bin" {
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
format := map[string]string{
|
||||
".hex": "ihex",
|
||||
".bin": "binary",
|
||||
}[outext]
|
||||
cmd := exec.Command(spec.Objcopy, "-O", format, executable, tmppath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
err := Objcopy(executable, tmppath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if outext == ".uf2" {
|
||||
// Get UF2 from the .elf file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := ConvertELFFileToUF2File(executable, tmppath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -304,28 +368,133 @@ 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return Compile(pkgName, ".hex", spec, config, func(tmppath string) error {
|
||||
if spec.Flasher == "" {
|
||||
return errors.New("no flash command specified - did you miss a -target flag?")
|
||||
// determine the type of file to compile
|
||||
var fileExt string
|
||||
|
||||
switch spec.FlashMethod {
|
||||
case "command", "":
|
||||
switch {
|
||||
case strings.Contains(spec.FlashCommand, "{hex}"):
|
||||
fileExt = ".hex"
|
||||
case strings.Contains(spec.FlashCommand, "{elf}"):
|
||||
fileExt = ".elf"
|
||||
case strings.Contains(spec.FlashCommand, "{bin}"):
|
||||
fileExt = ".bin"
|
||||
case strings.Contains(spec.FlashCommand, "{uf2}"):
|
||||
fileExt = ".uf2"
|
||||
default:
|
||||
return errors.New("invalid target file - did you forget the {hex} token in the 'flash-command' section?")
|
||||
}
|
||||
case "msd":
|
||||
if spec.FlashFilename == "" {
|
||||
return errors.New("invalid target file: flash-method was set to \"msd\" but no msd-firmware-name was set")
|
||||
}
|
||||
fileExt = filepath.Ext(spec.FlashFilename)
|
||||
case "openocd":
|
||||
fileExt = ".hex"
|
||||
case "native":
|
||||
return errors.New("unknown flash method \"native\" - did you miss a -target flag?")
|
||||
default:
|
||||
return errors.New("unknown flash method: " + spec.FlashMethod)
|
||||
}
|
||||
|
||||
return Compile(pkgName, fileExt, spec, config, func(tmppath string) error {
|
||||
// do we need port reset to put MCU into bootloader mode?
|
||||
if spec.PortReset == "true" {
|
||||
err := touchSerialPortAt1200bps(port)
|
||||
if err != nil {
|
||||
return &commandError{"failed to reset port", tmppath, err}
|
||||
}
|
||||
// give the target MCU a chance to restart into bootloader
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
|
||||
// Create the command.
|
||||
flashCmd := spec.Flasher
|
||||
flashCmd = strings.Replace(flashCmd, "{hex}", tmppath, -1)
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
// this flashing method copies the binary data to a Mass Storage Device (msd)
|
||||
switch spec.FlashMethod {
|
||||
case "", "command":
|
||||
// Create the command.
|
||||
flashCmd := spec.FlashCommand
|
||||
fileToken := "{" + fileExt[1:] + "}"
|
||||
flashCmd = strings.Replace(flashCmd, fileToken, tmppath, -1)
|
||||
flashCmd = strings.Replace(flashCmd, "{port}", port, -1)
|
||||
|
||||
// Execute the command.
|
||||
cmd := exec.Command("/bin/sh", "-c", flashCmd)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = sourceDir()
|
||||
return cmd.Run()
|
||||
// Execute the command.
|
||||
cmd := exec.Command("/bin/sh", "-c", flashCmd)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
case "msd":
|
||||
switch fileExt {
|
||||
case ".uf2":
|
||||
err := flashUF2UsingMSD(spec.FlashVolume, tmppath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
case ".hex":
|
||||
err := flashHexUsingMSD(spec.FlashVolume, tmppath)
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return errors.New("mass storage device flashing currently only supports uf2 and hex")
|
||||
}
|
||||
case "openocd":
|
||||
args, err := spec.OpenOCDConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args = append(args, "-c", "program "+tmppath+" reset exit")
|
||||
cmd := exec.Command("openocd", args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to flash", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("unknown flash method: %s", spec.FlashMethod)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -344,9 +513,33 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
}
|
||||
|
||||
return Compile(pkgName, "", spec, config, func(tmppath string) error {
|
||||
if len(spec.OCDDaemon) != 0 {
|
||||
// Find a good way to run GDB.
|
||||
gdbInterface := spec.FlashMethod
|
||||
switch gdbInterface {
|
||||
case "msd", "command", "":
|
||||
if gdbInterface == "" {
|
||||
gdbInterface = "command"
|
||||
}
|
||||
if spec.OpenOCDInterface != "" && spec.OpenOCDTarget != "" {
|
||||
gdbInterface = "openocd"
|
||||
}
|
||||
}
|
||||
|
||||
// Run the GDB server, if necessary.
|
||||
var gdbCommands []string
|
||||
switch gdbInterface {
|
||||
case "native":
|
||||
// Run GDB directly.
|
||||
gdbCommands = append(gdbCommands, "run")
|
||||
case "openocd":
|
||||
gdbCommands = append(gdbCommands, "target remote :3333", "monitor halt", "load", "monitor reset", "c")
|
||||
|
||||
// We need a separate debugging daemon for on-chip debugging.
|
||||
daemon := exec.Command(spec.OCDDaemon[0], spec.OCDDaemon[1:]...)
|
||||
args, err := spec.OpenOCDConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daemon := exec.Command("openocd", args...)
|
||||
if ocdOutput {
|
||||
// Make it clear which output is from the daemon.
|
||||
w := &ColorWriter{
|
||||
@@ -359,11 +552,7 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
}
|
||||
// Make sure the daemon doesn't receive Ctrl-C that is intended for
|
||||
// GDB (to break the currently executing program).
|
||||
// https://stackoverflow.com/a/35435038/559350
|
||||
daemon.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setpgid: true,
|
||||
Pgid: 0,
|
||||
}
|
||||
setCommandAsDaemon(daemon)
|
||||
// Start now, and kill it on exit.
|
||||
daemon.Start()
|
||||
defer func() {
|
||||
@@ -371,6 +560,10 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
// Maybe we should send a .Kill() after x seconds?
|
||||
daemon.Wait()
|
||||
}()
|
||||
case "msd":
|
||||
return errors.New("gdb is not supported for drag-and-drop programmable devices")
|
||||
default:
|
||||
return fmt.Errorf("gdb is not supported with interface %#v", gdbInterface)
|
||||
}
|
||||
|
||||
// Ignore Ctrl-C, it must be passed on to GDB.
|
||||
@@ -385,14 +578,18 @@ func FlashGDB(pkgName, target, port string, ocdOutput bool, config *BuildConfig)
|
||||
// By default: gdb -ex run <binary>
|
||||
// Exit GDB with Ctrl-D.
|
||||
params := []string{tmppath}
|
||||
for _, cmd := range spec.GDBCmds {
|
||||
for _, cmd := range gdbCommands {
|
||||
params = append(params, "-ex", cmd)
|
||||
}
|
||||
cmd := exec.Command(spec.GDB, params...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return &commandError{"failed to run gdb with", tmppath, err}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -415,8 +612,9 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return nil
|
||||
}
|
||||
return &commandError{"failed to run compiled binary", tmppath, err}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
} else {
|
||||
// Run in an emulator.
|
||||
args := append(spec.Emulator[1:], tmppath)
|
||||
@@ -429,20 +627,97 @@ func Run(pkgName, target string, config *BuildConfig) error {
|
||||
// Workaround for QEMU which always exits with an error.
|
||||
return nil
|
||||
}
|
||||
return &commandError{"failed to run emulator with", tmppath, err}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func touchSerialPortAt1200bps(port string) error {
|
||||
// Open port
|
||||
p, err := serial.Open(port, &serial.Mode{BaudRate: 1200})
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening port: %s", err)
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
p.SetDTR(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
func flashUF2UsingMSD(volume, tmppath string) error {
|
||||
// find standard UF2 info path
|
||||
infoPath := "/media/*/" + volume + "/INFO_UF2.TXT"
|
||||
if runtime.GOOS == "darwin" {
|
||||
infoPath = "/Volumes/" + volume + "/INFO_UF2.TXT"
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(infoPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d == nil {
|
||||
return errors.New("unable to locate UF2 device: " + volume)
|
||||
}
|
||||
|
||||
return moveFile(tmppath, filepath.Dir(d[0])+"/flash.uf2")
|
||||
}
|
||||
|
||||
func flashHexUsingMSD(volume, tmppath string) error {
|
||||
// find expected volume path
|
||||
destPath := "/media/*/" + volume
|
||||
if runtime.GOOS == "darwin" {
|
||||
destPath = "/Volumes/" + volume
|
||||
}
|
||||
|
||||
d, err := filepath.Glob(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d == nil {
|
||||
return errors.New("unable to locate device: " + volume)
|
||||
}
|
||||
|
||||
return moveFile(tmppath, d[0]+"/flash.hex")
|
||||
}
|
||||
|
||||
// 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)
|
||||
fmt.Fprintf(os.Stderr, "usage: %s command [-printir] [-target=<target>] -o <output> <input>\n", os.Args[0])
|
||||
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()+")")
|
||||
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
|
||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
|
||||
fmt.Fprintln(os.Stderr, " help: print this help text")
|
||||
fmt.Fprintln(os.Stderr, "\nflags:")
|
||||
flag.PrintDefaults()
|
||||
@@ -450,19 +725,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)
|
||||
@@ -472,18 +752,22 @@ 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")
|
||||
initInterp := flag.Bool("initinterp", true, "enable/disable partial evaluator of generated IR")
|
||||
port := flag.String("port", "/dev/ttyACM0", "flash port")
|
||||
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.")
|
||||
@@ -494,14 +778,17 @@ func main() {
|
||||
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
config := &BuildConfig{
|
||||
opt: *opt,
|
||||
gc: *gc,
|
||||
printIR: *printIR,
|
||||
dumpSSA: *dumpSSA,
|
||||
debug: !*nodebug,
|
||||
printSizes: *printSize,
|
||||
initInterp: *initInterp,
|
||||
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 != "" {
|
||||
@@ -512,6 +799,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 {
|
||||
@@ -521,8 +821,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)
|
||||
}
|
||||
@@ -530,7 +833,21 @@ 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!
|
||||
if *outpath == "" {
|
||||
fmt.Fprintln(os.Stderr, "No output filename supplied (-o).")
|
||||
usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
if *target == "" {
|
||||
fmt.Fprintln(os.Stderr, "No target (-target).")
|
||||
}
|
||||
err := compileBuiltins(*target, func(path string) error {
|
||||
return moveFile(path, *outpath)
|
||||
})
|
||||
handleCompilerError(err)
|
||||
case "flash", "gdb":
|
||||
if *outpath != "" {
|
||||
@@ -558,16 +875,44 @@ 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()
|
||||
err := os.RemoveAll(dir)
|
||||
err := os.RemoveAll(goenv.Get("GOCACHE"))
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cannot clean cache:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
case "help":
|
||||
usage()
|
||||
case "version":
|
||||
goversion := "<unknown>"
|
||||
if s, err := getGorootVersionString(goenv.Get("GOROOT")); err == nil {
|
||||
goversion = s
|
||||
}
|
||||
fmt.Printf("tinygo version %s %s/%s (using go version %s)\n", version, runtime.GOOS, runtime.GOARCH, goversion)
|
||||
case "env":
|
||||
if flag.NArg() == 0 {
|
||||
// Show all environment variables.
|
||||
for _, key := range goenv.Keys {
|
||||
fmt.Printf("%s=%#v\n", key, goenv.Get(key))
|
||||
}
|
||||
} else {
|
||||
// Show only one (or a few) environment variables.
|
||||
for i := 0; i < flag.NArg(); i++ {
|
||||
fmt.Println(goenv.Get(flag.Arg(i)))
|
||||
}
|
||||
}
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "Unknown command:", command)
|
||||
usage()
|
||||
|
||||
+63
-11
@@ -10,19 +10,25 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"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)
|
||||
}
|
||||
if len(matches) == 0 || len(dirMatches) == 0 {
|
||||
t.Fatal("no test files found")
|
||||
}
|
||||
@@ -39,25 +45,63 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
t.Log("running tests on the host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Log("running tests on host...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "", t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("running tests on the qemu target...")
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
|
||||
t.Log("running tests for emulated cortex-m3...")
|
||||
for _, path := range matches {
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "qemu", t)
|
||||
})
|
||||
}
|
||||
|
||||
if runtime.GOOS == "linux" {
|
||||
t.Log("running tests for linux/arm...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "arm--linux-gnueabihf", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for linux/arm64...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "cgo")+string(filepath.Separator) {
|
||||
continue // TODO: improve CGo
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "aarch64--linux-gnu", t)
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("running tests for WebAssembly...")
|
||||
for _, path := range matches {
|
||||
if path == filepath.Join("testdata", "gc.go") {
|
||||
continue // known to fail
|
||||
}
|
||||
t.Run(path, func(t *testing.T) {
|
||||
runTest(path, tmpdir, "wasm", 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)
|
||||
@@ -74,14 +118,21 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
opt: "z",
|
||||
printIR: false,
|
||||
dumpSSA: false,
|
||||
verifyIR: true,
|
||||
debug: false,
|
||||
printSizes: "",
|
||||
initInterp: true,
|
||||
wasmAbi: "js",
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -113,6 +164,7 @@ func runTest(path, tmpdir string, target string, t *testing.T) {
|
||||
|
||||
// putchar() prints CRLF, convert it to LF.
|
||||
actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
|
||||
expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows
|
||||
|
||||
// Check whether the command ran successfully.
|
||||
fail := false
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/marcinbor85/gohex"
|
||||
)
|
||||
|
||||
// ObjcopyError is an error returned by functions that act like objcopy.
|
||||
type ObjcopyError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ObjcopyError) Error() string {
|
||||
if e.Err == nil {
|
||||
return e.Op
|
||||
}
|
||||
return e.Op + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
type ProgSlice []*elf.Prog
|
||||
|
||||
func (s ProgSlice) Len() int { return len(s) }
|
||||
func (s ProgSlice) Less(i, j int) bool { return s[i].Paddr < s[j].Paddr }
|
||||
func (s ProgSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
// ExtractROM extracts a firmware image and the first load address from the
|
||||
// given ELF file. It tries to emulate the behavior of objcopy.
|
||||
func ExtractROM(path string) (uint64, []byte, error) {
|
||||
f, err := elf.Open(path)
|
||||
if err != nil {
|
||||
return 0, nil, ObjcopyError{"failed to open ELF file to extract text segment", err}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// The GNU objcopy command does the following for firmware extraction (from
|
||||
// the man page):
|
||||
// > When objcopy generates a raw binary file, it will essentially produce a
|
||||
// > memory dump of the contents of the input object file. All symbols and
|
||||
// > relocation information will be discarded. The memory dump will start at
|
||||
// > the load address of the lowest section copied into the output file.
|
||||
|
||||
// Find the lowest section address.
|
||||
startAddr := ^uint64(0)
|
||||
for _, section := range f.Sections {
|
||||
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if section.Addr < startAddr {
|
||||
startAddr = section.Addr
|
||||
}
|
||||
}
|
||||
|
||||
progs := make(ProgSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||
continue
|
||||
}
|
||||
progs = append(progs, prog)
|
||||
}
|
||||
if len(progs) == 0 {
|
||||
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
|
||||
}
|
||||
sort.Sort(progs)
|
||||
|
||||
var rom []byte
|
||||
for _, prog := range progs {
|
||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||
return 0, nil, ObjcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||
}
|
||||
data, err := ioutil.ReadAll(prog.Open())
|
||||
if err != nil {
|
||||
return 0, nil, ObjcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||
}
|
||||
rom = append(rom, data...)
|
||||
}
|
||||
if progs[0].Paddr < startAddr {
|
||||
// The lowest memory address is before the first section. This means
|
||||
// that there is some extra data loaded at the start of the image that
|
||||
// should be discarded.
|
||||
// Example: ELF files where .text doesn't start at address 0 because
|
||||
// there is a bootloader at the start.
|
||||
return startAddr, rom[startAddr-progs[0].Paddr:], nil
|
||||
} else {
|
||||
return progs[0].Paddr, rom, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Objcopy converts an ELF file to a different (simpler) output file format:
|
||||
// .bin or .hex. It extracts only the .text section.
|
||||
func Objcopy(infile, outfile string) error {
|
||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Read the .text segment.
|
||||
addr, data, err := ExtractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write to the file, in the correct format.
|
||||
switch filepath.Ext(outfile) {
|
||||
case ".gba":
|
||||
// The address is not stored in a .gba file.
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".bin":
|
||||
// The address is not stored in a .bin file (therefore you
|
||||
// should use .hex files in most cases).
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".hex":
|
||||
mem := gohex.NewMemory()
|
||||
err := mem.AddBinary(uint32(addr), data)
|
||||
if err != nil {
|
||||
return ObjcopyError{"failed to create .hex file", err}
|
||||
}
|
||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||
return nil
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
+62
-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,5 +138,34 @@ 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.
|
||||
//
|
||||
// TODO: it doesn't actually return the old state, meaning that it cannot be
|
||||
// nested.
|
||||
func DisableInterrupts() uintptr {
|
||||
Asm("cpsid if")
|
||||
return 0
|
||||
}
|
||||
|
||||
// EnableInterrupts enables all interrupts again. The value passed in must be
|
||||
// the mask returned by DisableInterrupts.
|
||||
//
|
||||
// TODO: it doesn't actually use the old state, meaning that it cannot be
|
||||
// nested.
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -7,27 +7,34 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// change these to test a different UART or pins if available
|
||||
var (
|
||||
uart = machine.UART0
|
||||
tx = machine.UART_TX_PIN
|
||||
rx = machine.UART_RX_PIN
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.UART0.Configure(machine.UARTConfig{})
|
||||
machine.UART0.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
|
||||
|
||||
input := make([]byte, 64)
|
||||
i := 0
|
||||
for {
|
||||
if machine.UART0.Buffered() > 0 {
|
||||
data, _ := machine.UART0.ReadByte()
|
||||
if uart.Buffered() > 0 {
|
||||
data, _ := uart.ReadByte()
|
||||
|
||||
switch data {
|
||||
case 13:
|
||||
// return key
|
||||
machine.UART0.Write([]byte("\r\n"))
|
||||
machine.UART0.Write([]byte("You typed: "))
|
||||
machine.UART0.Write(input[:i])
|
||||
machine.UART0.Write([]byte("\r\n"))
|
||||
uart.Write([]byte("\r\n"))
|
||||
uart.Write([]byte("You typed: "))
|
||||
uart.Write(input[:i])
|
||||
uart.Write([]byte("\r\n"))
|
||||
i = 0
|
||||
default:
|
||||
// just echo the character
|
||||
machine.UART0.WriteByte(data)
|
||||
uart.WriteByte(data)
|
||||
input[i] = data
|
||||
i++
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
const WASM_URL = 'wasm.wasm';
|
||||
|
||||
var wasm;
|
||||
|
||||
function updateResult() {
|
||||
wasm.exports.update();
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelector('#a').oninput = updateResult;
|
||||
document.querySelector('#b').oninput = updateResult;
|
||||
|
||||
const go = new Go();
|
||||
if ('instantiateStreaming' in WebAssembly) {
|
||||
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
})
|
||||
} else {
|
||||
fetch(WASM_URL).then(resp =>
|
||||
resp.arrayBuffer()
|
||||
).then(bytes =>
|
||||
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
|
||||
wasm = obj.instance;
|
||||
go.run(wasm);
|
||||
updateResult();
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user