mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 22:58:41 +00:00
Compare commits
391 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ebde8b5875 | |||
| 9d50587d63 | |||
| 5e6f1725ac | |||
| 9cdc2fa768 | |||
| 084386262a | |||
| 674ddef219 | |||
| 0933577e60 | |||
| 69c1d802e1 | |||
| b424056721 | |||
| d735df6e16 | |||
| d37bbadb54 | |||
| ab7dc45288 | |||
| eee1b995f6 | |||
| a4fa41b49d | |||
| a5a90a57b9 | |||
| 71a380ce8c | |||
| 3b2a4b64c5 | |||
| 184827e4d8 | |||
| 14474e7099 | |||
| 3656ac2fc9 | |||
| 08f01ba3ff | |||
| 699312f477 | |||
| 9644edcd5a | |||
| 46325910c5 | |||
| 923c2e7ada | |||
| 18e446561d | |||
| 447537aebe | |||
| 74e32acf33 | |||
| 072f8c354e | |||
| 0587934a44 | |||
| c2481b10f4 | |||
| d41f01f003 | |||
| 5a70c88483 | |||
| 5510dec846 | |||
| dffb9fbfa7 | |||
| 525ded3d90 | |||
| ec2658ca79 | |||
| 2004555fe2 | |||
| 9aeb8d9e06 | |||
| 34ee3883d6 | |||
| 768c652468 | |||
| 2778377ac9 | |||
| cf32607306 | |||
| ad022ef23d | |||
| c97b7221bd | |||
| 8d32a7c3a3 | |||
| 49eb414530 | |||
| 39d21e21f1 | |||
| fa8a93b4e7 | |||
| 8f9419a35d | |||
| 7bdd4a1186 | |||
| 0105f815c6 | |||
| d441f0152f | |||
| 06647aab24 | |||
| 24259cbb5f | |||
| 2f932a9eee | |||
| 93a06d1157 | |||
| 374349cfa5 | |||
| 024a0827ea | |||
| fadddc54a3 | |||
| a44d74d4c0 | |||
| 24ff2d1ee2 | |||
| 0db26b0662 | |||
| 4f7a650614 | |||
| e74db01f82 | |||
| 86f48da594 | |||
| 4be80e0372 | |||
| 10e1420237 | |||
| 6a1bb134f9 | |||
| f0bb3c092d | |||
| 3d3e48179e | |||
| ea5df0f214 | |||
| c09724bfc5 | |||
| 00f745e351 | |||
| 5171618284 | |||
| 4605cbbc6e | |||
| 98eee7c22a | |||
| 81199da3f1 | |||
| 3cedebd299 | |||
| 118af9df69 | |||
| 172efc26a7 | |||
| f49e69b02a | |||
| e20af665fa | |||
| 36d1198115 | |||
| 009b27350e | |||
| 93961f9d41 | |||
| acdaaa17d8 | |||
| b7b548a8d0 | |||
| 8266d2ff58 | |||
| d2d78d3d0a | |||
| c6255e4d0a | |||
| 8e6cb89ceb | |||
| 946e2dd405 | |||
| ad73a727a3 | |||
| efafda1d32 | |||
| 405c0263b0 | |||
| 45b5decb4e | |||
| fb39e0917b | |||
| 16fbf53ae2 | |||
| b153bd63f2 | |||
| 6108ee6859 | |||
| 76c9f13e13 | |||
| d31deda1b5 | |||
| 473576e756 | |||
| 913131bf62 | |||
| b41e58bcb4 | |||
| 0db403dc0c | |||
| cadb75a4aa | |||
| 5987233b99 | |||
| 992f1fa248 | |||
| 1cbe09ee89 | |||
| 18cce571a2 | |||
| ac330f4a70 | |||
| e977276044 | |||
| feb2b4715b | |||
| 3b0ed63c29 | |||
| 59cc901340 | |||
| ef600965aa | |||
| e7cf75030c | |||
| dff6e6566d | |||
| cdff0bd3ee | |||
| fa25fa1b0c | |||
| be7529b261 | |||
| 26bdfa9c84 | |||
| b72f677310 | |||
| 2a5ab2500d | |||
| c138a50457 | |||
| 86ab03c999 | |||
| 923a6f5873 | |||
| 071f863e5d | |||
| b884db81ea | |||
| 41df9648a8 | |||
| 3ec94a06ed | |||
| 3777791aa3 | |||
| 96d4987345 | |||
| 66ed03faa2 | |||
| 46d468b79d | |||
| 373fa6d69b | |||
| a4642ddf59 | |||
| ceece08959 | |||
| 7014f90120 | |||
| f7dde33842 | |||
| 2f059ac91e | |||
| e139a9dd71 | |||
| 8dc0deaf46 | |||
| 388d11eecf | |||
| adff391bd2 | |||
| a7794de99d | |||
| 4339cbd56f | |||
| 6b1faeb882 | |||
| 2c15f36702 | |||
| 9d21bfce8c | |||
| c4dff990d9 | |||
| b66b15d02c | |||
| 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 |
+122
-91
@@ -18,7 +18,6 @@ commands:
|
||||
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 \
|
||||
@@ -40,78 +39,42 @@ commands:
|
||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||
rm node-v10.15.1-linux-x64.tar.xz
|
||||
dep:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install Go dependencies"
|
||||
command: |
|
||||
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
dep ensure --vendor-only
|
||||
llvm-source-linux:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-8-v2
|
||||
- llvm-source-9-v0
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-v2
|
||||
key: llvm-source-9-v0
|
||||
paths:
|
||||
- llvm
|
||||
smoketest:
|
||||
steps:
|
||||
- smoketest-no-avr
|
||||
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||
smoketest-no-avr:
|
||||
steps:
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/i2s
|
||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
- llvm-project
|
||||
test-linux:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- apt-dependencies:
|
||||
llvm: <<parameters.llvm>>
|
||||
llvm: "-9"
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- dep
|
||||
- run: go install .
|
||||
- run: go test -v
|
||||
- run: go test -v ./cgo ./compileopts ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- smoketest
|
||||
- run: make smoketest
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
build-linux:
|
||||
assert-test-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
@@ -119,7 +82,6 @@ commands:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
python3 \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
@@ -132,12 +94,70 @@ commands:
|
||||
- install-node
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||
- 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-v4
|
||||
- llvm-build-9-linux-v0-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-9-linux-v0-assert
|
||||
paths:
|
||||
llvm-build
|
||||
- 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
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get install \
|
||||
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-9-linux-v0
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -155,16 +175,9 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-linux-v4
|
||||
key: llvm-build-9-linux-v0
|
||||
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
|
||||
- dep
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
@@ -176,10 +189,11 @@ commands:
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||
- save_cache:
|
||||
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
@@ -187,7 +201,7 @@ commands:
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest
|
||||
- run: make smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
@@ -195,20 +209,27 @@ commands:
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
|
||||
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:
|
||||
- llvm-source-8-macos-v2
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-9-macos-v0
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-8-macos-v2
|
||||
key: llvm-source-9-macos-v0
|
||||
paths:
|
||||
- llvm
|
||||
- llvm-project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-8-macos-v3
|
||||
- llvm-build-9-macos-v0
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -220,16 +241,9 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-8-macos-v3
|
||||
key: llvm-build-9-macos-v0
|
||||
paths:
|
||||
llvm-build
|
||||
- run:
|
||||
name: "Create LLVM symlinks"
|
||||
command: |
|
||||
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
||||
- run:
|
||||
name: "Install Go dependencies"
|
||||
command: dep ensure --vendor-only
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
@@ -247,34 +261,49 @@ commands:
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- smoketest-no-avr
|
||||
- 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:
|
||||
test-llvm9-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
- image: circleci/golang:1.11-stretch
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "-8"
|
||||
test-llvm8-go112:
|
||||
- test-linux
|
||||
test-llvm9-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
- image: circleci/golang:1.12-stretch
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "-8"
|
||||
- test-linux
|
||||
test-llvm9-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.12
|
||||
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||
- image: circleci/golang:1.13-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
working_directory: ~/go/src/github.com/tinygo-org/tinygo
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
@@ -284,7 +313,9 @@ jobs:
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm8-go111
|
||||
- test-llvm8-go112
|
||||
- test-llvm9-go111
|
||||
- test-llvm9-go112
|
||||
- test-llvm9-go113
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
|
||||
+5
-3
@@ -5,10 +5,12 @@ src/device/avr/*.ld
|
||||
src/device/avr/*.s
|
||||
src/device/nrf/*.go
|
||||
src/device/nrf/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/sam/*.go
|
||||
src/device/sam/*.s
|
||||
src/device/sifive/*.go
|
||||
src/device/sifive/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
vendor
|
||||
llvm
|
||||
llvm-build
|
||||
llvm-project
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
url = https://github.com/avr-rust/avr-mcu.git
|
||||
[submodule "lib/cmsis-svd"]
|
||||
path = lib/cmsis-svd
|
||||
url = https://github.com/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
|
||||
|
||||
+8
-5
@@ -16,7 +16,6 @@ LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||
|
||||
* Go (1.11+)
|
||||
* [dep](https://golang.github.io/dep/)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git
|
||||
* CMake
|
||||
@@ -27,16 +26,20 @@ on a different system like Mac.
|
||||
|
||||
## Download the source
|
||||
|
||||
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||
perform these steps:
|
||||
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:
|
||||
|
||||
dep ensure -vendor-only # download Go dependencies
|
||||
make llvm-source # download LLVM
|
||||
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
|
||||
|
||||
+267
@@ -1,3 +1,270 @@
|
||||
0.11.0
|
||||
---
|
||||
* **command line**
|
||||
- add support for QEMU in `gdb` subcommand
|
||||
- use builtin Clang when building statically, dropping the clang-9 dependency
|
||||
- search for default serial port on both macOS and Linux
|
||||
- windows: support `tinygo flash` directly by using win32 wmi
|
||||
* **compiler**
|
||||
- add location information to the IR checker
|
||||
- make reflection sidetables constant globals
|
||||
- improve error locations in goroutine lowering
|
||||
- interp: improve support for maps with string keys
|
||||
- interp: add runtime fallback for mapassign operations
|
||||
* **standard library**
|
||||
- `machine`: add support for `SPI.Tx()` on play.tinygo.org
|
||||
- `machine`: rename `CPU_FREQUENCY` to `CPUFrequency()`
|
||||
* **targets**
|
||||
- `adafruit-pybadge`: add Adafruit Pybadge
|
||||
- `arduino-nano33`: allow simulation on play.tinygo.org
|
||||
- `arduino-nano33`: fix default SPI pin numbers to be D13/D11/D12
|
||||
- `circuitplay-express`: allow simulation on play.tinygo.org
|
||||
- `hifive1-qemu`: add target for testing RISC-V bare metal in QEMU
|
||||
- `riscv`: fix heap corruption due to changes in LLVM 9
|
||||
- `riscv`: add support for compiler-rt
|
||||
- `qemu`: rename to `cortex-m-qemu`
|
||||
|
||||
0.10.0
|
||||
---
|
||||
* **command line**
|
||||
- halt GDB after flashing with `gdb` subcommand
|
||||
- fix a crash when using `-ocd-output`
|
||||
- add `info` subcommand
|
||||
- add `-programmer` flag
|
||||
* **builder**
|
||||
- macos: use llvm@8 instead of just llvm in paths
|
||||
- add `linkerscript` key to target JSON files
|
||||
- write a symbol table when writing out the compiler-rt lib
|
||||
- make Clang header detection more robust
|
||||
- switch to LLVM 9
|
||||
* **compiler**
|
||||
- fix interface miscompilation with reflect
|
||||
- fix miscompile of static goroutine calls to closures
|
||||
- fix `todo: store` panic
|
||||
- fix incorrect starting value for optimized allocations in a loop
|
||||
- optimize coroutines on non-Cortex-M targets
|
||||
- fix crash for programs which have heap allocations but never hit the GC
|
||||
- add support for async interface calls
|
||||
- fix inserting non-const values in a const global
|
||||
- interp: improve error reporting
|
||||
- interp: implement comparing ptrtoint to 0
|
||||
* **cgo**
|
||||
- improve diagnostics
|
||||
- implement the constant parser (for `#define`) as a real parser
|
||||
- rename reserved field names such as `type`
|
||||
- avoid `"unsafe" imported but not used` error
|
||||
- include all enums in the CGo Go AST
|
||||
- add support for nested structs and unions
|
||||
- implement `#cgo CFLAGS`
|
||||
* **standard library**
|
||||
- `reflect`: add implementation of array alignment
|
||||
- `runtime`: improve scheduler performance when no goroutines are queued
|
||||
- `runtime`: add blocking select
|
||||
- `runtime`: implement interface equality in non-trivial cases
|
||||
- `runtime`: add AdjustTimeOffset to update current time
|
||||
- `runtime`: only implement CountString for required platforms
|
||||
- `runtime`: use MSP/PSP registers for scheduling on Cortex-M
|
||||
* **targets**
|
||||
- `arm`: add system timer registers
|
||||
- `atmega`: add port C GPIO support
|
||||
- `atsamd21`: correct handling of pins >= 32
|
||||
- `atsamd21`: i2s initialization fixes
|
||||
- `atsamd51`: fix clock init code
|
||||
- `atsamd51`: correct initialization for RTC
|
||||
- `atsamd51`: fix pin function selection
|
||||
- `atsamd51`: pin method cleanup
|
||||
- `atsamd51`: allow setting pin mode for each of the SPI pins
|
||||
- `atsamd51`: correct channel init and pin map for ADC based on ItsyBitsy-M4
|
||||
- `feather-m4`: add Adafruit Feather M4 board
|
||||
- `hifive1b`: add support for SPI1
|
||||
- `hifive1b`: fix compiling in simulation
|
||||
- `linux`: fix time on arm32
|
||||
- `metro-m4`: add support for Adafruit Metro M4 Express Airlift board
|
||||
- `metro-m4`: fixes for UART2
|
||||
- `pinetime-devkit0`: add support for the PineTime dev kit
|
||||
- `x9pro`: add support for this smartwatch
|
||||
- `pca10040-s132v6`: add support for SoftDevice
|
||||
- `pca10056-s140v7`: add support for SoftDevice
|
||||
- `arduino-nano33`: added SPI1 connected to NINA-W102 chip on Arduino Nano 33 IOT
|
||||
|
||||
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**
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to make participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all project spaces, and it also applies when
|
||||
an individual is representing the project or its community in public spaces.
|
||||
Examples of representing a project or community include using an official
|
||||
project e-mail address, posting via an official social media account, or acting
|
||||
as an appointed representative at an online or offline event. Representation of
|
||||
a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
@@ -16,12 +16,16 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||
|
||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||
|
||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||
|
||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||
|
||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
||||
|
||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
|
||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# This is the official list of TinyGo authors for copyright purposes.
|
||||
#
|
||||
# This file is not actively maintained.
|
||||
# To be included, send a change adding the individual or
|
||||
# company who owns a contribution's copyright.
|
||||
#
|
||||
# Names should be added to this file as one of
|
||||
# Organization's name
|
||||
# Individual's name <submission email address>
|
||||
# Individual's name <submission email address> <email2> <emailN>
|
||||
#
|
||||
# Please keep the list sorted.
|
||||
|
||||
Ayke van Laethem <aykevanlaethem@gmail.com>
|
||||
Daniel Esteban <conejo@conejo.me>
|
||||
Loon, LLC.
|
||||
Ron Evans <ron@hybridgroup.com>
|
||||
+33
-43
@@ -1,52 +1,48 @@
|
||||
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
|
||||
FROM golang:latest AS tinygo-base
|
||||
# TinyGo base stage installs Go 1.13, LLVM 9 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-8 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-8-dev libclang-8-dev git
|
||||
apt-get install -y llvm-9-dev libclang-9-dev git
|
||||
|
||||
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||
|
||||
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||
COPY . /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 /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
dep ensure --vendor-only && \
|
||||
go install /go/src/github.com/tinygo-org/tinygo/
|
||||
RUN cd /tinygo/ && \
|
||||
go install /tinygo/
|
||||
|
||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||
FROM tinygo-base AS tinygo-wasm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y libllvm8 lld-8
|
||||
apt-get install -y libllvm9 lld-9
|
||||
|
||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
@@ -54,33 +50,27 @@ RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
FROM tinygo-base AS tinygo-arm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-8 && \
|
||||
make gen-device-nrf && make gen-device-stm32 && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
apt-get install -y apt-utils make clang-9 && \
|
||||
make gen-device-nrf && make gen-device-stm32
|
||||
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device && \
|
||||
apt-get remove -y python3 make && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device
|
||||
|
||||
CMD ["tinygo"]
|
||||
|
||||
Generated
-51
@@ -1,51 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||
name = "github.com/blakesmith/ar"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||
name = "github.com/marcinbor85/gohex"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||
name = "golang.org/x/tools"
|
||||
packages = [
|
||||
"go/ast/astutil",
|
||||
"go/ssa",
|
||||
"go/types/typeutil",
|
||||
]
|
||||
pruneopts = "UT"
|
||||
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||
|
||||
[[projects]]
|
||||
branch = "llvm8"
|
||||
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
packages = ["."]
|
||||
pruneopts = "UT"
|
||||
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/blakesmith/ar",
|
||||
"github.com/marcinbor85/gohex",
|
||||
"golang.org/x/tools/go/ast/astutil",
|
||||
"golang.org/x/tools/go/ssa",
|
||||
"tinygo.org/x/go-llvm",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
[[constraint]]
|
||||
branch = "llvm8"
|
||||
name = "tinygo.org/x/go-llvm"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/tools"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -3,6 +3,9 @@ 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.
|
||||
|
||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
@@ -1,90 +1,263 @@
|
||||
|
||||
# aliases
|
||||
all: tinygo
|
||||
tinygo: build/tinygo
|
||||
|
||||
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm/tools/clang
|
||||
LLD_SRC ?= llvm/tools/lld
|
||||
CLANG_SRC ?= llvm-project/clang
|
||||
LLD_SRC ?= llvm-project/lld
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
export GOROOT = $(shell $(GO) env GOROOT)
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Linux)
|
||||
# md5sum binary
|
||||
MD5SUM = md5sum
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
|
||||
# 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
|
||||
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 coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
|
||||
# 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
|
||||
|
||||
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 ifeq ($(shell uname -s),FreeBSD)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
else
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
CLANG_LIBS = $(START_GROUP) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
|
||||
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.
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++11
|
||||
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -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
|
||||
FMT_PATHS = ./*.go builder 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:
|
||||
@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
|
||||
|
||||
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
|
||||
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
||||
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
||||
./build/gen-device-avr 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
|
||||
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||
$(GO) build -o $@ ./tools/gen-device-svd/
|
||||
|
||||
gen-device-sam:
|
||||
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||
go fmt ./src/device/sam
|
||||
gen-device-nrf: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||
|
||||
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
|
||||
gen-device-sam: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||
|
||||
gen-device-sifive: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||
|
||||
gen-device-stm32: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
llvm/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||
llvm/tools/clang/README.txt:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||
llvm/tools/lld/README.md:
|
||||
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||
llvm-project/README.md:
|
||||
git clone -b release/9.x https://github.com/llvm/llvm-project
|
||||
llvm-source: llvm-project/README.md
|
||||
|
||||
# Configure LLVM.
|
||||
llvm-build/build.ninja: llvm-source
|
||||
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||
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;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -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-build: llvm-build/build.ninja
|
||||
cd llvm-build; ninja
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
build/tinygo:
|
||||
@if [ ! -f llvm-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go build -o build/tinygo -tags byollvm .
|
||||
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 .
|
||||
|
||||
test:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||
|
||||
release: build/tinygo gen-device
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && tinygo test
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
$(TINYGO) version
|
||||
# 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/systick
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||
@$(MD5SUM) test.hex
|
||||
# test simulated boards on play.tinygo.org
|
||||
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
# test all targets/boards
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(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=feather-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 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
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(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
|
||||
@@ -94,7 +267,7 @@ release: build/tinygo gen-device
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@echo copying source files
|
||||
@cp -p build/tinygo build/release/tinygo/bin
|
||||
@cp -p 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
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
|
||||
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
||||
|
||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
led := machine.GPIO{machine.LED}
|
||||
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||
led := machine.LED
|
||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
for {
|
||||
led.Low()
|
||||
time.Sleep(time.Millisecond * 1000)
|
||||
@@ -43,19 +43,32 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following microcontroller boards are currently supported:
|
||||
The following 22 microcontroller boards are currently supported:
|
||||
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [BBC:Microbit](https://microbit.org/)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [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)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||
* [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)
|
||||
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
@@ -114,3 +127,5 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
|
||||
## License
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||
# be built anyway.
|
||||
trigger:
|
||||
- master
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 240 # 4h
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
- checkout: self
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-9-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-9-windows-v0
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
choco install ninja
|
||||
# LLVM 9 cannot be built with MinGW 8.
|
||||
# For details: https://reviews.llvm.org/D70266
|
||||
choco uninstall mingw
|
||||
choco install mingw --version=7.3.0
|
||||
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="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make test
|
||||
- task: Bash@3
|
||||
displayName: Build TinyGo release tarball
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
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="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make smoketest TINYGO=build/tinygo AVR=0
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
)
|
||||
|
||||
// makeArchive creates an arcive for static linking from a list of object files
|
||||
// given as a parameter. It is equivalent to the following command:
|
||||
//
|
||||
// ar -rcs <archivePath> <objs...>
|
||||
func makeArchive(archivePath string, objs []string) error {
|
||||
// Open the archive file.
|
||||
arfile, err := os.Create(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer arfile.Close()
|
||||
arwriter := ar.NewWriter(arfile)
|
||||
err = arwriter.WriteGlobalHeader()
|
||||
if err != nil {
|
||||
return &os.PathError{"write ar header", archivePath, err}
|
||||
}
|
||||
|
||||
// Open all object files and read the symbols for the symbol table.
|
||||
symbolTable := []struct {
|
||||
name string // symbol name
|
||||
fileIndex int // index into objfiles
|
||||
}{}
|
||||
objfiles := make([]struct {
|
||||
file *os.File
|
||||
archiveOffset int32
|
||||
}, len(objs))
|
||||
for i, objpath := range objs {
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objfiles[i].file = objfile
|
||||
|
||||
// Read the symbols and add them to the symbol table.
|
||||
dbg, err := elf.NewFile(objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
symbols, err := dbg.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, symbol := range symbols {
|
||||
bind := elf.ST_BIND(symbol.Info)
|
||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||
// Don't include local symbols (STB_LOCAL).
|
||||
continue
|
||||
}
|
||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
|
||||
// Not a function.
|
||||
// TODO: perhaps globals variables should also be included?
|
||||
continue
|
||||
}
|
||||
// Include in archive.
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
}
|
||||
}
|
||||
|
||||
// Create the symbol table buffer.
|
||||
// For some (sparse) details on the file format:
|
||||
// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
|
||||
buf := &bytes.Buffer{}
|
||||
binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
|
||||
for range symbolTable {
|
||||
// This is a placeholder index, it will be updated after all files have
|
||||
// been written to the archive (see the end of this function).
|
||||
err = binary.Write(buf, binary.BigEndian, int32(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, sym := range symbolTable {
|
||||
_, err := buf.Write([]byte(sym.name + "\x00"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for buf.Len()%2 != 0 {
|
||||
// The symbol table must be aligned.
|
||||
// This appears to be required by lld.
|
||||
buf.WriteByte(0)
|
||||
}
|
||||
|
||||
// Write the symbol table.
|
||||
err = arwriter.WriteHeader(&ar.Header{
|
||||
Name: "/",
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Mode: 0,
|
||||
Size: int64(buf.Len()),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep track of the start of the symbol table.
|
||||
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write symbol table contents.
|
||||
_, err = arfile.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add all object files to the archive.
|
||||
for i, objfile := range objfiles {
|
||||
// Store the start index, for when we'll update the symbol table with
|
||||
// the correct file start indices.
|
||||
offset, err := arfile.Seek(0, os.SEEK_CUR)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if int64(int32(offset)) != offset {
|
||||
return errors.New("large archives (4GB+) not supported: " + archivePath)
|
||||
}
|
||||
objfiles[i].archiveOffset = int32(offset)
|
||||
|
||||
// Write the file header.
|
||||
st, err := objfile.file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = arwriter.WriteHeader(&ar.Header{
|
||||
Name: filepath.Base(objfile.file.Name()),
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Mode: 0644,
|
||||
Size: st.Size(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the file contents into the archive.
|
||||
n, err := io.Copy(arwriter, objfile.file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + archivePath)
|
||||
}
|
||||
|
||||
// File is not needed anymore.
|
||||
objfile.file.Close()
|
||||
}
|
||||
|
||||
// Create symbol indices.
|
||||
indicesBuf := &bytes.Buffer{}
|
||||
for _, sym := range symbolTable {
|
||||
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite placeholder indices.
|
||||
_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
// Package builder is the compiler driver of TinyGo. It takes in a package name
|
||||
// and an output path, and outputs an executable. It manages the entire
|
||||
// compilation pipeline in between.
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/interp"
|
||||
)
|
||||
|
||||
// Build performs a single package to executable Go build. It takes in a package
|
||||
// name, an output path, and set of compile options and from that it manages the
|
||||
// whole compilation process.
|
||||
//
|
||||
// The error value may be of type *MultiError. Callers will likely want to check
|
||||
// for this case and print such errors individually.
|
||||
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
|
||||
c, err := compiler.NewCompiler(pkgName, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compile Go code to IR.
|
||||
errs := c.Compile(pkgName)
|
||||
if len(errs) != 0 {
|
||||
return newMultiError(errs)
|
||||
}
|
||||
if config.Options.PrintIR {
|
||||
fmt.Println("; Generated LLVM IR:")
|
||||
fmt.Println(c.IR())
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after IR construction")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if config.GOOS() != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
}
|
||||
|
||||
// Browsers cannot handle external functions that have type i64 because it
|
||||
// cannot be represented exactly in JavaScript (JS only has doubles). To
|
||||
// keep functions interoperable, pass int64 types as pointers to
|
||||
// stack-allocated values.
|
||||
// Use -wasm-abi=generic to disable this behaviour.
|
||||
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
|
||||
err := c.ExternalInt64AsPtr()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Optimization levels here are roughly the same as Clang, but probably not
|
||||
// exactly.
|
||||
errs = nil
|
||||
switch config.Options.Opt {
|
||||
case "none:", "0":
|
||||
errs = c.Optimize(0, 0, 0) // -O0
|
||||
case "1":
|
||||
errs = c.Optimize(1, 0, 0) // -O1
|
||||
case "2":
|
||||
errs = c.Optimize(2, 0, 225) // -O2
|
||||
case "s":
|
||||
errs = c.Optimize(2, 1, 225) // -Os
|
||||
case "z":
|
||||
errs = c.Optimize(2, 2, 5) // -Oz, default
|
||||
default:
|
||||
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return newMultiError(errs)
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification failure after LLVM optimization passes")
|
||||
}
|
||||
|
||||
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
||||
// know. As a temporary fix, load all global variables in RAM.
|
||||
// In the future, there should be a compiler pass that determines which
|
||||
// pointers are flash and which are in RAM so that pointers can have a
|
||||
// correct address space parameter (address space 1 is for flash).
|
||||
if strings.HasPrefix(config.Triple(), "avr") {
|
||||
c.NonConstGlobals()
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after making all globals non-constant on AVR")
|
||||
}
|
||||
}
|
||||
|
||||
// Generate output.
|
||||
outext := filepath.Ext(outpath)
|
||||
switch outext {
|
||||
case ".o":
|
||||
return c.EmitObject(outpath)
|
||||
case ".bc":
|
||||
return c.EmitBitcode(outpath)
|
||||
case ".ll":
|
||||
return c.EmitText(outpath)
|
||||
default:
|
||||
// Act as a compiler driver.
|
||||
|
||||
// Create a temporary directory for intermediary files.
|
||||
dir, err := ioutil.TempDir("", "tinygo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Write the object file.
|
||||
objfile := filepath.Join(dir, "main.o")
|
||||
err = c.EmitObject(objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load builtins library from the cache, possibly compiling it on the
|
||||
// fly.
|
||||
var librt string
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
librt, err = loadBuiltins(config.Triple())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare link command.
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
for i, path := range config.ExtraFiles() {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
|
||||
// Compile C files in packages.
|
||||
for i, pkg := range c.Packages() {
|
||||
for _, file := range pkg.CFiles {
|
||||
path := filepath.Join(pkg.Package.Dir, file)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = link(config.Target.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||
sizes, err := loadProgramSize(executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Options.PrintSizes == "short" {
|
||||
fmt.Printf(" code data bss | flash ram\n")
|
||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
} else {
|
||||
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
||||
for _, name := range sizes.sortedPackageNames() {
|
||||
pkgSize := sizes.Packages[name]
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
||||
}
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
||||
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
return action(tmppath)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,13 @@
|
||||
package main
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Get the cache directory, usually ~/.cache/tinygo
|
||||
func cacheDir() string {
|
||||
dir, err := os.UserCacheDir()
|
||||
if err != nil {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
}
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
// for stale caches.
|
||||
@@ -41,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
|
||||
@@ -76,7 +68,7 @@ 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
|
||||
@@ -117,10 +109,10 @@ func moveFile(src, dst string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Rename(dst+".tmp", dst)
|
||||
err = outf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return outf.Close()
|
||||
return os.Rename(dst+".tmp", dst)
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
package main
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/blakesmith/ar"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -169,13 +166,13 @@ func builtinFiles(target string) []string {
|
||||
|
||||
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||
func builtinsDir() string {
|
||||
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||
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(sourceDir(), "pkg", target, "compiler-rt.a")
|
||||
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.
|
||||
@@ -196,7 +193,7 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
}
|
||||
|
||||
var cachepath string
|
||||
err = compileBuiltins(target, func(path string) error {
|
||||
err = CompileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
@@ -204,11 +201,11 @@ func loadBuiltins(target string) (path string, err error) {
|
||||
return cachepath, err
|
||||
}
|
||||
|
||||
// compileBuiltins compiles builtins from compiler-rt into a static library.
|
||||
// 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 {
|
||||
func CompileBuiltins(target string, callback func(path string) error) error {
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
@@ -239,56 +236,25 @@ func compileBuiltins(target string, callback func(path string) error) error {
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the
|
||||
// archive itself, which varies each run.
|
||||
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)
|
||||
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
|
||||
if strings.HasPrefix(target, "riscv32-") {
|
||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||
}
|
||||
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
}
|
||||
|
||||
// Put all builtins in an archive to link as a static library.
|
||||
// Note: this does not create a symbol index, but ld.lld doesn't seem to
|
||||
// care.
|
||||
// Put all the object files in a single archive. This archive file will be
|
||||
// used to statically link compiler-rt.
|
||||
arpath := filepath.Join(dir, "librt.a")
|
||||
arfile, err := os.Create(arpath)
|
||||
err = makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer arfile.Close()
|
||||
arwriter := ar.NewWriter(arfile)
|
||||
err = arwriter.WriteGlobalHeader()
|
||||
if err != nil {
|
||||
return &os.PathError{"write ar header", arpath, err}
|
||||
}
|
||||
for _, objpath := range objs {
|
||||
name := filepath.Base(objpath)
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer objfile.Close()
|
||||
st, err := objfile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
arwriter.WriteHeader(&ar.Header{
|
||||
Name: name,
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
Mode: 0644,
|
||||
Size: st.Size(),
|
||||
})
|
||||
n, err := io.Copy(arwriter, objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + arpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Give the caller the resulting file. The callback must copy the file,
|
||||
// because after it returns the temporary directory will be removed.
|
||||
arfile.Close()
|
||||
return callback(arpath)
|
||||
}
|
||||
@@ -0,0 +1,504 @@
|
||||
// +build byollvm
|
||||
|
||||
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This is the entry point to the clang -cc1as functionality, which implements
|
||||
// the direct interface to the LLVM MC based assembler.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/DiagnosticOptions.h"
|
||||
#include "clang/Driver/DriverDiagnostic.h"
|
||||
#include "clang/Driver/Options.h"
|
||||
#include "clang/Frontend/FrontendDiagnostic.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "clang/Frontend/Utils.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/ADT/StringSwitch.h"
|
||||
#include "llvm/ADT/Triple.h"
|
||||
#include "llvm/IR/DataLayout.h"
|
||||
#include "llvm/MC/MCAsmBackend.h"
|
||||
#include "llvm/MC/MCAsmInfo.h"
|
||||
#include "llvm/MC/MCCodeEmitter.h"
|
||||
#include "llvm/MC/MCContext.h"
|
||||
#include "llvm/MC/MCInstrInfo.h"
|
||||
#include "llvm/MC/MCObjectFileInfo.h"
|
||||
#include "llvm/MC/MCObjectWriter.h"
|
||||
#include "llvm/MC/MCParser/MCAsmParser.h"
|
||||
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
|
||||
#include "llvm/MC/MCRegisterInfo.h"
|
||||
#include "llvm/MC/MCSectionMachO.h"
|
||||
#include "llvm/MC/MCStreamer.h"
|
||||
#include "llvm/MC/MCSubtargetInfo.h"
|
||||
#include "llvm/MC/MCTargetOptions.h"
|
||||
#include "llvm/Option/Arg.h"
|
||||
#include "llvm/Option/ArgList.h"
|
||||
#include "llvm/Option/OptTable.h"
|
||||
#include "llvm/Support/CommandLine.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
#include "llvm/Support/FileSystem.h"
|
||||
#include "llvm/Support/FormattedStream.h"
|
||||
#include "llvm/Support/Host.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetRegistry.h"
|
||||
#include "llvm/Support/TargetSelect.h"
|
||||
#include "llvm/Support/Timer.h"
|
||||
#include "llvm/Support/raw_ostream.h"
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
using namespace clang;
|
||||
using namespace clang::driver;
|
||||
using namespace clang::driver::options;
|
||||
using namespace llvm;
|
||||
using namespace llvm::opt;
|
||||
|
||||
#include "cc1as.h"
|
||||
|
||||
bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
ArrayRef<const char *> Argv,
|
||||
DiagnosticsEngine &Diags) {
|
||||
bool Success = true;
|
||||
|
||||
// Parse the arguments.
|
||||
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
|
||||
|
||||
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
|
||||
unsigned MissingArgIndex, MissingArgCount;
|
||||
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
||||
IncludedFlagsBitmask);
|
||||
|
||||
// Check for missing argument error.
|
||||
if (MissingArgCount) {
|
||||
Diags.Report(diag::err_drv_missing_argument)
|
||||
<< Args.getArgString(MissingArgIndex) << MissingArgCount;
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// Issue errors on unknown arguments.
|
||||
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
||||
auto ArgString = A->getAsString(Args);
|
||||
std::string Nearest;
|
||||
if (OptTbl->findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
||||
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
||||
else
|
||||
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
||||
<< ArgString << Nearest;
|
||||
Success = false;
|
||||
}
|
||||
|
||||
// Construct the invocation.
|
||||
|
||||
// Target Options
|
||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
|
||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||
|
||||
// Use the default target triple if unspecified.
|
||||
if (Opts.Triple.empty())
|
||||
Opts.Triple = llvm::sys::getDefaultTargetTriple();
|
||||
|
||||
// Language Options
|
||||
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
|
||||
Opts.NoInitialTextSection = Args.hasArg(OPT_n);
|
||||
Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels);
|
||||
// Any DebugInfoKind implies GenDwarfForAssembly.
|
||||
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
||||
|
||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
|
||||
OPT_compress_debug_sections_EQ)) {
|
||||
if (A->getOption().getID() == OPT_compress_debug_sections) {
|
||||
// TODO: be more clever about the compression type auto-detection
|
||||
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
|
||||
} else {
|
||||
Opts.CompressDebugSections =
|
||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
||||
.Case("none", llvm::DebugCompressionType::None)
|
||||
.Case("zlib", llvm::DebugCompressionType::Z)
|
||||
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
||||
.Default(llvm::DebugCompressionType::None);
|
||||
}
|
||||
}
|
||||
|
||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
||||
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
|
||||
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
|
||||
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
|
||||
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
|
||||
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
|
||||
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
|
||||
|
||||
// Frontend Options
|
||||
if (Args.hasArg(OPT_INPUT)) {
|
||||
bool First = true;
|
||||
for (const Arg *A : Args.filtered(OPT_INPUT)) {
|
||||
if (First) {
|
||||
Opts.InputFile = A->getValue();
|
||||
First = false;
|
||||
} else {
|
||||
Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args);
|
||||
Success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
||||
Opts.OutputPath = Args.getLastArgValue(OPT_o);
|
||||
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
|
||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
||||
StringRef Name = A->getValue();
|
||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
||||
.Case("asm", FT_Asm)
|
||||
.Case("null", FT_Null)
|
||||
.Case("obj", FT_Obj)
|
||||
.Default(~0U);
|
||||
if (OutputType == ~0U) {
|
||||
Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
|
||||
Success = false;
|
||||
} else
|
||||
Opts.OutputType = FileType(OutputType);
|
||||
}
|
||||
Opts.ShowHelp = Args.hasArg(OPT_help);
|
||||
Opts.ShowVersion = Args.hasArg(OPT_version);
|
||||
|
||||
// Transliterate Options
|
||||
Opts.OutputAsmVariant =
|
||||
getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags);
|
||||
Opts.ShowEncoding = Args.hasArg(OPT_show_encoding);
|
||||
Opts.ShowInst = Args.hasArg(OPT_show_inst);
|
||||
|
||||
// Assemble Options
|
||||
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
|
||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
||||
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
|
||||
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
|
||||
Opts.IncrementalLinkerCompatible =
|
||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
||||
|
||||
// EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag.
|
||||
// EmbedBitcode behaves the same for all embed options for assembly files.
|
||||
if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) {
|
||||
Opts.EmbedBitcode = llvm::StringSwitch<unsigned>(A->getValue())
|
||||
.Case("all", 1)
|
||||
.Case("bitcode", 1)
|
||||
.Case("marker", 1)
|
||||
.Default(0);
|
||||
}
|
||||
|
||||
return Success;
|
||||
}
|
||||
|
||||
static std::unique_ptr<raw_fd_ostream>
|
||||
getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
||||
// Make sure that the Out file gets unlinked from the disk if we get a
|
||||
// SIGINT.
|
||||
if (Path != "-")
|
||||
sys::RemoveFileOnSignal(Path);
|
||||
|
||||
std::error_code EC;
|
||||
auto Out = llvm::make_unique<raw_fd_ostream>(
|
||||
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
|
||||
if (EC) {
|
||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return Out;
|
||||
}
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
// Get the target specific parser.
|
||||
std::string Error;
|
||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
||||
if (!TheTarget)
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
|
||||
|
||||
if (std::error_code EC = Buffer.getError()) {
|
||||
Error = EC.message();
|
||||
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
|
||||
}
|
||||
|
||||
SourceMgr SrcMgr;
|
||||
|
||||
// Tell SrcMgr about this buffer, which is what the parser will pick up.
|
||||
unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
|
||||
|
||||
// Record the location of the include directories so that the lexer can find
|
||||
// it later.
|
||||
SrcMgr.setIncludeDirs(Opts.IncludePaths);
|
||||
|
||||
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
|
||||
assert(MRI && "Unable to create target register info!");
|
||||
|
||||
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
|
||||
assert(MAI && "Unable to create target asm info!");
|
||||
|
||||
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
|
||||
// may be created with a combination of default and explicit settings.
|
||||
MAI->setCompressDebugSections(Opts.CompressDebugSections);
|
||||
|
||||
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
|
||||
|
||||
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
|
||||
if (Opts.OutputPath.empty())
|
||||
Opts.OutputPath = "-";
|
||||
std::unique_ptr<raw_fd_ostream> FDOS =
|
||||
getOutputStream(Opts.OutputPath, Diags, IsBinary);
|
||||
if (!FDOS)
|
||||
return true;
|
||||
std::unique_ptr<raw_fd_ostream> DwoOS;
|
||||
if (!Opts.SplitDwarfOutput.empty())
|
||||
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
||||
|
||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
||||
|
||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
|
||||
|
||||
bool PIC = false;
|
||||
if (Opts.RelocationModel == "static") {
|
||||
PIC = false;
|
||||
} else if (Opts.RelocationModel == "pic") {
|
||||
PIC = true;
|
||||
} else {
|
||||
assert(Opts.RelocationModel == "dynamic-no-pic" &&
|
||||
"Invalid PIC model!");
|
||||
PIC = false;
|
||||
}
|
||||
|
||||
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
|
||||
if (Opts.SaveTemporaryLabels)
|
||||
Ctx.setAllowTemporaryLabels(false);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
Ctx.setGenDwarfForAssembly(true);
|
||||
if (!Opts.DwarfDebugFlags.empty())
|
||||
Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags));
|
||||
if (!Opts.DwarfDebugProducer.empty())
|
||||
Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer));
|
||||
if (!Opts.DebugCompilationDir.empty())
|
||||
Ctx.setCompilationDir(Opts.DebugCompilationDir);
|
||||
else {
|
||||
// If no compilation dir is set, try to use the current directory.
|
||||
SmallString<128> CWD;
|
||||
if (!sys::fs::current_path(CWD))
|
||||
Ctx.setCompilationDir(CWD);
|
||||
}
|
||||
if (!Opts.DebugPrefixMap.empty())
|
||||
for (const auto &KV : Opts.DebugPrefixMap)
|
||||
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
||||
if (!Opts.MainFileName.empty())
|
||||
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
||||
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||
|
||||
// Build up the feature string from the target feature list.
|
||||
std::string FS;
|
||||
if (!Opts.Features.empty()) {
|
||||
FS = Opts.Features[0];
|
||||
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
|
||||
FS += "," + Opts.Features[i];
|
||||
}
|
||||
|
||||
std::unique_ptr<MCStreamer> Str;
|
||||
|
||||
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
||||
std::unique_ptr<MCSubtargetInfo> STI(
|
||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||
|
||||
raw_pwrite_stream *Out = FDOS.get();
|
||||
std::unique_ptr<buffer_ostream> BOS;
|
||||
|
||||
MCTargetOptions MCOptions;
|
||||
MCOptions.ABIName = Opts.TargetABI;
|
||||
|
||||
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
|
||||
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
|
||||
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
|
||||
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> CE;
|
||||
if (Opts.ShowEncoding)
|
||||
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
|
||||
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
|
||||
Str.reset(TheTarget->createAsmStreamer(
|
||||
Ctx, std::move(FOut), /*asmverbose*/ true,
|
||||
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
|
||||
Opts.ShowInst));
|
||||
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
|
||||
Str.reset(createNullStreamer(Ctx));
|
||||
} else {
|
||||
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
|
||||
"Invalid file type!");
|
||||
if (!FDOS->supportsSeeking()) {
|
||||
BOS = make_unique<buffer_ostream>(*FDOS);
|
||||
Out = BOS.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<MCCodeEmitter> CE(
|
||||
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
std::unique_ptr<MCObjectWriter> OW =
|
||||
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
|
||||
: MAB->createObjectWriter(*Out);
|
||||
|
||||
Triple T(Opts.Triple);
|
||||
Str.reset(TheTarget->createMCObjectStreamer(
|
||||
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
|
||||
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
|
||||
/*DWARFMustBeAtTheEnd*/ true));
|
||||
Str.get()->InitSections(Opts.NoExecStack);
|
||||
}
|
||||
|
||||
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
|
||||
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
||||
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
|
||||
MCObjectFileInfo::IsMachO) {
|
||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||
Str.get()->SwitchSection(AsmLabel);
|
||||
Str.get()->EmitZeros(1);
|
||||
}
|
||||
|
||||
// Assembly to object compilation should leverage assembly info.
|
||||
Str->setUseAssemblerInfoForParsing(true);
|
||||
|
||||
bool Failed = false;
|
||||
|
||||
std::unique_ptr<MCAsmParser> Parser(
|
||||
createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
|
||||
|
||||
// FIXME: init MCTargetOptions from sanitizer flags here.
|
||||
std::unique_ptr<MCTargetAsmParser> TAP(
|
||||
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
|
||||
if (!TAP)
|
||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
// Set values for symbols, if any.
|
||||
for (auto &S : Opts.SymbolDefs) {
|
||||
auto Pair = StringRef(S).split('=');
|
||||
auto Sym = Pair.first;
|
||||
auto Val = Pair.second;
|
||||
int64_t Value;
|
||||
// We have already error checked this in the driver.
|
||||
Val.getAsInteger(0, Value);
|
||||
Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value);
|
||||
}
|
||||
|
||||
if (!Failed) {
|
||||
Parser->setTargetParser(*TAP.get());
|
||||
Failed = Parser->Run(Opts.NoInitialTextSection);
|
||||
}
|
||||
|
||||
// Close Streamer first.
|
||||
// It might have a reference to the output stream.
|
||||
Str.reset();
|
||||
// Close the output stream early.
|
||||
BOS.reset();
|
||||
FDOS.reset();
|
||||
|
||||
// Delete output file if there were errors.
|
||||
if (Failed) {
|
||||
if (Opts.OutputPath != "-")
|
||||
sys::fs::remove(Opts.OutputPath);
|
||||
if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-")
|
||||
sys::fs::remove(Opts.SplitDwarfOutput);
|
||||
}
|
||||
|
||||
return Failed;
|
||||
}
|
||||
|
||||
static void LLVMErrorHandler(void *UserData, const std::string &Message,
|
||||
bool GenCrashDiag) {
|
||||
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
|
||||
|
||||
Diags.Report(diag::err_fe_error_backend) << Message;
|
||||
|
||||
// We cannot recover from llvm errors.
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
// Initialize targets and assembly printers/parsers.
|
||||
InitializeAllTargetInfos();
|
||||
InitializeAllTargetMCs();
|
||||
InitializeAllAsmParsers();
|
||||
|
||||
// Construct our diagnostic client.
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
TextDiagnosticPrinter *DiagClient
|
||||
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
|
||||
DiagClient->setPrefix("clang -cc1as");
|
||||
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
|
||||
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
|
||||
|
||||
// Set an error handler, so that any LLVM backend diagnostics go through our
|
||||
// error handler.
|
||||
ScopedFatalErrorHandler FatalErrorHandler
|
||||
(LLVMErrorHandler, static_cast<void*>(&Diags));
|
||||
|
||||
// Parse the arguments.
|
||||
AssemblerInvocation Asm;
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags))
|
||||
return 1;
|
||||
|
||||
if (Asm.ShowHelp) {
|
||||
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
|
||||
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler",
|
||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||
/*ShowAllAliases=*/false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Honor -version.
|
||||
//
|
||||
// FIXME: Use a better -version message?
|
||||
if (Asm.ShowVersion) {
|
||||
llvm::cl::PrintVersionMessage();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Honor -mllvm.
|
||||
//
|
||||
// FIXME: Remove this, one day.
|
||||
if (!Asm.LLVMArgs.empty()) {
|
||||
unsigned NumArgs = Asm.LLVMArgs.size();
|
||||
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
|
||||
Args[0] = "clang (LLVM option parsing)";
|
||||
for (unsigned i = 0; i != NumArgs; ++i)
|
||||
Args[i + 1] = Asm.LLVMArgs[i].c_str();
|
||||
Args[NumArgs + 1] = nullptr;
|
||||
llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get());
|
||||
}
|
||||
|
||||
// Execute the invocation, unless there were parsing errors.
|
||||
bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
||||
|
||||
// If any timers were active but haven't been destroyed yet, print their
|
||||
// results now.
|
||||
TimerGroup::printAll(errs());
|
||||
|
||||
return !!Failed;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
|
||||
//
|
||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This is the entry point to the clang -cc1as functionality, which implements
|
||||
// the direct interface to the LLVM MC based assembler.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
/// Helper class for representing a single invocation of the assembler.
|
||||
struct AssemblerInvocation {
|
||||
/// @name Target Options
|
||||
/// @{
|
||||
|
||||
/// The name of the target triple to assemble for.
|
||||
std::string Triple;
|
||||
|
||||
/// If given, the name of the target CPU to determine which instructions
|
||||
/// are legal.
|
||||
std::string CPU;
|
||||
|
||||
/// The list of target specific features to enable or disable -- this should
|
||||
/// be a list of strings starting with '+' or '-'.
|
||||
std::vector<std::string> Features;
|
||||
|
||||
/// The list of symbol definitions.
|
||||
std::vector<std::string> SymbolDefs;
|
||||
|
||||
/// @}
|
||||
/// @name Language Options
|
||||
/// @{
|
||||
|
||||
std::vector<std::string> IncludePaths;
|
||||
unsigned NoInitialTextSection : 1;
|
||||
unsigned SaveTemporaryLabels : 1;
|
||||
unsigned GenDwarfForAssembly : 1;
|
||||
unsigned RelaxELFRelocations : 1;
|
||||
unsigned DwarfVersion;
|
||||
std::string DwarfDebugFlags;
|
||||
std::string DwarfDebugProducer;
|
||||
std::string DebugCompilationDir;
|
||||
std::map<const std::string, const std::string> DebugPrefixMap;
|
||||
llvm::DebugCompressionType CompressDebugSections =
|
||||
llvm::DebugCompressionType::None;
|
||||
std::string MainFileName;
|
||||
std::string SplitDwarfOutput;
|
||||
|
||||
/// @}
|
||||
/// @name Frontend Options
|
||||
/// @{
|
||||
|
||||
std::string InputFile;
|
||||
std::vector<std::string> LLVMArgs;
|
||||
std::string OutputPath;
|
||||
enum FileType {
|
||||
FT_Asm, ///< Assembly (.s) output, transliterate mode.
|
||||
FT_Null, ///< No output, for timing purposes.
|
||||
FT_Obj ///< Object file output.
|
||||
};
|
||||
FileType OutputType;
|
||||
unsigned ShowHelp : 1;
|
||||
unsigned ShowVersion : 1;
|
||||
|
||||
/// @}
|
||||
/// @name Transliterate Options
|
||||
/// @{
|
||||
|
||||
unsigned OutputAsmVariant;
|
||||
unsigned ShowEncoding : 1;
|
||||
unsigned ShowInst : 1;
|
||||
|
||||
/// @}
|
||||
/// @name Assembler Options
|
||||
/// @{
|
||||
|
||||
unsigned RelaxAll : 1;
|
||||
unsigned NoExecStack : 1;
|
||||
unsigned FatalWarnings : 1;
|
||||
unsigned IncrementalLinkerCompatible : 1;
|
||||
unsigned EmbedBitcode : 1;
|
||||
|
||||
/// The name of the relocation model to use.
|
||||
std::string RelocationModel;
|
||||
|
||||
/// The ABI targeted by the backend. Specified using -target-abi. Empty
|
||||
/// otherwise.
|
||||
std::string TargetABI;
|
||||
|
||||
/// @}
|
||||
|
||||
public:
|
||||
AssemblerInvocation() {
|
||||
Triple = "";
|
||||
NoInitialTextSection = 0;
|
||||
InputFile = "-";
|
||||
OutputPath = "-";
|
||||
OutputType = FT_Asm;
|
||||
OutputAsmVariant = 0;
|
||||
ShowInst = 0;
|
||||
ShowEncoding = 0;
|
||||
RelaxAll = 0;
|
||||
NoExecStack = 0;
|
||||
FatalWarnings = 0;
|
||||
IncrementalLinkerCompatible = 0;
|
||||
DwarfVersion = 0;
|
||||
EmbedBitcode = 0;
|
||||
}
|
||||
|
||||
static bool CreateFromArgs(AssemblerInvocation &Res,
|
||||
ArrayRef<const char *> Argv,
|
||||
DiagnosticsEngine &Diags);
|
||||
};
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags);
|
||||
@@ -0,0 +1,97 @@
|
||||
// +build byollvm
|
||||
|
||||
#include <clang/Basic/DiagnosticOptions.h>
|
||||
#include <clang/CodeGen/CodeGenAction.h>
|
||||
#include <clang/Driver/Compilation.h>
|
||||
#include <clang/Driver/Driver.h>
|
||||
#include <clang/Frontend/CompilerInstance.h>
|
||||
#include <clang/Frontend/CompilerInvocation.h>
|
||||
#include <clang/Frontend/FrontendDiagnostic.h>
|
||||
#include <clang/Frontend/TextDiagnosticPrinter.h>
|
||||
#include <clang/FrontendTool/Utils.h>
|
||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
||||
#include <llvm/Option/Option.h>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
|
||||
#include "cc1as.h"
|
||||
|
||||
// This file provides C wrappers for the builtin tools cc1 and cc1as
|
||||
// provided by Clang, and calls them as the driver would call them.
|
||||
|
||||
extern "C" {
|
||||
|
||||
bool tinygo_clang_driver(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
|
||||
// The compiler invocation needs a DiagnosticsEngine so it can report problems
|
||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
|
||||
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
|
||||
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
|
||||
|
||||
// Create the clang driver
|
||||
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
|
||||
|
||||
// Create the set of actions to perform
|
||||
std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));
|
||||
if (!C) {
|
||||
return false;
|
||||
}
|
||||
const clang::driver::JobList &Jobs = C->getJobs();
|
||||
|
||||
// There may be more than one job, for example for .S files
|
||||
// (preprocessor + assembler).
|
||||
for (auto Cmd : Jobs) {
|
||||
// Select the tool: cc1 or cc1as.
|
||||
const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments();
|
||||
|
||||
if (strcmp(*CCArgs.data(), "-cc1") == 0) {
|
||||
// This is the C frontend.
|
||||
// Initialize a compiler invocation object from the clang (-cc1) arguments.
|
||||
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
|
||||
bool success = clang::CompilerInvocation::CreateFromArgs(
|
||||
Clang->getInvocation(),
|
||||
const_cast<const char **>(CCArgs.data()),
|
||||
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
|
||||
Diags);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the actual diagnostics engine.
|
||||
Clang->createDiagnostics();
|
||||
if (!Clang->hasDiagnostics()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Execute the frontend actions.
|
||||
success = ExecuteCompilerInvocation(Clang.get());
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
|
||||
// This is the assembler frontend. Parse the arguments.
|
||||
AssemblerInvocation Asm;
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
|
||||
return false;
|
||||
|
||||
// Execute the invocation, unless there were parsing errors.
|
||||
bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unknown tool, print the tool and exit.
|
||||
fprintf(stderr, "unknown tool: %s\n", *CCArgs.data());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Commands executed successfully.
|
||||
return true;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,61 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Commands lists command alternatives for various operating systems. These
|
||||
// commands may have a slightly different name across operating systems and
|
||||
// distributions or may not even exist in $PATH, in which case absolute paths
|
||||
// may be used.
|
||||
var commands = map[string][]string{
|
||||
"clang": {"clang-9"},
|
||||
"ld.lld": {"ld.lld-9", "ld.lld"},
|
||||
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
|
||||
// manually set $PATH).
|
||||
if runtime.GOOS == "darwin" {
|
||||
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/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")
|
||||
}
|
||||
// Add the path to the llvm90 installed from ports
|
||||
if runtime.GOOS == "freebsd" {
|
||||
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
|
||||
}
|
||||
}
|
||||
|
||||
func execCommand(cmdNames []string, args ...string) error {
|
||||
for _, cmdName := range cmdNames {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || 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,60 @@
|
||||
// +build byollvm
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -fno-rtti
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version invokes the built-in Clang when trying to run the Clang compiler.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
switch command {
|
||||
case "clang":
|
||||
// Compile this with the internal Clang compiler.
|
||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
if headerPath == "" {
|
||||
return errors.New("could not locate Clang headers")
|
||||
}
|
||||
flags = append(flags, "-I"+headerPath)
|
||||
flags = append([]string{"tinygo:" + command}, 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_clang_driver(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to compile using built-in clang")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Running some other compiler. Maybe it has been defined in the
|
||||
// commands map (unlikely).
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
// Alternatively, run the compiler directly.
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// +build !byollvm
|
||||
|
||||
package builder
|
||||
|
||||
// This file provides a way to a C compiler as an external command. See also:
|
||||
// clang-external.go
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version always runs the compiler as an external command.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// NewConfig builds a new Config object from a set of compiler options. It also
|
||||
// loads some information from the environment while doing that. For example, it
|
||||
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
||||
// version to use.
|
||||
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
spec, err := compileopts.LoadTarget(options.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
goroot := goenv.Get("GOROOT")
|
||||
if goroot == "" {
|
||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
major, minor, err := getGorootVersion(goroot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
|
||||
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
|
||||
}
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
return &compileopts.Config{
|
||||
Options: options,
|
||||
Target: spec,
|
||||
GoMinorVersion: minor,
|
||||
ClangHeaders: clangHeaderPath,
|
||||
TestConfig: options.TestConfig,
|
||||
}, nil
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
func getGorootVersion(goroot string) (major, minor int, err error) {
|
||||
s, err := GorootVersionString(goroot)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if s == "" || s[:2] != "go" {
|
||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||
}
|
||||
|
||||
parts := strings.Split(s[2:], ".")
|
||||
if len(parts) < 2 {
|
||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||
}
|
||||
|
||||
// Ignore the errors, we don't really handle errors here anyway.
|
||||
var trailing string
|
||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||
if n == 2 && err == io.EOF {
|
||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GorootVersionString returns the version string as reported by the Go
|
||||
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
||||
// can have some variations (for beta releases, for example).
|
||||
func GorootVersionString(goroot string) (string, error) {
|
||||
if data, err := ioutil.ReadFile(filepath.Join(
|
||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||
|
||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||
matches := r.FindSubmatch(data)
|
||||
if len(matches) != 2 {
|
||||
return "", errors.New("Invalid go version output:\n" + string(data))
|
||||
}
|
||||
|
||||
return string(matches[1]), nil
|
||||
|
||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||
return string(data), nil
|
||||
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||
// multiple locations, which should make it find the directory when installed in
|
||||
// various ways.
|
||||
func getClangHeaderPath(TINYGOROOT string) string {
|
||||
// Check whether we're running from the source directory.
|
||||
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return path
|
||||
}
|
||||
|
||||
// Check whether we're running from the installation directory.
|
||||
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return path
|
||||
}
|
||||
|
||||
// It looks like we are built with a system-installed LLVM. Do a last
|
||||
// attempt: try to use Clang headers relative to the clang binary.
|
||||
for _, cmdName := range commands["clang"] {
|
||||
binpath, err := exec.LookPath(cmdName)
|
||||
if err == nil {
|
||||
// This should be the command that will also be used by
|
||||
// execCommand. To avoid inconsistencies, make sure we use the
|
||||
// headers relative to this command.
|
||||
binpath, err = filepath.EvalSymlinks(binpath)
|
||||
if err != nil {
|
||||
// Unexpected.
|
||||
return ""
|
||||
}
|
||||
// Example executable:
|
||||
// /usr/lib/llvm-9/bin/clang
|
||||
// Example include path:
|
||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
||||
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
|
||||
dirs, err := ioutil.ReadDir(clangVersionRoot)
|
||||
if err != nil {
|
||||
// Unexpected.
|
||||
continue
|
||||
}
|
||||
dirnames := make([]string, len(dirs))
|
||||
for i, d := range dirs {
|
||||
dirnames[i] = d.Name()
|
||||
}
|
||||
sort.Strings(dirnames)
|
||||
// Check for the highest version first.
|
||||
for i := len(dirnames) - 1; i >= 0; i-- {
|
||||
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
|
||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find it.
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package builder
|
||||
|
||||
// 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 the first error, to conform to the error interface. Clients should
|
||||
// really do a type-assertion on *MultiError.
|
||||
return e.Errs[0].Error()
|
||||
}
|
||||
|
||||
// newMultiError returns a *MultiError if there is more than one error, or
|
||||
// returns that error directly when there is only one. Passing an empty slice
|
||||
// will lead to a panic.
|
||||
func newMultiError(errs []error) error {
|
||||
switch len(errs) {
|
||||
case 0:
|
||||
panic("attempted to create empty MultiError")
|
||||
case 1:
|
||||
return errs[0]
|
||||
default:
|
||||
return &MultiError{errs}
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build byollvm
|
||||
|
||||
package main
|
||||
package builder
|
||||
|
||||
// This file provides a Link() function that uses the bundled lld if possible.
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -19,10 +21,10 @@ bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Link invokes a linker with the given name and flags.
|
||||
// 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 {
|
||||
func link(linker string, flags ...string) error {
|
||||
switch linker {
|
||||
case "ld.lld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
@@ -63,6 +65,7 @@ func Link(linker string, flags ...string) error {
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
// +build !byollvm
|
||||
|
||||
package main
|
||||
package builder
|
||||
|
||||
// This file provides a Link() function that always runs an external command. It
|
||||
// is provided for when tinygo is built without linking to liblld.
|
||||
@@ -8,17 +8,20 @@ package main
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Link invokes a linker with the given name and arguments.
|
||||
// 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 {
|
||||
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()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package builder
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
@@ -10,31 +10,31 @@ import (
|
||||
"github.com/marcinbor85/gohex"
|
||||
)
|
||||
|
||||
// ObjcopyError is an error returned by functions that act like objcopy.
|
||||
type ObjcopyError struct {
|
||||
// objcopyError is an error returned by functions that act like objcopy.
|
||||
type objcopyError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e ObjcopyError) Error() string {
|
||||
func (e objcopyError) Error() string {
|
||||
if e.Err == nil {
|
||||
return e.Op
|
||||
}
|
||||
return e.Op + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
type ProgSlice []*elf.Prog
|
||||
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] }
|
||||
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
|
||||
// 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) {
|
||||
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}
|
||||
return 0, nil, objcopyError{"failed to open ELF file to extract text segment", err}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@@ -56,7 +56,7 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
progs := make(ProgSlice, 0, 2)
|
||||
progs := make(progSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||
continue
|
||||
@@ -64,18 +64,18 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
||||
progs = append(progs, prog)
|
||||
}
|
||||
if len(progs) == 0 {
|
||||
return 0, nil, ObjcopyError{"file does not contain ROM segments: " + path, nil}
|
||||
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}
|
||||
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}
|
||||
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||
}
|
||||
rom = append(rom, data...)
|
||||
}
|
||||
@@ -91,9 +91,9 @@ func ExtractROM(path string) (uint64, []byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Objcopy converts an ELF file to a different (simpler) output file format:
|
||||
// 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 {
|
||||
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
|
||||
@@ -101,13 +101,17 @@ func Objcopy(infile, outfile string) error {
|
||||
defer f.Close()
|
||||
|
||||
// Read the .text segment.
|
||||
addr, data, err := ExtractROM(infile)
|
||||
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).
|
||||
@@ -117,7 +121,7 @@ func Objcopy(infile, outfile string) error {
|
||||
mem := gohex.NewMemory()
|
||||
err := mem.AddBinary(uint32(addr), data)
|
||||
if err != nil {
|
||||
return ObjcopyError{"failed to create .hex file", err}
|
||||
return objcopyError{"failed to create .hex file", err}
|
||||
}
|
||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||
return nil
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package builder
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
@@ -6,18 +6,18 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Statistics about code size in a program.
|
||||
type ProgramSize struct {
|
||||
Packages map[string]*PackageSize
|
||||
Sum *PackageSize
|
||||
// programSize contains size statistics per package of a compiled program.
|
||||
type programSize struct {
|
||||
Packages map[string]*packageSize
|
||||
Sum *packageSize
|
||||
Code uint64
|
||||
Data uint64
|
||||
BSS uint64
|
||||
}
|
||||
|
||||
// Return the list of package names (ProgramSize.Packages) sorted
|
||||
// alphabetically.
|
||||
func (ps *ProgramSize) SortedPackageNames() []string {
|
||||
// sortedPackageNames returns the list of package names (ProgramSize.Packages)
|
||||
// sorted alphabetically.
|
||||
func (ps *programSize) sortedPackageNames() []string {
|
||||
names := make([]string, 0, len(ps.Packages))
|
||||
for name := range ps.Packages {
|
||||
names = append(names, name)
|
||||
@@ -26,8 +26,9 @@ func (ps *ProgramSize) SortedPackageNames() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
// The size of a package, calculated from the linked object file.
|
||||
type PackageSize struct {
|
||||
// packageSize contains the size of a package, calculated from the linked object
|
||||
// file.
|
||||
type packageSize struct {
|
||||
Code uint64
|
||||
ROData uint64
|
||||
Data uint64
|
||||
@@ -35,12 +36,12 @@ type PackageSize struct {
|
||||
}
|
||||
|
||||
// Flash usage in regular microcontrollers.
|
||||
func (ps *PackageSize) Flash() uint64 {
|
||||
func (ps *packageSize) Flash() uint64 {
|
||||
return ps.Code + ps.ROData + ps.Data
|
||||
}
|
||||
|
||||
// Static RAM usage in regular microcontrollers.
|
||||
func (ps *PackageSize) RAM() uint64 {
|
||||
func (ps *packageSize) RAM() uint64 {
|
||||
return ps.Data + ps.BSS
|
||||
}
|
||||
|
||||
@@ -64,8 +65,9 @@ func (l symbolList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
}
|
||||
|
||||
// Calculate program/data size breakdown of each package for a given ELF file.
|
||||
func Sizes(path string) (*ProgramSize, error) {
|
||||
// loadProgramSize calculate a program/data size breakdown of each package for a
|
||||
// given ELF file.
|
||||
func loadProgramSize(path string) (*programSize, error) {
|
||||
file, err := elf.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -115,7 +117,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
||||
}
|
||||
sort.Sort(symbolList(symbols))
|
||||
|
||||
sizes := map[string]*PackageSize{}
|
||||
sizes := map[string]*packageSize{}
|
||||
var lastSymbolValue uint64
|
||||
for _, symbol := range symbols {
|
||||
symType := elf.ST_TYPE(symbol.Info)
|
||||
@@ -129,7 +131,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
||||
}
|
||||
pkgSize := sizes[pkgName]
|
||||
if pkgSize == nil {
|
||||
pkgSize = &PackageSize{}
|
||||
pkgSize = &packageSize{}
|
||||
sizes[pkgName] = pkgSize
|
||||
}
|
||||
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
|
||||
@@ -148,7 +150,7 @@ func Sizes(path string) (*ProgramSize, error) {
|
||||
lastSymbolValue = symbol.Value
|
||||
}
|
||||
|
||||
sum := &PackageSize{}
|
||||
sum := &packageSize{}
|
||||
for _, pkg := range sizes {
|
||||
sum.Code += pkg.Code
|
||||
sum.ROData += pkg.ROData
|
||||
@@ -156,5 +158,5 @@ func Sizes(path string) (*ProgramSize, error) {
|
||||
sum.BSS += pkg.BSS
|
||||
}
|
||||
|
||||
return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
||||
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
||||
}
|
||||
+25
-25
@@ -1,10 +1,11 @@
|
||||
// Converts firmware files from BIN to UF2 format before flashing.
|
||||
package builder
|
||||
|
||||
// This file converts firmware files from BIN to UF2 format before flashing.
|
||||
//
|
||||
// For more information about the UF2 firmware file format, please see:
|
||||
// https://github.com/Microsoft/uf2
|
||||
//
|
||||
//
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -12,24 +13,24 @@ import (
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// ConvertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||
func ConvertELFFileToUF2File(infile, outfile string) error {
|
||||
// convertELFFileToUF2File converts an ELF file to a UF2 file.
|
||||
func convertELFFileToUF2File(infile, outfile string) error {
|
||||
// Read the .text segment.
|
||||
_, data, err := ExtractROM(infile)
|
||||
targetAddress, data, err := extractROM(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, _ := ConvertBinToUF2(data)
|
||||
output, _ := convertBinToUF2(data, uint32(targetAddress))
|
||||
return ioutil.WriteFile(outfile, output, 0644)
|
||||
}
|
||||
|
||||
// ConvertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||
func ConvertBinToUF2(input []byte) ([]byte, int) {
|
||||
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||
func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
|
||||
blocks := split(input, 256)
|
||||
output := make([]byte, 0)
|
||||
|
||||
bl := NewUF2Block()
|
||||
bl := newUF2Block(targetAddr)
|
||||
bl.SetNumBlocks(len(blocks))
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
@@ -44,14 +45,13 @@ func ConvertBinToUF2(input []byte) ([]byte, int) {
|
||||
}
|
||||
|
||||
const (
|
||||
uf2MagicStart0 = 0x0A324655 // "UF2\n"
|
||||
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
|
||||
uf2MagicEnd = 0x0AB16F30 // Ditto
|
||||
uf2StartAddress = 0x2000
|
||||
uf2MagicStart0 = 0x0A324655 // "UF2\n"
|
||||
uf2MagicStart1 = 0x9E5D5157 // Randomly selected
|
||||
uf2MagicEnd = 0x0AB16F30 // Ditto
|
||||
)
|
||||
|
||||
// UF2Block is the structure used for each UF2 code block sent to device.
|
||||
type UF2Block struct {
|
||||
// uf2Block is the structure used for each UF2 code block sent to device.
|
||||
type uf2Block struct {
|
||||
magicStart0 uint32
|
||||
magicStart1 uint32
|
||||
flags uint32
|
||||
@@ -64,12 +64,12 @@ type UF2Block struct {
|
||||
magicEnd uint32
|
||||
}
|
||||
|
||||
// NewUF2Block returns a new UF2Block struct that has been correctly populated
|
||||
func NewUF2Block() *UF2Block {
|
||||
return &UF2Block{magicStart0: uf2MagicStart0,
|
||||
// newUF2Block returns a new uf2Block struct that has been correctly populated
|
||||
func newUF2Block(targetAddr uint32) *uf2Block {
|
||||
return &uf2Block{magicStart0: uf2MagicStart0,
|
||||
magicStart1: uf2MagicStart1,
|
||||
magicEnd: uf2MagicEnd,
|
||||
targetAddr: uf2StartAddress,
|
||||
targetAddr: targetAddr,
|
||||
flags: 0x0,
|
||||
familyID: 0x0,
|
||||
payloadSize: 256,
|
||||
@@ -77,8 +77,8 @@ func NewUF2Block() *UF2Block {
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes converts the UF2Block to a slice of bytes that can be written to file.
|
||||
func (b *UF2Block) Bytes() []byte {
|
||||
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
|
||||
func (b *uf2Block) Bytes() []byte {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 512))
|
||||
binary.Write(buf, binary.LittleEndian, b.magicStart0)
|
||||
binary.Write(buf, binary.LittleEndian, b.magicStart1)
|
||||
@@ -95,23 +95,23 @@ func (b *UF2Block) Bytes() []byte {
|
||||
}
|
||||
|
||||
// IncrementAddress moves the target address pointer forward by count bytes.
|
||||
func (b *UF2Block) IncrementAddress(count uint32) {
|
||||
func (b *uf2Block) IncrementAddress(count uint32) {
|
||||
b.targetAddr += b.payloadSize
|
||||
}
|
||||
|
||||
// SetData sets the data to be used for the current block.
|
||||
func (b *UF2Block) SetData(d []byte) {
|
||||
func (b *uf2Block) SetData(d []byte) {
|
||||
b.data = make([]byte, 476)
|
||||
copy(b.data[:], d)
|
||||
}
|
||||
|
||||
// SetBlockNo sets the current block number to be used.
|
||||
func (b *UF2Block) SetBlockNo(bn int) {
|
||||
func (b *uf2Block) SetBlockNo(bn int) {
|
||||
b.blockNo = uint32(bn)
|
||||
}
|
||||
|
||||
// SetNumBlocks sets the total number of blocks for this UF2 file.
|
||||
func (b *UF2Block) SetNumBlocks(total int) {
|
||||
func (b *uf2Block) SetNumBlocks(total int) {
|
||||
b.numBlocks = uint32(total)
|
||||
}
|
||||
|
||||
+754
-61
File diff suppressed because it is too large
Load Diff
+129
@@ -0,0 +1,129 @@
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Pass -update to go test to update the output of the test files.
|
||||
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
|
||||
|
||||
func TestCGo(t *testing.T) {
|
||||
var cflags = []string{"--target=armv6m-none-eabi"}
|
||||
|
||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
fset := token.NewFileSet()
|
||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatal("could not parse Go source file:", err)
|
||||
}
|
||||
|
||||
// Process the AST with CGo.
|
||||
cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
|
||||
// Check the AST for type errors.
|
||||
var typecheckErrors []error
|
||||
config := types.Config{
|
||||
Error: func(err error) {
|
||||
typecheckErrors = append(typecheckErrors, err)
|
||||
},
|
||||
Importer: simpleImporter{},
|
||||
Sizes: types.SizesFor("gccgo", "arm"),
|
||||
}
|
||||
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
|
||||
if err != nil && len(typecheckErrors) == 0 {
|
||||
// Only report errors when no type errors are found (an
|
||||
// unexpected condition).
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Store the (formatted) output in a buffer. Format it, so it
|
||||
// becomes easier to read (and will hopefully change less with CGo
|
||||
// changes).
|
||||
buf := &bytes.Buffer{}
|
||||
if len(cgoErrors) != 0 {
|
||||
buf.WriteString("// CGo errors:\n")
|
||||
for _, err := range cgoErrors {
|
||||
buf.WriteString(formatDiagnostic(err))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
if len(typecheckErrors) != 0 {
|
||||
buf.WriteString("// Type checking errors after CGo processing:\n")
|
||||
for _, err := range typecheckErrors {
|
||||
buf.WriteString(formatDiagnostic(err))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
err = format.Node(buf, fset, cgoAST)
|
||||
if err != nil {
|
||||
t.Errorf("could not write out CGo AST: %v", err)
|
||||
}
|
||||
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
|
||||
|
||||
// Read the file with the expected output, to compare against.
|
||||
outfile := filepath.Join("testdata", name+".out.go")
|
||||
expectedBytes, err := ioutil.ReadFile(outfile)
|
||||
if err != nil {
|
||||
t.Fatalf("could not read expected output: %v", err)
|
||||
}
|
||||
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
|
||||
|
||||
// Check whether the output is as expected.
|
||||
if expected != actual {
|
||||
// It is not. Test failed.
|
||||
if *flagUpdate {
|
||||
// Update the file with the expected data.
|
||||
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
|
||||
if err != nil {
|
||||
t.Error("could not write updated output file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
t.Errorf("output did not match:\n%s", string(actual))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// simpleImporter implements the types.Importer interface, but only allows
|
||||
// importing the unsafe package.
|
||||
type simpleImporter struct {
|
||||
}
|
||||
|
||||
// Import implements the Importer interface. For testing usage only: it only
|
||||
// supports importing the unsafe package.
|
||||
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
||||
switch path {
|
||||
case "unsafe":
|
||||
return types.Unsafe, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("importer not implemented for package %s", path)
|
||||
}
|
||||
}
|
||||
|
||||
// formatDiagnostics formats the error message to be an indented comment. It
|
||||
// also fixes Windows path name issues (backward slashes).
|
||||
func formatDiagnostic(err error) string {
|
||||
msg := err.Error()
|
||||
if runtime.GOOS == "windows" {
|
||||
// Fix Windows path slashes.
|
||||
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
|
||||
}
|
||||
return "// " + msg + "\n"
|
||||
}
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
package cgo
|
||||
|
||||
// This file implements a parser of a subset of the C language, just enough to
|
||||
// parse common #define statements to Go constant expressions.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// parseConst parses the given string as a C constant.
|
||||
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
|
||||
t := newTokenizer(pos, fset, value)
|
||||
expr, err := parseConstExpr(t)
|
||||
if t.token != token.EOF {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: "unexpected token " + t.token.String(),
|
||||
}
|
||||
}
|
||||
return expr, err
|
||||
}
|
||||
|
||||
// parseConstExpr parses a stream of C tokens to a Go expression.
|
||||
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
switch t.token {
|
||||
case token.LPAREN:
|
||||
lparen := t.pos
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.token != token.RPAREN {
|
||||
return nil, unexpectedToken(t, token.RPAREN)
|
||||
}
|
||||
expr := &ast.ParenExpr{
|
||||
Lparen: lparen,
|
||||
X: x,
|
||||
Rparen: t.pos,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.INT, token.FLOAT, token.STRING, token.CHAR:
|
||||
expr := &ast.BasicLit{
|
||||
ValuePos: t.pos,
|
||||
Kind: t.token,
|
||||
Value: t.value,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.IDENT:
|
||||
expr := &ast.Ident{
|
||||
NamePos: t.pos,
|
||||
Name: "C." + t.value,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.EOF:
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: "empty constant",
|
||||
}
|
||||
default:
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: fmt.Sprintf("unexpected token %s", t.token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
||||
// BAR".
|
||||
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
||||
return &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
|
||||
}
|
||||
}
|
||||
|
||||
// tokenizer reads C source code and converts it to Go tokens.
|
||||
type tokenizer struct {
|
||||
pos token.Pos
|
||||
fset *token.FileSet
|
||||
token token.Token
|
||||
value string
|
||||
buf string
|
||||
}
|
||||
|
||||
// newTokenizer initializes a new tokenizer, positioned at the first token in
|
||||
// the string.
|
||||
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
|
||||
t := &tokenizer{
|
||||
pos: start,
|
||||
fset: fset,
|
||||
buf: buf,
|
||||
token: token.ILLEGAL,
|
||||
}
|
||||
t.Next() // Parse the first token.
|
||||
return t
|
||||
}
|
||||
|
||||
// Next consumes the next token in the stream. There is no return value, read
|
||||
// the next token from the pos, token and value properties.
|
||||
func (t *tokenizer) Next() {
|
||||
t.pos += token.Pos(len(t.value))
|
||||
for {
|
||||
if len(t.buf) == 0 {
|
||||
t.token = token.EOF
|
||||
return
|
||||
}
|
||||
c := t.buf[0]
|
||||
switch {
|
||||
case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v':
|
||||
// Skip whitespace.
|
||||
// Based on this source, not sure whether it represents C whitespace:
|
||||
// https://en.cppreference.com/w/cpp/string/byte/isspace
|
||||
t.pos++
|
||||
t.buf = t.buf[1:]
|
||||
case c == '(' || c == ')':
|
||||
// Single-character tokens.
|
||||
switch c {
|
||||
case '(':
|
||||
t.token = token.LPAREN
|
||||
case ')':
|
||||
t.token = token.RPAREN
|
||||
}
|
||||
t.value = t.buf[:1]
|
||||
t.buf = t.buf[1:]
|
||||
return
|
||||
case c >= '0' && c <= '9':
|
||||
// Numeric constant (int, float, etc.).
|
||||
// Find the last non-numeric character.
|
||||
tokenLen := len(t.buf)
|
||||
hasDot := false
|
||||
for i, c := range t.buf {
|
||||
if c == '.' {
|
||||
hasDot = true
|
||||
}
|
||||
if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
|
||||
tokenLen = i + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
if hasDot {
|
||||
// Integer constants are more complicated than this but this is
|
||||
// a close approximation.
|
||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||
t.token = token.FLOAT
|
||||
t.value = strings.TrimRight(t.value, "f")
|
||||
} else {
|
||||
t.token = token.INT
|
||||
t.value = strings.TrimRight(t.value, "uUlL")
|
||||
}
|
||||
return
|
||||
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
||||
// Identifier. Find all remaining tokens that are part of this
|
||||
// identifier.
|
||||
tokenLen := len(t.buf)
|
||||
for i, c := range t.buf {
|
||||
if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' {
|
||||
tokenLen = i + 1
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
t.token = token.IDENT
|
||||
return
|
||||
case c == '"':
|
||||
// String constant. Find the first '"' character that is not
|
||||
// preceded by a backslash.
|
||||
escape := false
|
||||
tokenLen := len(t.buf)
|
||||
for i, c := range t.buf {
|
||||
if i != 0 && c == '"' && !escape {
|
||||
tokenLen = i + 1
|
||||
break
|
||||
}
|
||||
if !escape {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.STRING
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
case c == '\'':
|
||||
// Char (rune) constant. Find the first '\'' character that is not
|
||||
// preceded by a backslash.
|
||||
escape := false
|
||||
tokenLen := len(t.buf)
|
||||
for i, c := range t.buf {
|
||||
if i != 0 && c == '\'' && !escape {
|
||||
tokenLen = i + 1
|
||||
break
|
||||
}
|
||||
if !escape {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.CHAR
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
default:
|
||||
t.token = token.ILLEGAL
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"go/format"
|
||||
"go/token"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseConst(t *testing.T) {
|
||||
// Test converting a C constant to a Go constant.
|
||||
for _, tc := range []struct {
|
||||
C string
|
||||
Go string
|
||||
}{
|
||||
{`5`, `5`},
|
||||
{`(5)`, `(5)`},
|
||||
{`(((5)))`, `(5)`},
|
||||
{`)`, `error: 1:1: unexpected token )`},
|
||||
{`5)`, `error: 1:2: unexpected token )`},
|
||||
{" \t)", `error: 1:4: unexpected token )`},
|
||||
{`5.8f`, `5.8`},
|
||||
{`foo`, `C.foo`},
|
||||
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
|
||||
{`"foo"`, `"foo"`},
|
||||
{`"a\\n"`, `"a\\n"`},
|
||||
{`"a\n"`, `"a\n"`},
|
||||
{`"a\""`, `"a\""`},
|
||||
{`'a'`, `'a'`},
|
||||
{`0b10`, `0b10`},
|
||||
{`0x1234_5678`, `0x1234_5678`},
|
||||
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
|
||||
} {
|
||||
fset := token.NewFileSet()
|
||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||
expr, err := parseConst(startPos, fset, tc.C)
|
||||
s := "<invalid>"
|
||||
if err != nil {
|
||||
if !strings.HasPrefix(tc.Go, "error: ") {
|
||||
t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error())
|
||||
continue
|
||||
}
|
||||
s = "error: " + err.Error()
|
||||
} else if expr != nil {
|
||||
// Serialize the Go constant to a string, for more readable test
|
||||
// cases.
|
||||
buf := &bytes.Buffer{}
|
||||
err := format.Node(buf, fset, expr)
|
||||
if err != nil {
|
||||
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
|
||||
continue
|
||||
}
|
||||
s = buf.String()
|
||||
}
|
||||
if s != tc.Go {
|
||||
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
|
||||
}
|
||||
}
|
||||
}
|
||||
+305
-153
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-8-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -49,9 +49,13 @@ 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"
|
||||
|
||||
@@ -105,7 +109,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
||||
&unit)
|
||||
if errCode != 0 {
|
||||
panic("loader: failed to parse source with libclang")
|
||||
// This is probably a bug in the usage of libclang.
|
||||
panic("cgo: failed to parse source with libclang")
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
@@ -114,27 +119,8 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
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,
|
||||
})
|
||||
pos := p.getClangLocationPosition(location, unit)
|
||||
p.addError(pos, severity+": "+spelling)
|
||||
}
|
||||
for i := 0; i < numDiagnostics; i++ {
|
||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||
@@ -146,7 +132,6 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ref := storedRefs.Put(p)
|
||||
@@ -233,14 +218,17 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
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")
|
||||
p.addError(pos, "internal error: could not find file where macro is defined")
|
||||
break
|
||||
}
|
||||
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")
|
||||
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
|
||||
break
|
||||
}
|
||||
if startOffset > endOffset {
|
||||
panic("startOffset > endOffset")
|
||||
p.addError(pos, "internal error: start offset of macro is after end offset")
|
||||
break
|
||||
}
|
||||
|
||||
// read file contents and extract the relevant byte range
|
||||
@@ -248,59 +236,29 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
var size C.size_t
|
||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
||||
if endOffset >= C.uint(size) {
|
||||
panic("endOffset lies after end of file")
|
||||
p.addError(pos, "internal error: end offset of macro lies after end of file")
|
||||
break
|
||||
}
|
||||
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))
|
||||
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
|
||||
break
|
||||
}
|
||||
value := strings.TrimSpace(source[len(name):])
|
||||
for len(value) != 0 && value[0] == '(' && value[len(value)-1] == ')' {
|
||||
value = strings.TrimSpace(value[1 : len(value)-1])
|
||||
value := source[len(name):]
|
||||
// Try to convert this #define into a Go constant expression.
|
||||
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
||||
if err != nil {
|
||||
p.errors = append(p.errors, err)
|
||||
}
|
||||
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
|
||||
if expr != nil {
|
||||
// Parsing was successful.
|
||||
p.constants[name] = constantInfo{expr, pos}
|
||||
}
|
||||
case C.CXCursor_EnumDecl:
|
||||
// Visit all enums, because the fields may be used even when the enum
|
||||
// type itself is not.
|
||||
typ := C.tinygo_clang_getCursorType(c)
|
||||
p.makeASTType(typ, pos)
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
@@ -312,11 +270,16 @@ func getString(clangString C.CXString) (s string) {
|
||||
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.
|
||||
// getCursorPosition returns a usable token.Pos from a libclang cursor.
|
||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
location := C.tinygo_clang_getCursorLocation(cursor)
|
||||
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
|
||||
}
|
||||
|
||||
// getClangLocationPosition returns a usable token.Pos based on a libclang
|
||||
// location and translation unit. 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) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
var column C.unsigned
|
||||
@@ -330,7 +293,6 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
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]
|
||||
@@ -344,7 +306,62 @@ func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
||||
f.SetLines(lines)
|
||||
p.tokenFiles[filename] = f
|
||||
}
|
||||
return p.tokenFiles[filename].Pos(int(offset))
|
||||
positionFile := p.tokenFiles[filename]
|
||||
|
||||
// Check for alternative line/column information (set with a line directive).
|
||||
var filename2String C.CXString
|
||||
var line2 C.unsigned
|
||||
var column2 C.unsigned
|
||||
C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
|
||||
filename2 := getString(filename2String)
|
||||
if filename2 != filename || line2 != line || column2 != column {
|
||||
// The location was changed with a preprocessor directive.
|
||||
// TODO: this only works for locations that are added in order. Adding
|
||||
// line/column info to a file that already has line/column info after
|
||||
// the given offset is ignored.
|
||||
positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
|
||||
}
|
||||
|
||||
return positionFile.Pos(int(offset))
|
||||
}
|
||||
|
||||
// addError is a utility function to add an error to the list of errors. It will
|
||||
// convert the token position to a line/column position first, and call
|
||||
// addErrorAt.
|
||||
func (p *cgoPackage) addError(pos token.Pos, msg string) {
|
||||
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
|
||||
}
|
||||
|
||||
// addErrorAfter is like addError, but adds the text `after` to the source
|
||||
// location.
|
||||
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
|
||||
position := p.fset.PositionFor(pos, true)
|
||||
lines := strings.Split(after, "\n")
|
||||
if len(lines) != 1 {
|
||||
// Adjust lines.
|
||||
// For why we can't just do pos+token.Pos(len(after)), see:
|
||||
// https://github.com/golang/go/issues/35803
|
||||
position.Line += len(lines) - 1
|
||||
position.Column = len(lines[len(lines)-1]) + 1
|
||||
} else {
|
||||
position.Column += len(after)
|
||||
}
|
||||
p.addErrorAt(position, msg)
|
||||
}
|
||||
|
||||
// addErrorAt is a utility function to add an error to the list of errors.
|
||||
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
|
||||
if filepath.IsAbs(position.Filename) {
|
||||
// Relative paths for readability, like other Go parser errors.
|
||||
relpath, err := filepath.Rel(p.dir, position.Filename)
|
||||
if err == nil {
|
||||
position.Filename = relpath
|
||||
}
|
||||
}
|
||||
p.errors = append(p.errors, scanner.Error{
|
||||
Pos: position,
|
||||
Msg: msg,
|
||||
})
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
@@ -455,7 +472,7 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
// 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")
|
||||
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
|
||||
}
|
||||
switch underlyingType.kind {
|
||||
case C.CXType_Char_S:
|
||||
@@ -501,90 +518,86 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
switch underlying.kind {
|
||||
case C.CXType_Record:
|
||||
return p.makeASTType(underlying, pos)
|
||||
case C.CXType_Enum:
|
||||
return p.makeASTType(underlying, pos)
|
||||
default:
|
||||
panic("unknown elaborated type")
|
||||
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
|
||||
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
|
||||
typeName = "<unknown>"
|
||||
}
|
||||
case C.CXType_Record:
|
||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
||||
var cgoName string
|
||||
var cgoRecordPrefix string
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
cgoName = "struct_" + name
|
||||
cgoRecordPrefix = "struct_"
|
||||
case C.CXCursor_UnionDecl:
|
||||
cgoName = "union_" + name
|
||||
cgoRecordPrefix = "union_"
|
||||
default:
|
||||
panic("unknown record declaration")
|
||||
// makeASTRecordType will create an appropriate error.
|
||||
cgoRecordPrefix = "record_"
|
||||
}
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
fieldList := &ast.FieldList{
|
||||
Opening: pos,
|
||||
Closing: pos,
|
||||
if name == "" {
|
||||
// Anonymous record, probably inside a typedef.
|
||||
typeInfo := p.makeASTRecordType(cursor, pos)
|
||||
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
|
||||
// This record is a union or is a struct with bitfields, so we
|
||||
// have to declare it as a named type (for getters/setters to
|
||||
// work).
|
||||
p.anonStructNum++
|
||||
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
|
||||
p.elaboratedTypes[cgoName] = typeInfo
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
}
|
||||
}
|
||||
ref := storedRefs.Put(struct {
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
}{fieldList, p})
|
||||
return typeInfo.typeExpr
|
||||
} else {
|
||||
cgoName := cgoRecordPrefix + name
|
||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
||||
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
|
||||
}
|
||||
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_struct_visitor), C.CXClientData(ref))
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
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,
|
||||
}
|
||||
case C.CXCursor_UnionDecl:
|
||||
if len(fieldList.List) > 1 {
|
||||
// Insert a special field at the front (of zero width) as a
|
||||
// marker that this is struct is actually a union. This is done
|
||||
// by giving the field a name that cannot be expressed directly
|
||||
// in Go.
|
||||
// Other parts of the compiler look at the first element in a
|
||||
// struct (of size > 2) to know whether this is a union.
|
||||
// Note that we don't have to insert it for single-element
|
||||
// unions as they're basically equivalent to a struct.
|
||||
unionMarker := &ast.Field{
|
||||
Type: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
}
|
||||
unionMarker.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C union",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C union",
|
||||
Decl: unionMarker,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append([]*ast.Field{unionMarker}, fieldList.List...)
|
||||
}
|
||||
p.elaboratedTypes[cgoName] = &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C." + cgoName,
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "C.enum_" + name,
|
||||
}
|
||||
}
|
||||
}
|
||||
if typeName == "" {
|
||||
// Fallback, probably incorrect but at least the error points to an odd
|
||||
// type name.
|
||||
typeName = "C." + getString(C.clang_getTypeSpelling(typ))
|
||||
// Report this as an error.
|
||||
typeSpelling := getString(C.clang_getTypeSpelling(typ))
|
||||
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
|
||||
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
|
||||
typeName = "C.<unknown>"
|
||||
}
|
||||
return &ast.Ident{
|
||||
NamePos: pos,
|
||||
@@ -592,25 +605,151 @@ func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
}
|
||||
}
|
||||
|
||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
||||
// a Go struct type.
|
||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
|
||||
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))
|
||||
renameFieldKeywords(fieldList)
|
||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
||||
case C.CXCursor_StructDecl:
|
||||
return &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
case C.CXCursor_UnionDecl:
|
||||
typeInfo := &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
Fields: fieldList,
|
||||
},
|
||||
pos: pos,
|
||||
bitfields: bitfieldList,
|
||||
}
|
||||
if len(fieldList.List) <= 1 {
|
||||
// Useless union, treat it as a regular struct.
|
||||
return typeInfo
|
||||
}
|
||||
if bitfieldList != nil {
|
||||
// This is valid C... but please don't do this.
|
||||
p.addError(pos, "bitfield in a union is not supported")
|
||||
}
|
||||
typ := C.tinygo_clang_getCursorType(cursor)
|
||||
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
|
||||
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
|
||||
if sizeInBytes == 0 {
|
||||
p.addError(pos, "zero-length union is not supported")
|
||||
}
|
||||
typeInfo.unionSize = sizeInBytes
|
||||
typeInfo.unionAlign = alignInBytes
|
||||
return typeInfo
|
||||
default:
|
||||
cursorKind := C.tinygo_clang_getCursorKind(cursor)
|
||||
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
||||
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
|
||||
return &elaboratedTypeInfo{
|
||||
typeExpr: &ast.StructType{
|
||||
Struct: pos,
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//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
|
||||
fieldList *ast.FieldList
|
||||
pkg *cgoPackage
|
||||
inBitfield *bool
|
||||
bitfieldNum *int
|
||||
bitfieldList *[]bitfieldInfo
|
||||
})
|
||||
fieldList := passed.fieldList
|
||||
p := passed.pkg
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_FieldDecl {
|
||||
panic("expected field inside cursor")
|
||||
inBitfield := passed.inBitfield
|
||||
bitfieldNum := passed.bitfieldNum
|
||||
bitfieldList := passed.bitfieldList
|
||||
pos := p.getCursorPosition(c)
|
||||
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
|
||||
case C.CXCursor_FieldDecl:
|
||||
// Expected. This is a regular field.
|
||||
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
|
||||
// Ignore. The next field will be the struct/union itself.
|
||||
return C.CXChildVisit_Continue
|
||||
default:
|
||||
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
||||
p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
p.addError(pos, "expected a bitfield")
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
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: p.getCursorPosition(c),
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
@@ -622,3 +761,16 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
//export tinygo_clang_enum_visitor
|
||||
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
||||
pos := p.getCursorPosition(c)
|
||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
||||
p.constants[name] = constantInfo{
|
||||
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
|
||||
pos: pos,
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
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
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
|
||||
+13
-1
@@ -3,7 +3,7 @@
|
||||
// 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
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
|
||||
|
||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||
return clang_getTranslationUnitCursor(tu);
|
||||
@@ -56,3 +56,15 @@ CXSourceRange tinygo_clang_getCursorExtent(CXCursor c) {
|
||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(CXCursor c) {
|
||||
return clang_Cursor_getTranslationUnit(c);
|
||||
}
|
||||
|
||||
long long tinygo_clang_getEnumConstantDeclValue(CXCursor c) {
|
||||
return clang_getEnumConstantDeclValue(c);
|
||||
}
|
||||
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
|
||||
return clang_getEnumDeclIntegerType(c);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
||||
return clang_Cursor_isBitField(c);
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file has been copied from the Go 1.13 release tree.
|
||||
|
||||
// Checking of compiler and linker flags.
|
||||
// We must avoid flags like -fplugin=, which can allow
|
||||
// arbitrary code execution during the build.
|
||||
// Do not make changes here without carefully
|
||||
// considering the implications.
|
||||
// (That's why the code is isolated in a file named security.go.)
|
||||
//
|
||||
// Note that -Wl,foo means split foo on commas and pass to
|
||||
// the linker, so that -Wl,-foo,bar means pass -foo bar to
|
||||
// the linker. Similarly -Wa,foo for the assembler and so on.
|
||||
// If any of these are permitted, the wildcard portion must
|
||||
// disallow commas.
|
||||
//
|
||||
// Note also that GNU binutils accept any argument @foo
|
||||
// as meaning "read more flags from the file foo", so we must
|
||||
// guard against any command-line argument beginning with @,
|
||||
// even things like "-I @foo".
|
||||
// We use safeArg (which is even more conservative)
|
||||
// to reject these.
|
||||
//
|
||||
// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
|
||||
// so although gcc doesn't expand the @foo, cc1 will.
|
||||
// So out of paranoia, we reject @ at the beginning of every
|
||||
// flag argument that might be split into its own argument.
|
||||
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
var re = regexp.MustCompile
|
||||
|
||||
var validCompilerFlags = []*regexp.Regexp{
|
||||
re(`-D([A-Za-z_].*)`),
|
||||
re(`-F([^@\-].*)`),
|
||||
re(`-I([^@\-].*)`),
|
||||
re(`-O`),
|
||||
re(`-O([^@\-].*)`),
|
||||
re(`-W`),
|
||||
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
|
||||
re(`-Wa,-mbig-obj`),
|
||||
re(`-Wp,-D([A-Za-z_].*)`),
|
||||
re(`-ansi`),
|
||||
re(`-f(no-)?asynchronous-unwind-tables`),
|
||||
re(`-f(no-)?blocks`),
|
||||
re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
|
||||
re(`-f(no-)?common`),
|
||||
re(`-f(no-)?constant-cfstrings`),
|
||||
re(`-fdiagnostics-show-note-include-stack`),
|
||||
re(`-f(no-)?eliminate-unused-debug-types`),
|
||||
re(`-f(no-)?exceptions`),
|
||||
re(`-f(no-)?fast-math`),
|
||||
re(`-f(no-)?inline-functions`),
|
||||
re(`-finput-charset=([^@\-].*)`),
|
||||
re(`-f(no-)?fat-lto-objects`),
|
||||
re(`-f(no-)?keep-inline-dllexport`),
|
||||
re(`-f(no-)?lto`),
|
||||
re(`-fmacro-backtrace-limit=(.+)`),
|
||||
re(`-fmessage-length=(.+)`),
|
||||
re(`-f(no-)?modules`),
|
||||
re(`-f(no-)?objc-arc`),
|
||||
re(`-f(no-)?objc-nonfragile-abi`),
|
||||
re(`-f(no-)?objc-legacy-dispatch`),
|
||||
re(`-f(no-)?omit-frame-pointer`),
|
||||
re(`-f(no-)?openmp(-simd)?`),
|
||||
re(`-f(no-)?permissive`),
|
||||
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
||||
re(`-f(no-)?plt`),
|
||||
re(`-f(no-)?rtti`),
|
||||
re(`-f(no-)?split-stack`),
|
||||
re(`-f(no-)?stack-(.+)`),
|
||||
re(`-f(no-)?strict-aliasing`),
|
||||
re(`-f(un)signed-char`),
|
||||
re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
|
||||
re(`-f(no-)?visibility-inlines-hidden`),
|
||||
re(`-fsanitize=(.+)`),
|
||||
re(`-ftemplate-depth-(.+)`),
|
||||
re(`-fvisibility=(.+)`),
|
||||
re(`-g([^@\-].*)?`),
|
||||
re(`-m32`),
|
||||
re(`-m64`),
|
||||
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
||||
re(`-m(no-)?v?aes`),
|
||||
re(`-marm`),
|
||||
re(`-m(no-)?avx[0-9a-z]*`),
|
||||
re(`-mfloat-abi=([^@\-].*)`),
|
||||
re(`-mfpmath=[0-9a-z,+]*`),
|
||||
re(`-m(no-)?avx[0-9a-z.]*`),
|
||||
re(`-m(no-)?ms-bitfields`),
|
||||
re(`-m(no-)?stack-(.+)`),
|
||||
re(`-mmacosx-(.+)`),
|
||||
re(`-mios-simulator-version-min=(.+)`),
|
||||
re(`-miphoneos-version-min=(.+)`),
|
||||
re(`-mtvos-simulator-version-min=(.+)`),
|
||||
re(`-mtvos-version-min=(.+)`),
|
||||
re(`-mwatchos-simulator-version-min=(.+)`),
|
||||
re(`-mwatchos-version-min=(.+)`),
|
||||
re(`-mnop-fun-dllimport`),
|
||||
re(`-m(no-)?sse[0-9.]*`),
|
||||
re(`-m(no-)?ssse3`),
|
||||
re(`-mthumb(-interwork)?`),
|
||||
re(`-mthreads`),
|
||||
re(`-mwindows`),
|
||||
re(`--param=ssp-buffer-size=[0-9]*`),
|
||||
re(`-pedantic(-errors)?`),
|
||||
re(`-pipe`),
|
||||
re(`-pthread`),
|
||||
re(`-?-std=([^@\-].*)`),
|
||||
re(`-?-stdlib=([^@\-].*)`),
|
||||
re(`--sysroot=([^@\-].*)`),
|
||||
re(`-w`),
|
||||
re(`-x([^@\-].*)`),
|
||||
re(`-v`),
|
||||
}
|
||||
|
||||
var validCompilerFlagsWithNextArg = []string{
|
||||
"-arch",
|
||||
"-D",
|
||||
"-I",
|
||||
"-framework",
|
||||
"-isysroot",
|
||||
"-isystem",
|
||||
"--sysroot",
|
||||
"-target",
|
||||
"-x",
|
||||
}
|
||||
|
||||
var validLinkerFlags = []*regexp.Regexp{
|
||||
re(`-F([^@\-].*)`),
|
||||
re(`-l([^@\-].*)`),
|
||||
re(`-L([^@\-].*)`),
|
||||
re(`-O`),
|
||||
re(`-O([^@\-].*)`),
|
||||
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
||||
re(`-f(no-)?openmp(-simd)?`),
|
||||
re(`-fsanitize=([^@\-].*)`),
|
||||
re(`-flat_namespace`),
|
||||
re(`-g([^@\-].*)?`),
|
||||
re(`-headerpad_max_install_names`),
|
||||
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
||||
re(`-mfloat-abi=([^@\-].*)`),
|
||||
re(`-mmacosx-(.+)`),
|
||||
re(`-mios-simulator-version-min=(.+)`),
|
||||
re(`-miphoneos-version-min=(.+)`),
|
||||
re(`-mthreads`),
|
||||
re(`-mwindows`),
|
||||
re(`-(pic|PIC|pie|PIE)`),
|
||||
re(`-pthread`),
|
||||
re(`-rdynamic`),
|
||||
re(`-shared`),
|
||||
re(`-?-static([-a-z0-9+]*)`),
|
||||
re(`-?-stdlib=([^@\-].*)`),
|
||||
re(`-v`),
|
||||
|
||||
// Note that any wildcards in -Wl need to exclude comma,
|
||||
// since -Wl splits its argument at commas and passes
|
||||
// them all to the linker uninterpreted. Allowing comma
|
||||
// in a wildcard would allow tunnelling arbitrary additional
|
||||
// linker arguments through one of these.
|
||||
re(`-Wl,--(no-)?allow-multiple-definition`),
|
||||
re(`-Wl,--(no-)?allow-shlib-undefined`),
|
||||
re(`-Wl,--(no-)?as-needed`),
|
||||
re(`-Wl,-Bdynamic`),
|
||||
re(`-Wl,-berok`),
|
||||
re(`-Wl,-Bstatic`),
|
||||
re(`-WL,-O([^@,\-][^,]*)?`),
|
||||
re(`-Wl,-d[ny]`),
|
||||
re(`-Wl,--disable-new-dtags`),
|
||||
re(`-Wl,-e[=,][a-zA-Z0-9]*`),
|
||||
re(`-Wl,--enable-new-dtags`),
|
||||
re(`-Wl,--end-group`),
|
||||
re(`-Wl,--(no-)?export-dynamic`),
|
||||
re(`-Wl,-framework,[^,@\-][^,]+`),
|
||||
re(`-Wl,-headerpad_max_install_names`),
|
||||
re(`-Wl,--no-undefined`),
|
||||
re(`-Wl,-R([^@\-][^,@]*$)`),
|
||||
re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
|
||||
re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
|
||||
re(`-Wl,-s`),
|
||||
re(`-Wl,-search_paths_first`),
|
||||
re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
|
||||
re(`-Wl,--start-group`),
|
||||
re(`-Wl,-?-static`),
|
||||
re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
|
||||
re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
|
||||
re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
|
||||
re(`-Wl,-?-unresolved-symbols=[^,]+`),
|
||||
re(`-Wl,--(no-)?warn-([^,]+)`),
|
||||
re(`-Wl,-z,(no)?execstack`),
|
||||
re(`-Wl,-z,relro`),
|
||||
|
||||
re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
|
||||
re(`\./.*\.(a|o|obj|dll|dylib|so)`),
|
||||
}
|
||||
|
||||
var validLinkerFlagsWithNextArg = []string{
|
||||
"-arch",
|
||||
"-F",
|
||||
"-l",
|
||||
"-L",
|
||||
"-framework",
|
||||
"-isysroot",
|
||||
"--sysroot",
|
||||
"-target",
|
||||
"-Wl,-framework",
|
||||
"-Wl,-rpath",
|
||||
"-Wl,-R",
|
||||
"-Wl,--just-symbols",
|
||||
"-Wl,-undefined",
|
||||
}
|
||||
|
||||
func checkCompilerFlags(name string, list []string) error {
|
||||
return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
|
||||
}
|
||||
|
||||
func checkLinkerFlags(name string, list []string) error {
|
||||
return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
|
||||
}
|
||||
|
||||
func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
|
||||
// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
|
||||
var (
|
||||
allow *regexp.Regexp
|
||||
disallow *regexp.Regexp
|
||||
)
|
||||
if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
|
||||
r, err := regexp.Compile(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
|
||||
}
|
||||
allow = r
|
||||
}
|
||||
if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
|
||||
r, err := regexp.Compile(env)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
|
||||
}
|
||||
disallow = r
|
||||
}
|
||||
|
||||
Args:
|
||||
for i := 0; i < len(list); i++ {
|
||||
arg := list[i]
|
||||
if disallow != nil && disallow.FindString(arg) == arg {
|
||||
goto Bad
|
||||
}
|
||||
if allow != nil && allow.FindString(arg) == arg {
|
||||
continue Args
|
||||
}
|
||||
for _, re := range valid {
|
||||
if re.FindString(arg) == arg { // must be complete match
|
||||
continue Args
|
||||
}
|
||||
}
|
||||
for _, x := range validNext {
|
||||
if arg == x {
|
||||
if i+1 < len(list) && safeArg(list[i+1]) {
|
||||
i++
|
||||
continue Args
|
||||
}
|
||||
|
||||
// Permit -Wl,-framework -Wl,name.
|
||||
if i+1 < len(list) &&
|
||||
strings.HasPrefix(arg, "-Wl,") &&
|
||||
strings.HasPrefix(list[i+1], "-Wl,") &&
|
||||
safeArg(list[i+1][4:]) &&
|
||||
!strings.Contains(list[i+1][4:], ",") {
|
||||
i++
|
||||
continue Args
|
||||
}
|
||||
|
||||
if i+1 < len(list) {
|
||||
return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
|
||||
}
|
||||
return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
|
||||
}
|
||||
}
|
||||
Bad:
|
||||
return fmt.Errorf("invalid flag: %s", arg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeArg(name string) bool {
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
c := name[0]
|
||||
return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file has been copied from the Go 1.13 release tree.
|
||||
|
||||
package cgo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var goodCompilerFlags = [][]string{
|
||||
{"-DFOO"},
|
||||
{"-Dfoo=bar"},
|
||||
{"-F/Qt"},
|
||||
{"-I/"},
|
||||
{"-I/etc/passwd"},
|
||||
{"-I."},
|
||||
{"-O"},
|
||||
{"-O2"},
|
||||
{"-Osmall"},
|
||||
{"-W"},
|
||||
{"-Wall"},
|
||||
{"-fobjc-arc"},
|
||||
{"-fno-objc-arc"},
|
||||
{"-fomit-frame-pointer"},
|
||||
{"-fno-omit-frame-pointer"},
|
||||
{"-fpic"},
|
||||
{"-fno-pic"},
|
||||
{"-fPIC"},
|
||||
{"-fno-PIC"},
|
||||
{"-fpie"},
|
||||
{"-fno-pie"},
|
||||
{"-fPIE"},
|
||||
{"-fno-PIE"},
|
||||
{"-fsplit-stack"},
|
||||
{"-fno-split-stack"},
|
||||
{"-fstack-xxx"},
|
||||
{"-fno-stack-xxx"},
|
||||
{"-fsanitize=hands"},
|
||||
{"-g"},
|
||||
{"-ggdb"},
|
||||
{"-march=souza"},
|
||||
{"-mcpu=123"},
|
||||
{"-mfpu=123"},
|
||||
{"-mtune=happybirthday"},
|
||||
{"-mstack-overflow"},
|
||||
{"-mno-stack-overflow"},
|
||||
{"-mmacosx-version"},
|
||||
{"-mnop-fun-dllimport"},
|
||||
{"-pthread"},
|
||||
{"-std=c99"},
|
||||
{"-xc"},
|
||||
{"-D", "FOO"},
|
||||
{"-D", "foo=bar"},
|
||||
{"-I", "."},
|
||||
{"-I", "/etc/passwd"},
|
||||
{"-I", "世界"},
|
||||
{"-framework", "Chocolate"},
|
||||
{"-x", "c"},
|
||||
{"-v"},
|
||||
}
|
||||
|
||||
var badCompilerFlags = [][]string{
|
||||
{"-D@X"},
|
||||
{"-D-X"},
|
||||
{"-F@dir"},
|
||||
{"-F-dir"},
|
||||
{"-I@dir"},
|
||||
{"-I-dir"},
|
||||
{"-O@1"},
|
||||
{"-Wa,-foo"},
|
||||
{"-W@foo"},
|
||||
{"-g@gdb"},
|
||||
{"-g-gdb"},
|
||||
{"-march=@dawn"},
|
||||
{"-march=-dawn"},
|
||||
{"-std=@c99"},
|
||||
{"-std=-c99"},
|
||||
{"-x@c"},
|
||||
{"-x-c"},
|
||||
{"-D", "@foo"},
|
||||
{"-D", "-foo"},
|
||||
{"-I", "@foo"},
|
||||
{"-I", "-foo"},
|
||||
{"-framework", "-Caffeine"},
|
||||
{"-framework", "@Home"},
|
||||
{"-x", "--c"},
|
||||
{"-x", "@obj"},
|
||||
}
|
||||
|
||||
func TestCheckCompilerFlags(t *testing.T) {
|
||||
for _, f := range goodCompilerFlags {
|
||||
if err := checkCompilerFlags("test", f); err != nil {
|
||||
t.Errorf("unexpected error for %q: %v", f, err)
|
||||
}
|
||||
}
|
||||
for _, f := range badCompilerFlags {
|
||||
if err := checkCompilerFlags("test", f); err == nil {
|
||||
t.Errorf("missing error for %q", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var goodLinkerFlags = [][]string{
|
||||
{"-Fbar"},
|
||||
{"-lbar"},
|
||||
{"-Lbar"},
|
||||
{"-fpic"},
|
||||
{"-fno-pic"},
|
||||
{"-fPIC"},
|
||||
{"-fno-PIC"},
|
||||
{"-fpie"},
|
||||
{"-fno-pie"},
|
||||
{"-fPIE"},
|
||||
{"-fno-PIE"},
|
||||
{"-fsanitize=hands"},
|
||||
{"-g"},
|
||||
{"-ggdb"},
|
||||
{"-march=souza"},
|
||||
{"-mcpu=123"},
|
||||
{"-mfpu=123"},
|
||||
{"-mtune=happybirthday"},
|
||||
{"-pic"},
|
||||
{"-pthread"},
|
||||
{"-Wl,-rpath,foo"},
|
||||
{"-Wl,-rpath,$ORIGIN/foo"},
|
||||
{"-Wl,-R", "/foo"},
|
||||
{"-Wl,-R", "foo"},
|
||||
{"-Wl,-R,foo"},
|
||||
{"-Wl,--just-symbols=foo"},
|
||||
{"-Wl,--just-symbols,foo"},
|
||||
{"-Wl,--warn-error"},
|
||||
{"-Wl,--no-warn-error"},
|
||||
{"foo.so"},
|
||||
{"_世界.dll"},
|
||||
{"./x.o"},
|
||||
{"libcgosotest.dylib"},
|
||||
{"-F", "framework"},
|
||||
{"-l", "."},
|
||||
{"-l", "/etc/passwd"},
|
||||
{"-l", "世界"},
|
||||
{"-L", "framework"},
|
||||
{"-framework", "Chocolate"},
|
||||
{"-v"},
|
||||
{"-Wl,-framework", "-Wl,Chocolate"},
|
||||
{"-Wl,-framework,Chocolate"},
|
||||
{"-Wl,-unresolved-symbols=ignore-all"},
|
||||
}
|
||||
|
||||
var badLinkerFlags = [][]string{
|
||||
{"-DFOO"},
|
||||
{"-Dfoo=bar"},
|
||||
{"-W"},
|
||||
{"-Wall"},
|
||||
{"-fobjc-arc"},
|
||||
{"-fno-objc-arc"},
|
||||
{"-fomit-frame-pointer"},
|
||||
{"-fno-omit-frame-pointer"},
|
||||
{"-fsplit-stack"},
|
||||
{"-fno-split-stack"},
|
||||
{"-fstack-xxx"},
|
||||
{"-fno-stack-xxx"},
|
||||
{"-mstack-overflow"},
|
||||
{"-mno-stack-overflow"},
|
||||
{"-mnop-fun-dllimport"},
|
||||
{"-std=c99"},
|
||||
{"-xc"},
|
||||
{"-D", "FOO"},
|
||||
{"-D", "foo=bar"},
|
||||
{"-I", "FOO"},
|
||||
{"-L", "@foo"},
|
||||
{"-L", "-foo"},
|
||||
{"-x", "c"},
|
||||
{"-D@X"},
|
||||
{"-D-X"},
|
||||
{"-I@dir"},
|
||||
{"-I-dir"},
|
||||
{"-O@1"},
|
||||
{"-Wa,-foo"},
|
||||
{"-W@foo"},
|
||||
{"-g@gdb"},
|
||||
{"-g-gdb"},
|
||||
{"-march=@dawn"},
|
||||
{"-march=-dawn"},
|
||||
{"-std=@c99"},
|
||||
{"-std=-c99"},
|
||||
{"-x@c"},
|
||||
{"-x-c"},
|
||||
{"-D", "@foo"},
|
||||
{"-D", "-foo"},
|
||||
{"-I", "@foo"},
|
||||
{"-I", "-foo"},
|
||||
{"-l", "@foo"},
|
||||
{"-l", "-foo"},
|
||||
{"-framework", "-Caffeine"},
|
||||
{"-framework", "@Home"},
|
||||
{"-Wl,-framework,-Caffeine"},
|
||||
{"-Wl,-framework", "-Wl,@Home"},
|
||||
{"-Wl,-framework", "@Home"},
|
||||
{"-Wl,-framework,Chocolate,@Home"},
|
||||
{"-x", "--c"},
|
||||
{"-x", "@obj"},
|
||||
{"-Wl,-rpath,@foo"},
|
||||
{"-Wl,-R,foo,bar"},
|
||||
{"-Wl,-R,@foo"},
|
||||
{"-Wl,--just-symbols,@foo"},
|
||||
{"../x.o"},
|
||||
}
|
||||
|
||||
func TestCheckLinkerFlags(t *testing.T) {
|
||||
for _, f := range goodLinkerFlags {
|
||||
if err := checkLinkerFlags("test", f); err != nil {
|
||||
t.Errorf("unexpected error for %q: %v", f, err)
|
||||
}
|
||||
}
|
||||
for _, f := range badLinkerFlags {
|
||||
if err := checkLinkerFlags("test", f); err == nil {
|
||||
t.Errorf("missing error for %q", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFlagAllowDisallow(t *testing.T) {
|
||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
||||
t.Fatalf("missing error for -disallow")
|
||||
}
|
||||
os.Setenv("CGO_TEST_ALLOW", "-disallo")
|
||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
||||
t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
|
||||
}
|
||||
os.Setenv("CGO_TEST_ALLOW", "-disallow")
|
||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
|
||||
t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
|
||||
}
|
||||
os.Unsetenv("CGO_TEST_ALLOW")
|
||||
|
||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
|
||||
t.Fatalf("unexpected error for -Wall: %v", err)
|
||||
}
|
||||
os.Setenv("CGO_TEST_DISALLOW", "-Wall")
|
||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
||||
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
|
||||
}
|
||||
os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
|
||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
||||
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
|
||||
}
|
||||
|
||||
os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
|
||||
os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
|
||||
if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
|
||||
t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
|
||||
}
|
||||
if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
|
||||
t.Fatalf("missing error for -fplugin=lint.so: %v", err)
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
package main
|
||||
|
||||
import "C"
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#define foo 3
|
||||
#define bar foo
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
Foo = C.foo
|
||||
Bar = C.bar
|
||||
)
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
const C.bar = C.foo
|
||||
const C.foo = 3
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#warning some warning
|
||||
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
} point_t;
|
||||
|
||||
typedef someType noType; // undefined type
|
||||
|
||||
#define SOME_CONST_1 5) // invalid const syntax
|
||||
#define SOME_CONST_2 6) // const not used (so no error)
|
||||
#define SOME_CONST_3 1234 // const too large for byte
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Make sure that errors for the following lines won't change with future
|
||||
// additions to the CGo preamble.
|
||||
//line errors.go:100
|
||||
var (
|
||||
// constant too large
|
||||
_ C.uint8_t = 2 << 10
|
||||
|
||||
// z member does not exist
|
||||
_ C.point_t = C.point_t{z: 3}
|
||||
|
||||
// constant has syntax error
|
||||
_ = C.SOME_CONST_1
|
||||
|
||||
_ byte = C.SOME_CONST_3
|
||||
)
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
// CGo errors:
|
||||
// testdata/errors.go:4:2: warning: some warning
|
||||
// testdata/errors.go:11:9: error: unknown type name 'someType'
|
||||
// testdata/errors.go:13:23: unexpected token )
|
||||
|
||||
// Type checking errors after CGo processing:
|
||||
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
|
||||
// testdata/errors.go:105: unknown field z in struct literal
|
||||
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
|
||||
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
|
||||
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
const C.SOME_CONST_3 = 1234
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
type C.point_t = struct {
|
||||
x C.int
|
||||
y C.int
|
||||
}
|
||||
Vendored
+26
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
// this name doesn't exist
|
||||
#cgo NOFLAGS: -foo
|
||||
|
||||
// unknown flag
|
||||
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
|
||||
|
||||
#cgo CFLAGS: -DFOO
|
||||
|
||||
#if defined(FOO)
|
||||
#define BAR 3
|
||||
#else
|
||||
#define BAR 5
|
||||
#endif
|
||||
|
||||
#if defined(NOTDEFINED)
|
||||
#warning flag must not be defined
|
||||
#endif
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var (
|
||||
_ = C.BAR
|
||||
)
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
// CGo errors:
|
||||
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
||||
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
||||
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
const C.BAR = 3
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
Vendored
+165
@@ -0,0 +1,165 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
// Simple typedef.
|
||||
typedef int myint;
|
||||
|
||||
// Structs, with or without name.
|
||||
typedef struct {
|
||||
int x;
|
||||
int y;
|
||||
} point2d_t;
|
||||
typedef struct point3d {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
} point3d_t;
|
||||
|
||||
// Structs with reserved field names.
|
||||
struct type1 {
|
||||
// All these fields should be renamed.
|
||||
int type;
|
||||
int _type;
|
||||
int __type;
|
||||
};
|
||||
struct type2 {
|
||||
// This field should not be renamed.
|
||||
int _type;
|
||||
};
|
||||
|
||||
// Unions.
|
||||
typedef union {
|
||||
// Union should be treated as a struct.
|
||||
int i;
|
||||
} union1_t;
|
||||
typedef union {
|
||||
// Union must contain a single field and have special getters/setters.
|
||||
int i;
|
||||
double d;
|
||||
short s;
|
||||
} union3_t;
|
||||
typedef union union2d {
|
||||
int i;
|
||||
double d[2];
|
||||
} union2d_t;
|
||||
typedef union {
|
||||
unsigned char arr[10];
|
||||
} unionarray_t;
|
||||
|
||||
// Nested structs and unions.
|
||||
typedef struct {
|
||||
point2d_t begin;
|
||||
point2d_t end;
|
||||
int tag;
|
||||
union {
|
||||
point2d_t area;
|
||||
point3d_t solid;
|
||||
} coord;
|
||||
} struct_nested_t;
|
||||
typedef union {
|
||||
point3d_t point;
|
||||
unionarray_t array;
|
||||
union3_t thing;
|
||||
} union_nested_t;
|
||||
|
||||
// Enums. These define constant numbers. All these constants must be given the
|
||||
// correct number.
|
||||
typedef enum option {
|
||||
optionA,
|
||||
optionB,
|
||||
optionC = -5,
|
||||
optionD,
|
||||
optionE = 10,
|
||||
optionF,
|
||||
optionG,
|
||||
} option_t;
|
||||
enum unused {
|
||||
unused1 = 5,
|
||||
};
|
||||
|
||||
// Anonymous enum.
|
||||
typedef enum {
|
||||
option2A = 20,
|
||||
} option2_t;
|
||||
|
||||
// Various types that are usually translated directly to Go types, but storing
|
||||
// them in a struct reveals them.
|
||||
typedef struct {
|
||||
float f;
|
||||
double d;
|
||||
int *ptr;
|
||||
} types_t;
|
||||
|
||||
// Arrays.
|
||||
typedef int myIntArray[10];
|
||||
|
||||
// Bitfields.
|
||||
typedef struct {
|
||||
unsigned char start;
|
||||
unsigned char a : 5;
|
||||
unsigned char b : 1;
|
||||
unsigned char c : 2;
|
||||
unsigned char :0; // new field
|
||||
unsigned char d : 6;
|
||||
unsigned char e : 3;
|
||||
// Note that C++ allows bitfields bigger than the underlying type.
|
||||
} bitfield_t;
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var (
|
||||
// Simple typedefs.
|
||||
_ C.myint
|
||||
|
||||
// Structs.
|
||||
_ C.point2d_t
|
||||
_ C.point3d_t
|
||||
_ C.struct_point3d
|
||||
|
||||
// Structs with reserved field names.
|
||||
_ C.struct_type1
|
||||
_ C.struct_type2
|
||||
|
||||
// Unions.
|
||||
_ C.union1_t
|
||||
_ C.union3_t
|
||||
_ C.union2d_t
|
||||
_ C.unionarray_t
|
||||
|
||||
// Nested structs and unions.
|
||||
_ C.struct_nested_t
|
||||
_ C.union_nested_t
|
||||
|
||||
// Enums (anonymous and named).
|
||||
_ C.option_t
|
||||
_ C.enum_option
|
||||
_ C.option2_t
|
||||
|
||||
// Various types.
|
||||
_ C.types_t
|
||||
|
||||
// Arrays.
|
||||
_ C.myIntArray
|
||||
)
|
||||
|
||||
// Test bitfield accesses.
|
||||
func accessBitfields() {
|
||||
var x C.bitfield_t
|
||||
x.start = 3
|
||||
x.set_bitfield_a(4)
|
||||
x.set_bitfield_b(1)
|
||||
x.set_bitfield_c(2)
|
||||
x.d = 10
|
||||
x.e = 5
|
||||
var _ C.uchar = x.bitfield_a()
|
||||
}
|
||||
|
||||
// Test union accesses.
|
||||
func accessUnion() {
|
||||
var union1 C.union1_t
|
||||
union1.i = 5
|
||||
|
||||
var union2d C.union2d_t
|
||||
var _ *C.int = union2d.unionfield_i()
|
||||
var _ *[2]float64 = union2d.unionfield_d()
|
||||
}
|
||||
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
const C.option2A = 20
|
||||
const C.optionA = 0
|
||||
const C.optionB = 1
|
||||
const C.optionC = -5
|
||||
const C.optionD = -4
|
||||
const C.optionE = 10
|
||||
const C.optionF = 11
|
||||
const C.optionG = 12
|
||||
const C.unused1 = 5
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
type C.bitfield_t = C.struct_4
|
||||
type C.myIntArray = [10]C.int
|
||||
type C.myint = C.int
|
||||
type C.option2_t = C.uint
|
||||
type C.option_t = C.enum_option
|
||||
type C.point2d_t = struct {
|
||||
x C.int
|
||||
y C.int
|
||||
}
|
||||
type C.point3d_t = C.struct_point3d
|
||||
type C.struct_nested_t = struct {
|
||||
begin C.point2d_t
|
||||
end C.point2d_t
|
||||
tag C.int
|
||||
|
||||
coord C.union_2
|
||||
}
|
||||
type C.types_t = struct {
|
||||
f float32
|
||||
d float64
|
||||
ptr *C.int
|
||||
}
|
||||
type C.union1_t = struct{ i C.int }
|
||||
type C.union2d_t = C.union_union2d
|
||||
type C.union3_t = C.union_1
|
||||
type C.union_nested_t = C.union_3
|
||||
type C.unionarray_t = struct{ arr [10]C.uchar }
|
||||
|
||||
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
|
||||
func (s *C.struct_4) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
|
||||
func (s *C.struct_4) bitfield_b() C.uchar {
|
||||
return s.__bitfield_1 >> 5 & 0x1
|
||||
}
|
||||
func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
|
||||
func (s *C.struct_4) bitfield_c() C.uchar {
|
||||
return s.__bitfield_1 >> 6
|
||||
}
|
||||
func (s *C.struct_4) set_bitfield_c(value C.uchar,
|
||||
|
||||
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
|
||||
|
||||
type C.struct_4 struct {
|
||||
start C.uchar
|
||||
__bitfield_1 C.uchar
|
||||
|
||||
d C.uchar
|
||||
e C.uchar
|
||||
}
|
||||
type C.struct_point3d struct {
|
||||
x C.int
|
||||
y C.int
|
||||
z C.int
|
||||
}
|
||||
type C.struct_type1 struct {
|
||||
_type C.int
|
||||
__type C.int
|
||||
___type C.int
|
||||
}
|
||||
type C.struct_type2 struct{ _type C.int }
|
||||
|
||||
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
|
||||
|
||||
type C.union_1 struct{ $union uint64 }
|
||||
|
||||
func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||
|
||||
type C.union_2 struct{ $union [3]uint32 }
|
||||
|
||||
func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) }
|
||||
|
||||
type C.union_3 struct{ $union [2]uint64 }
|
||||
|
||||
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_union2d) unionfield_d() *[2]float64 { return (*[2]float64)(unsafe.Pointer(&union.$union)) }
|
||||
|
||||
type C.union_union2d struct{ $union [2]uint64 }
|
||||
type C.enum_option C.int
|
||||
type C.enum_unused C.uint
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"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 execCommand(cmdNames []string, args ...string) error {
|
||||
for _, cmdName := range cmdNames {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||
// this command was not found, try the next
|
||||
continue
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
// Package compileopts contains the configuration for a single to-be-built
|
||||
// binary.
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Config keeps all configuration affecting the build in a single struct.
|
||||
type Config struct {
|
||||
Options *Options
|
||||
Target *TargetSpec
|
||||
GoMinorVersion int
|
||||
ClangHeaders string // Clang built-in header include path
|
||||
TestConfig TestConfig
|
||||
}
|
||||
|
||||
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||
func (c *Config) Triple() string {
|
||||
return c.Target.Triple
|
||||
}
|
||||
|
||||
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
|
||||
// empty string if the CPU name is not known.
|
||||
func (c *Config) CPU() string {
|
||||
return c.Target.CPU
|
||||
}
|
||||
|
||||
// Features returns a list of features this CPU supports. For example, for a
|
||||
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
|
||||
// empty list will be returned.
|
||||
func (c *Config) Features() []string {
|
||||
return c.Target.Features
|
||||
}
|
||||
|
||||
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
||||
// for example, bare-metal targets will usually pretend to be linux to get the
|
||||
// standard library to compile.
|
||||
func (c *Config) GOOS() string {
|
||||
return c.Target.GOOS
|
||||
}
|
||||
|
||||
// GOARCH returns the GOARCH of the target. This might not always be the actual
|
||||
// archtecture: for example, the AVR target is not supported by the Go standard
|
||||
// library so such targets will usually pretend to be linux/arm.
|
||||
func (c *Config) GOARCH() string {
|
||||
return c.Target.GOARCH
|
||||
}
|
||||
|
||||
// BuildTags returns the complete list of build tags used during this build.
|
||||
func (c *Config) BuildTags() []string {
|
||||
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
|
||||
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
|
||||
tags = append(tags, extraTags...)
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// GC returns the garbage collection strategy in use on this platform. Valid
|
||||
// values are "none", "leaking", and "conservative".
|
||||
func (c *Config) GC() string {
|
||||
if c.Options.GC != "" {
|
||||
return c.Options.GC
|
||||
}
|
||||
if c.Target.GC != "" {
|
||||
return c.Target.GC
|
||||
}
|
||||
return "conservative"
|
||||
}
|
||||
|
||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||
// that can be traced by the garbage collector.
|
||||
func (c *Config) NeedsStackObjects() bool {
|
||||
if c.GC() != "conservative" {
|
||||
return false
|
||||
}
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "baremetal" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
|
||||
// and "tasks".
|
||||
func (c *Config) Scheduler() string {
|
||||
if c.Options.Scheduler != "" {
|
||||
return c.Options.Scheduler
|
||||
}
|
||||
if c.Target.Scheduler != "" {
|
||||
return c.Target.Scheduler
|
||||
}
|
||||
// Fall back to coroutines, which are supported everywhere.
|
||||
return "coroutines"
|
||||
}
|
||||
|
||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||
// instruction).
|
||||
func (c *Config) PanicStrategy() string {
|
||||
return c.Options.PanicStrategy
|
||||
}
|
||||
|
||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||
// preprocessing.
|
||||
func (c *Config) CFlags() []string {
|
||||
cflags := append([]string{}, c.Options.CFlags...)
|
||||
for _, flag := range c.Target.CFlags {
|
||||
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
|
||||
}
|
||||
return cflags
|
||||
}
|
||||
|
||||
// LDFlags returns the flags to pass to the linker. A few more flags are needed
|
||||
// (like the one for the compiler runtime), but this represents the majority of
|
||||
// the flags.
|
||||
func (c *Config) LDFlags() []string {
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// Merge and adjust LDFlags.
|
||||
ldflags := append([]string{}, c.Options.LDFlags...)
|
||||
for _, flag := range c.Target.LDFlags {
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
ldflags = append(ldflags, "-L", root)
|
||||
if c.Target.GOARCH == "wasm" {
|
||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||
// size).
|
||||
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
|
||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||
}
|
||||
if c.Target.LinkerScript != "" {
|
||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||
}
|
||||
return ldflags
|
||||
}
|
||||
|
||||
// ExtraFiles returns the list of extra files to be built and linked with the
|
||||
// executable. This can include extra C and assembly files.
|
||||
func (c *Config) ExtraFiles() []string {
|
||||
return c.Target.ExtraFiles
|
||||
}
|
||||
|
||||
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
|
||||
// enable this for debugging.
|
||||
func (c *Config) DumpSSA() bool {
|
||||
return c.Options.DumpSSA
|
||||
}
|
||||
|
||||
// VerifyIR returns whether to run extra checks on the IR. This is normally
|
||||
// disabled but enabled during testing.
|
||||
func (c *Config) VerifyIR() bool {
|
||||
return c.Options.VerifyIR
|
||||
}
|
||||
|
||||
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
|
||||
// and similar.
|
||||
func (c *Config) Debug() bool {
|
||||
return c.Options.Debug
|
||||
}
|
||||
|
||||
// Programmer returns the flash method and OpenOCD interface name given a
|
||||
// particular configuration. It may either be all configured in the target JSON
|
||||
// file or be modified using the -programmmer command-line option.
|
||||
func (c *Config) Programmer() (method, openocdInterface string) {
|
||||
switch c.Options.Programmer {
|
||||
case "":
|
||||
// No configuration supplied.
|
||||
return c.Target.FlashMethod, c.Target.OpenOCDInterface
|
||||
case "openocd", "msd", "command":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||
default:
|
||||
// The -programmer flag specifies something else, assume it specifies
|
||||
// the OpenOCD interface name.
|
||||
return "openocd", c.Options.Programmer
|
||||
}
|
||||
}
|
||||
|
||||
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
|
||||
// This list of command-line arguments is based on the various OpenOCD-related
|
||||
// flags in the target specification.
|
||||
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
||||
_, openocdInterface := c.Programmer()
|
||||
if openocdInterface == "" {
|
||||
return nil, errors.New("OpenOCD programmer not set")
|
||||
}
|
||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
|
||||
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
||||
}
|
||||
if c.Target.OpenOCDTarget == "" {
|
||||
return nil, errors.New("OpenOCD chip not set")
|
||||
}
|
||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
|
||||
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
||||
}
|
||||
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
||||
}
|
||||
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
||||
if c.Target.OpenOCDTransport != "" {
|
||||
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||
}
|
||||
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
||||
return args, nil
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package compileopts
|
||||
|
||||
// Options contains extra options to give to the compiler. These options are
|
||||
// usually passed from the command line.
|
||||
type Options struct {
|
||||
Target 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 TestConfig
|
||||
Programmer string
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package main
|
||||
package compileopts
|
||||
|
||||
// This file loads a target specification from a JSON file.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Target specification for a given target. Used for bare metal targets.
|
||||
@@ -19,24 +20,32 @@ import (
|
||||
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
|
||||
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
|
||||
type TargetSpec struct {
|
||||
Inherits []string `json:"inherits"`
|
||||
Triple string `json:"llvm-target"`
|
||||
CPU string `json:"cpu"`
|
||||
GOOS string `json:"goos"`
|
||||
GOARCH string `json:"goarch"`
|
||||
BuildTags []string `json:"build-tags"`
|
||||
GC string `json:"gc"`
|
||||
Compiler string `json:"compiler"`
|
||||
Linker string `json:"linker"`
|
||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||
CFlags []string `json:"cflags"`
|
||||
LDFlags []string `json:"ldflags"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
Emulator []string `json:"emulator"`
|
||||
Flasher string `json:"flash"`
|
||||
OCDDaemon []string `json:"ocd-daemon"`
|
||||
GDB string `json:"gdb"`
|
||||
GDBCmds []string `json:"gdb-initial-cmds"`
|
||||
Inherits []string `json:"inherits"`
|
||||
Triple string `json:"llvm-target"`
|
||||
CPU string `json:"cpu"`
|
||||
Features []string `json:"features"`
|
||||
GOOS string `json:"goos"`
|
||||
GOARCH string `json:"goarch"`
|
||||
BuildTags []string `json:"build-tags"`
|
||||
GC string `json:"gc"`
|
||||
Scheduler string `json:"scheduler"`
|
||||
Compiler string `json:"compiler"`
|
||||
Linker string `json:"linker"`
|
||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||
CFlags []string `json:"cflags"`
|
||||
LDFlags []string `json:"ldflags"`
|
||||
LinkerScript string `json:"linkerscript"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
Emulator []string `json:"emulator"`
|
||||
FlashCommand string `json:"flash-command"`
|
||||
GDB string `json:"gdb"`
|
||||
PortReset string `json:"flash-1200-bps-reset"`
|
||||
FlashMethod string `json:"flash-method"`
|
||||
FlashVolume string `json:"msd-volume-name"`
|
||||
FlashFilename string `json:"msd-firmware-name"`
|
||||
OpenOCDInterface string `json:"openocd-interface"`
|
||||
OpenOCDTarget string `json:"openocd-target"`
|
||||
OpenOCDTransport string `json:"openocd-transport"`
|
||||
}
|
||||
|
||||
// copyProperties copies all properties that are set in spec2 into itself.
|
||||
@@ -50,6 +59,7 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||
if spec2.CPU != "" {
|
||||
spec.CPU = spec2.CPU
|
||||
}
|
||||
spec.Features = append(spec.Features, spec2.Features...)
|
||||
if spec2.GOOS != "" {
|
||||
spec.GOOS = spec2.GOOS
|
||||
}
|
||||
@@ -60,6 +70,9 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||
if spec2.GC != "" {
|
||||
spec.GC = spec2.GC
|
||||
}
|
||||
if spec2.Scheduler != "" {
|
||||
spec.Scheduler = spec2.Scheduler
|
||||
}
|
||||
if spec2.Compiler != "" {
|
||||
spec.Compiler = spec2.Compiler
|
||||
}
|
||||
@@ -71,21 +84,39 @@ func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||
}
|
||||
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
|
||||
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
|
||||
if spec2.LinkerScript != "" {
|
||||
spec.LinkerScript = spec2.LinkerScript
|
||||
}
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
|
||||
if len(spec2.Emulator) != 0 {
|
||||
spec.Emulator = spec2.Emulator
|
||||
}
|
||||
if spec2.Flasher != "" {
|
||||
spec.Flasher = spec2.Flasher
|
||||
}
|
||||
if len(spec2.OCDDaemon) != 0 {
|
||||
spec.OCDDaemon = spec2.OCDDaemon
|
||||
if spec2.FlashCommand != "" {
|
||||
spec.FlashCommand = spec2.FlashCommand
|
||||
}
|
||||
if spec2.GDB != "" {
|
||||
spec.GDB = spec2.GDB
|
||||
}
|
||||
if len(spec2.GDBCmds) != 0 {
|
||||
spec.GDBCmds = spec2.GDBCmds
|
||||
if spec2.PortReset != "" {
|
||||
spec.PortReset = spec2.PortReset
|
||||
}
|
||||
if spec2.FlashMethod != "" {
|
||||
spec.FlashMethod = spec2.FlashMethod
|
||||
}
|
||||
if spec2.FlashVolume != "" {
|
||||
spec.FlashVolume = spec2.FlashVolume
|
||||
}
|
||||
if spec2.FlashFilename != "" {
|
||||
spec.FlashFilename = spec2.FlashFilename
|
||||
}
|
||||
if spec2.OpenOCDInterface != "" {
|
||||
spec.OpenOCDInterface = spec2.OpenOCDInterface
|
||||
}
|
||||
if spec2.OpenOCDTarget != "" {
|
||||
spec.OpenOCDTarget = spec2.OpenOCDTarget
|
||||
}
|
||||
if spec2.OpenOCDTransport != "" {
|
||||
spec.OpenOCDTransport = spec2.OpenOCDTransport
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,10 +131,18 @@ func (spec *TargetSpec) load(r io.Reader) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadFromName loads the given target from the targets/ directory inside the
|
||||
// compiler sources.
|
||||
func (spec *TargetSpec) loadFromName(name string) error {
|
||||
path := filepath.Join(sourceDir(), "targets", strings.ToLower(name)+".json")
|
||||
// loadFromGivenStr loads the TargetSpec from the given string that could be:
|
||||
// - targets/ directory inside the compiler sources
|
||||
// - a relative or absolute path to custom (project specific) target specification .json file;
|
||||
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
|
||||
// as well as path to custom files (ex. myAwesomeProject.json)
|
||||
func (spec *TargetSpec) loadFromGivenStr(str string) error {
|
||||
path := ""
|
||||
if strings.HasSuffix(str, ".json") {
|
||||
path, _ = filepath.Abs(str)
|
||||
} else {
|
||||
path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
|
||||
}
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -118,7 +157,7 @@ func (spec *TargetSpec) resolveInherits() error {
|
||||
newSpec := &TargetSpec{}
|
||||
for _, name := range spec.Inherits {
|
||||
subtarget := &TargetSpec{}
|
||||
err := subtarget.loadFromName(name)
|
||||
err := subtarget.loadFromGivenStr(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -141,14 +180,8 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
if target == "" {
|
||||
// Configure based on GOOS/GOARCH environment variables (falling back to
|
||||
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
|
||||
goos := os.Getenv("GOOS")
|
||||
if goos == "" {
|
||||
goos = runtime.GOOS
|
||||
}
|
||||
goarch := os.Getenv("GOARCH")
|
||||
if goarch == "" {
|
||||
goarch = runtime.GOARCH
|
||||
}
|
||||
goos := goenv.Get("GOOS")
|
||||
goarch := goenv.Get("GOARCH")
|
||||
llvmos := goos
|
||||
llvmarch := map[string]string{
|
||||
"386": "i386",
|
||||
@@ -168,7 +201,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
// See whether there is a target specification for this target (e.g.
|
||||
// Arduino).
|
||||
spec := &TargetSpec{}
|
||||
err := spec.loadFromName(target)
|
||||
err := spec.loadFromGivenStr(target)
|
||||
if err == nil {
|
||||
// Successfully loaded this target from a built-in .json file. Make sure
|
||||
// it includes all parents as specified in the "inherits" key.
|
||||
@@ -185,7 +218,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
// Load target from given triple, ignore GOOS/GOARCH environment
|
||||
// variables.
|
||||
tripleSplit := strings.Split(target, "-")
|
||||
if len(tripleSplit) == 1 {
|
||||
if len(tripleSplit) < 3 {
|
||||
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
|
||||
}
|
||||
goos := tripleSplit[2]
|
||||
@@ -208,14 +241,15 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
// No target spec available. Use the default one, useful on most systems
|
||||
// with a regular OS.
|
||||
spec := TargetSpec{
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Compiler: "clang",
|
||||
Linker: "cc",
|
||||
GDB: "gdb",
|
||||
GDBCmds: []string{"run"},
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Compiler: "clang",
|
||||
Linker: "cc",
|
||||
GDB: "gdb",
|
||||
PortReset: "false",
|
||||
FlashMethod: "native",
|
||||
}
|
||||
if goos == "darwin" {
|
||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||
@@ -241,130 +275,3 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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(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 getGopath() string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
if gopath != "" {
|
||||
return gopath
|
||||
}
|
||||
|
||||
// fallback
|
||||
home := getHomeDir()
|
||||
return filepath.Join(home, "go")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package compileopts
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLoadTarget(t *testing.T) {
|
||||
_, err := LoadTarget("arduino")
|
||||
if err != nil {
|
||||
t.Error("LoadTarget test failed:", err)
|
||||
}
|
||||
|
||||
_, err = LoadTarget("notexist")
|
||||
if err == nil {
|
||||
t.Error("LoadTarget should have failed with non existing target")
|
||||
}
|
||||
|
||||
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
|
||||
t.Error("LoadTarget failed for wrong reason:", err)
|
||||
}
|
||||
}
|
||||
+24
-7
@@ -22,7 +22,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
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.(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||
@@ -42,7 +42,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("lookuppanic", nil, "")
|
||||
c.createRuntimeCall("lookupPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
@@ -56,7 +56,7 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||
// logic is the same in both cases.
|
||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||
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.
|
||||
@@ -71,6 +71,9 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = high.Type()
|
||||
}
|
||||
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||
capacityType = max.Type()
|
||||
}
|
||||
if capacityType != capacity.Type() {
|
||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||
}
|
||||
@@ -90,6 +93,13 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = c.builder.CreateZExt(max, capacityType, "")
|
||||
} else {
|
||||
max = c.builder.CreateSExt(max, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||
@@ -97,13 +107,15 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("slicepanic", nil, "")
|
||||
c.createRuntimeCall("slicePanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
@@ -114,6 +126,11 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V
|
||||
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||
// pointer dereferences exist in valid Go code.
|
||||
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||
// Check whether we need to emit this check at all.
|
||||
if !ptr.IsAGlobalValue().IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether this is a nil pointer.
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||
@@ -141,7 +158,7 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("nilpanic", nil, "")
|
||||
c.createRuntimeCall("nilPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
|
||||
+6
-2
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -20,6 +21,9 @@ 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.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||
@@ -38,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:
|
||||
@@ -163,7 +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 := c.getZeroValue(t)
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
|
||||
+186
-34
@@ -6,67 +6,53 @@ package compiler
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitMakeChan returns a new channel value for the given channel type.
|
||||
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||
size := c.targetData.TypeAllocSize(chanType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
||||
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
|
||||
return ptr, nil
|
||||
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) {
|
||||
valueType := c.getLLVMType(instr.X.Type())
|
||||
ch := c.getValue(frame, instr.Chan)
|
||||
chanValue := c.getValue(frame, instr.X)
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
|
||||
// store value-to-send
|
||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
c.builder.SetInsertPointBefore(coroutine)
|
||||
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||
valueType := c.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
|
||||
// Do the send.
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
|
||||
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
||||
// This is a bit dirty, but it works (at least in LLVM 8).
|
||||
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
||||
// 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())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch := c.getValue(frame, unop.X)
|
||||
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||
|
||||
// Allocate memory to receive into.
|
||||
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||
c.builder.SetInsertPointBefore(coroutine)
|
||||
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
|
||||
// Do the receive.
|
||||
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
@@ -78,8 +64,174 @@ func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||
ch := c.getValue(frame, param)
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||
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 := llvmutil.CreateEntryBlockAlloca(c.builder, 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.createTemporaryAlloca(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, statesI8, statesSize := c.createTemporaryAlloca(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)
|
||||
|
||||
// Do the select in the runtime.
|
||||
var results llvm.Value
|
||||
if expr.Blocking {
|
||||
// Stack-allocate operation structures.
|
||||
// If these were simply created as a slice, they would heap-allocate.
|
||||
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
}, "select.block")
|
||||
|
||||
results = c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
||||
}, "select.result")
|
||||
|
||||
// Terminate the lifetime of the operation structures.
|
||||
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
||||
} else {
|
||||
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
}, "select.result")
|
||||
}
|
||||
|
||||
// Terminate the lifetime of the states alloca.
|
||||
c.emitLifetimeEnd(statesI8, statesSize)
|
||||
|
||||
// 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,172 @@
|
||||
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) error {
|
||||
// prevent infinite recursion for self-referential types
|
||||
if _, ok := checked[t]; ok {
|
||||
return nil
|
||||
}
|
||||
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
|
||||
return fmt.Errorf("type %q uses global context", t.String())
|
||||
default:
|
||||
// we used some other context by accident
|
||||
return 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 {
|
||||
return 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 i, v := range t.ParamTypes() {
|
||||
if err := c.checkType(v, checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error())
|
||||
}
|
||||
}
|
||||
if err := c.checkType(t.ReturnType(), checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error())
|
||||
}
|
||||
case llvm.StructTypeKind:
|
||||
// check all elements
|
||||
for i, v := range t.StructElementTypes() {
|
||||
if err := c.checkType(v, checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error())
|
||||
}
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
// check element type
|
||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
|
||||
}
|
||||
case llvm.PointerTypeKind:
|
||||
// check underlying type
|
||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
|
||||
}
|
||||
case llvm.VectorTypeKind:
|
||||
// check element type
|
||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// check type
|
||||
if err := c.checkType(v.Type(), types, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify type of value: %s", err.Error())
|
||||
}
|
||||
|
||||
// check if this is an undefined void
|
||||
if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind {
|
||||
return errors.New("encountered undefined void value")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// check value properties
|
||||
if err := c.checkValue(inst, types, specials); err != nil {
|
||||
return errorAt(inst, err.Error())
|
||||
}
|
||||
|
||||
// check operands
|
||||
for i := 0; i < inst.OperandsCount(); i++ {
|
||||
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
||||
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check basic block value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
||||
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
|
||||
}
|
||||
|
||||
// check instructions
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if err := c.checkInstruction(inst, types, specials); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check function value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(fn, types, specials); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
|
||||
}
|
||||
|
||||
// check basic blocks
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkModule() []error {
|
||||
// check for any context mismatches
|
||||
var errs []error
|
||||
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
|
||||
errs = append(errs, errors.New("module uses global context"))
|
||||
default:
|
||||
// we used some other context by accident
|
||||
errs = append(errs, 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) {
|
||||
errs = append(errs, c.checkFunction(fn, types, specials)...)
|
||||
}
|
||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||
if err := c.checkValue(g, types, specials); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
+348
-337
File diff suppressed because it is too large
Load Diff
+60
-12
@@ -14,6 +14,7 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -29,11 +30,45 @@ 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)
|
||||
}
|
||||
|
||||
// isInLoop checks if there is a path from a basic block to itself.
|
||||
func isInLoop(start *ssa.BasicBlock) bool {
|
||||
// Use a breadth-first search to scan backwards through the block graph.
|
||||
queue := []*ssa.BasicBlock{start}
|
||||
checked := map[*ssa.BasicBlock]struct{}{}
|
||||
|
||||
for len(queue) > 0 {
|
||||
// pop a block off of the queue
|
||||
block := queue[len(queue)-1]
|
||||
queue = queue[:len(queue)-1]
|
||||
|
||||
// Search through predecessors.
|
||||
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
|
||||
// Defers are often placed near the start of the function.
|
||||
for _, pred := range block.Preds {
|
||||
if pred == start {
|
||||
// cycle found
|
||||
return true
|
||||
}
|
||||
|
||||
if _, ok := checked[pred]; ok {
|
||||
// block already checked
|
||||
continue
|
||||
}
|
||||
|
||||
// add to queue and checked map
|
||||
queue = append(queue, pred)
|
||||
checked[pred] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// emitDefer emits a single defer instruction, to be run when this function
|
||||
// returns.
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
@@ -122,13 +157,26 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
|
||||
// Make a struct out of the collected values to put in the defer frame.
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFrame := c.getZeroValue(deferFrameType)
|
||||
deferFrame := llvm.ConstNull(deferFrameType)
|
||||
for i, value := range values {
|
||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||
}
|
||||
|
||||
// Put this struct in an alloca.
|
||||
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||
// Put this struct in an allocation.
|
||||
var alloca llvm.Value
|
||||
if !isInLoop(instr.Block()) {
|
||||
// This can safely use a stack allocation.
|
||||
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
|
||||
} else {
|
||||
// This may be hit a variable number of times, so use a heap allocation.
|
||||
size := c.targetData.TypeAllocSize(deferFrameType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||
}
|
||||
if c.NeedsStackObjects() {
|
||||
c.trackPointer(alloca)
|
||||
}
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
@@ -154,10 +202,10 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// }
|
||||
|
||||
// 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:
|
||||
@@ -189,7 +237,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// 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) {
|
||||
@@ -200,7 +248,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
}
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.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 {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||
}
|
||||
@@ -231,7 +279,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// Direct call.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||
}
|
||||
@@ -260,7 +308,7 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
case *ssa.MakeClosure:
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.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++ {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
@@ -16,3 +20,42 @@ func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
// errorAt returns an error value at the location of the instruction.
|
||||
// The location information may not be complete as it depends on debug
|
||||
// information in the IR.
|
||||
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||
return scanner.Error{
|
||||
Pos: getPosition(inst),
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// getPosition returns the position information for the given value, as far as
|
||||
// it is available.
|
||||
func getPosition(val llvm.Value) token.Position {
|
||||
if !val.IsAInstruction().IsNil() {
|
||||
loc := val.InstructionDebugLoc()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.LocationScope().ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.LocationLine()),
|
||||
Column: int(loc.LocationColumn()),
|
||||
}
|
||||
} else if !val.IsAFunction().IsNil() {
|
||||
loc := val.Subprogram()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.SubprogramLine()),
|
||||
}
|
||||
} else {
|
||||
return token.Position{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,269 +0,0 @@
|
||||
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.mod.GetTypeByName("runtime.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(fn)
|
||||
// if func.rawPtr == nil {
|
||||
// runtime.nilpanic()
|
||||
// }
|
||||
// result := func.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.
|
||||
var funcCall llvm.Value
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||
panic("expected inttoptr")
|
||||
}
|
||||
for _, ptrUse := range getUses(inttoptr) {
|
||||
if !ptrUse.IsABitCastInst().IsNil() {
|
||||
for _, bitcastUse := range getUses(ptrUse) {
|
||||
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
|
||||
panic("expected a call to runtime.isnil")
|
||||
}
|
||||
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||
bitcastUse.EraseFromParentAsInstruction()
|
||||
}
|
||||
ptrUse.EraseFromParentAsInstruction()
|
||||
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
|
||||
if !funcCall.IsNil() {
|
||||
panic("multiple calls on a single runtime.getFuncPtr")
|
||||
}
|
||||
funcCall = ptrUse
|
||||
} else {
|
||||
panic("unexpected getFuncPtrCall")
|
||||
}
|
||||
}
|
||||
}
|
||||
if funcCall.IsNil() {
|
||||
panic("expected exactly one call use of a runtime.getFuncPtr")
|
||||
}
|
||||
|
||||
// The block that cannot be reached with correct funcValues (to
|
||||
// help the optimizer).
|
||||
c.builder.SetInsertPointBefore(funcCall)
|
||||
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
|
||||
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Create the switch.
|
||||
c.builder.SetInsertPointBefore(funcCall)
|
||||
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||
|
||||
// Split right after the switch. We will need to insert a few
|
||||
// basic blocks in this gap.
|
||||
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||
|
||||
// The 0 case, which is actually a nil check.
|
||||
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
|
||||
c.builder.SetInsertPointAtEnd(nilBlock)
|
||||
c.createRuntimeCall("nilpanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
||||
|
||||
// Gather the list of parameters for every call we're going to
|
||||
// make.
|
||||
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
|
||||
for i := range callParams {
|
||||
callParams[i] = funcCall.Operand(i)
|
||||
}
|
||||
|
||||
// If the call produces a value, we need to get it using a PHI
|
||||
// node.
|
||||
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
||||
phiValues := make([]llvm.Value, len(functions))
|
||||
for i, fn := range functions {
|
||||
// Insert a switch case.
|
||||
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||
c.builder.SetInsertPointAtEnd(bb)
|
||||
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
|
||||
c.builder.CreateBr(nextBlock)
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||
phiBlocks[i] = bb
|
||||
phiValues[i] = result
|
||||
}
|
||||
// Create the PHI node so that the call result flows into the
|
||||
// next block (after the split). This is only necessary when the
|
||||
// call produced a value.
|
||||
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||
phi := c.builder.CreatePHI(funcCall.Type(), "")
|
||||
phi.AddIncoming(phiValues, phiBlocks)
|
||||
funcCall.ReplaceAllUsesWith(phi)
|
||||
}
|
||||
|
||||
// Finally, remove the old instructions.
|
||||
funcCall.EraseFromParentAsInstruction()
|
||||
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||
inttoptr.EraseFromParentAsInstruction()
|
||||
}
|
||||
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-23
@@ -32,10 +32,15 @@ const (
|
||||
// funcImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||
if c.GOARCH == "wasm" {
|
||||
// Always pick the switch implementation, as it allows the use of blocking
|
||||
// inside a function that is used as a func value.
|
||||
switch c.Scheduler() {
|
||||
case "coroutines":
|
||||
return funcValueSwitch
|
||||
} else {
|
||||
case "tasks":
|
||||
return funcValueDoubleword
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +53,11 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case funcValueSwitch:
|
||||
sigGlobal := c.getFuncSignature(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
@@ -73,22 +78,6 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// getFuncSignature returns a global for identification of a particular function
|
||||
// signature. It is used in runtime.funcValueWithSignature and in calls to
|
||||
// getFuncPtr.
|
||||
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
|
||||
typeCodeName := getTypeCodeName(sig)
|
||||
sigGlobalName := "reflect/types.type:" + typeCodeName
|
||||
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||
if sigGlobal.IsNil() {
|
||||
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
|
||||
sigGlobal.SetGlobalConstant(true)
|
||||
sigGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
return sigGlobal
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
@@ -103,14 +92,14 @@ func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
|
||||
// decodeFuncValue extracts the context and the function pointer from this func
|
||||
// value. This may be an expensive operation.
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
|
||||
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.getFuncSignature(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
@@ -126,7 +115,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case funcValueSwitch:
|
||||
return c.mod.GetTypeByName("runtime.funcValue")
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) addGlobalsBitmap() {
|
||||
if c.mod.NamedGlobal("runtime.trackedGlobalsStart").IsNil() {
|
||||
return // nothing to do: no GC in use
|
||||
}
|
||||
|
||||
var trackedGlobals []llvm.Value
|
||||
var trackedGlobalTypes []llvm.Type
|
||||
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||
if global.IsDeclaration() {
|
||||
continue
|
||||
}
|
||||
typ := global.Type().ElementType()
|
||||
ptrs := c.getPointerBitmap(typ, global.Name())
|
||||
if ptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
trackedGlobals = append(trackedGlobals, global)
|
||||
trackedGlobalTypes = append(trackedGlobalTypes, typ)
|
||||
}
|
||||
|
||||
//
|
||||
globalsBundleType := c.ctx.StructType(trackedGlobalTypes, false)
|
||||
globalsBundle := llvm.AddGlobal(c.mod, globalsBundleType, "tinygo.trackedGlobals")
|
||||
globalsBundle.SetLinkage(llvm.InternalLinkage)
|
||||
globalsBundle.SetUnnamedAddr(true)
|
||||
initializer := llvm.Undef(globalsBundleType)
|
||||
for i, global := range trackedGlobals {
|
||||
initializer = llvm.ConstInsertValue(initializer, global.Initializer(), []uint32{uint32(i)})
|
||||
gep := llvm.ConstGEP(globalsBundle, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
})
|
||||
global.ReplaceAllUsesWith(gep)
|
||||
global.EraseFromParentAsGlobal()
|
||||
}
|
||||
globalsBundle.SetInitializer(initializer)
|
||||
|
||||
trackedGlobalsStart := llvm.ConstPtrToInt(globalsBundle, c.uintptrType)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsStart").SetInitializer(trackedGlobalsStart)
|
||||
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
trackedGlobalsLength := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(globalsBundleType)/uint64(alignment), false)
|
||||
c.mod.NamedGlobal("runtime.trackedGlobalsLength").SetInitializer(trackedGlobalsLength)
|
||||
|
||||
bitmapBytes := c.getPointerBitmap(globalsBundleType, "globals bundle").Bytes()
|
||||
bitmapValues := make([]llvm.Value, len(bitmapBytes))
|
||||
for i, b := range bitmapBytes {
|
||||
bitmapValues[len(bitmapBytes)-i-1] = llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)
|
||||
}
|
||||
bitmapArray := llvm.ConstArray(llvm.ArrayType(c.ctx.Int8Type(), len(bitmapBytes)), bitmapValues)
|
||||
bitmapNew := llvm.AddGlobal(c.mod, bitmapArray.Type(), "runtime.trackedGlobalsBitmap.tmp")
|
||||
bitmapOld := c.mod.NamedGlobal("runtime.trackedGlobalsBitmap")
|
||||
bitmapOld.ReplaceAllUsesWith(bitmapNew)
|
||||
bitmapNew.SetInitializer(bitmapArray)
|
||||
bitmapNew.SetName("runtime.trackedGlobalsBitmap")
|
||||
}
|
||||
|
||||
func (c *Compiler) getPointerBitmap(typ llvm.Type, name string) *big.Int {
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
switch typ.TypeKind() {
|
||||
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return big.NewInt(0)
|
||||
case llvm.PointerTypeKind:
|
||||
return big.NewInt(1)
|
||||
case llvm.StructTypeKind:
|
||||
ptrs := big.NewInt(0)
|
||||
for i, subtyp := range typ.StructElementTypes() {
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
if subptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
offset := c.targetData.ElementOffset(typ, i)
|
||||
if offset%uint64(alignment) != 0 {
|
||||
panic("precise GC: global contains unaligned pointer: " + name)
|
||||
}
|
||||
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
case llvm.ArrayTypeKind:
|
||||
subtyp := typ.ElementType()
|
||||
subptrs := c.getPointerBitmap(subtyp, name)
|
||||
ptrs := big.NewInt(0)
|
||||
if subptrs.BitLen() == 0 {
|
||||
return ptrs
|
||||
}
|
||||
elementSize := c.targetData.TypeAllocSize(subtyp)
|
||||
for i := 0; i < typ.ArrayLength(); i++ {
|
||||
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
default:
|
||||
panic("unknown type kind of global: " + name)
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package compiler
|
||||
|
||||
// This file provides IR transformations necessary for precise and portable
|
||||
// garbage collectors.
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+688
-250
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
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.
|
||||
// In general, you should pass all regular parameters plus the context parameter.
|
||||
// There is one exception: the task-based scheduler needs to have the function
|
||||
// pointer passed in as a parameter too in addition to the context.
|
||||
//
|
||||
// Because a go statement doesn't return anything, return undef.
|
||||
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
switch c.Scheduler() {
|
||||
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, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
|
||||
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)-1])
|
||||
params = append(params, llvm.Undef(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, "")
|
||||
}
|
||||
+11
-2
@@ -18,10 +18,19 @@ import (
|
||||
// func ReadRegister(name string) uintptr
|
||||
//
|
||||
// The register name must be a constant, for example "sp".
|
||||
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
|
||||
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)
|
||||
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+109
-30
@@ -28,14 +28,14 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||
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.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
|
||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf
|
||||
@@ -45,27 +45,100 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
globalName := "type:" + getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||
// Create a new typecode global.
|
||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
||||
// Some type classes contain more information for underlying types or
|
||||
// element types. Store it directly in the typecode global to make
|
||||
// reflect lowering simpler.
|
||||
var references llvm.Value
|
||||
var length int64
|
||||
switch typ := typ.(type) {
|
||||
case *types.Named:
|
||||
references = c.getTypeCode(typ.Underlying())
|
||||
case *types.Chan:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Pointer:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Slice:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
case *types.Array:
|
||||
references = c.getTypeCode(typ.Elem())
|
||||
length = typ.Len()
|
||||
case *types.Struct:
|
||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||
structGlobal := c.makeStructTypeFields(typ)
|
||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
||||
}
|
||||
if !references.IsNil() {
|
||||
// Set the 'references' field of the runtime.typecodeID struct.
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
}
|
||||
|
||||
// makeStructTypeFields creates a new global that stores all type information
|
||||
// related to this struct type, and returns the resulting global. This global is
|
||||
// actually an array of all the fields in the structs.
|
||||
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
// The global is an array of runtime.structField structs.
|
||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
||||
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
|
||||
structGlobalValue := llvm.ConstNull(structGlobalType)
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
|
||||
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
|
||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldName.SetUnnamedAddr(true)
|
||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
||||
if typ.Tag(i) != "" {
|
||||
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
|
||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldTag.SetUnnamedAddr(true)
|
||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
||||
}
|
||||
if typ.Field(i).Embedded() {
|
||||
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
|
||||
}
|
||||
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
|
||||
}
|
||||
structGlobal.SetInitializer(structGlobalValue)
|
||||
structGlobal.SetUnnamedAddr(true)
|
||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
return structGlobal
|
||||
}
|
||||
|
||||
// getTypeCodeName returns a name for this type that can be used in the
|
||||
// interface lowering pass to assign type codes as expected by the reflect
|
||||
// package. See getTypeCodeNum.
|
||||
func getTypeCodeName(t types.Type) string {
|
||||
name := ""
|
||||
if named, ok := t.(*types.Named); ok {
|
||||
name = "~" + named.String() + ":"
|
||||
t = t.Underlying()
|
||||
}
|
||||
switch t := t.(type) {
|
||||
case *types.Named:
|
||||
return "named:" + t.String()
|
||||
case *types.Array:
|
||||
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||
case *types.Basic:
|
||||
var kind string
|
||||
switch t.Kind() {
|
||||
@@ -108,21 +181,21 @@ func getTypeCodeName(t types.Type) string {
|
||||
default:
|
||||
panic("unknown basic type: " + t.Name())
|
||||
}
|
||||
return "basic:" + name + kind
|
||||
return "basic:" + kind
|
||||
case *types.Chan:
|
||||
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||
return "chan:" + getTypeCodeName(t.Elem())
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
keyType := getTypeCodeName(t.Key())
|
||||
elemType := getTypeCodeName(t.Elem())
|
||||
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
||||
case *types.Pointer:
|
||||
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||
return "pointer:" + getTypeCodeName(t.Elem())
|
||||
case *types.Signature:
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
@@ -132,19 +205,22 @@ func getTypeCodeName(t types.Type) string {
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||
}
|
||||
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||
case *types.Slice:
|
||||
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||
return "slice:" + getTypeCodeName(t.Elem())
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
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++ {
|
||||
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||
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:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||
return "struct:" + "{" + strings.Join(elems, ",") + "}"
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
@@ -163,11 +239,11 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0))
|
||||
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))
|
||||
@@ -203,14 +279,14 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
// Every method is a *i16 reference indicating the signature of this method.
|
||||
// Every method is a *i8 reference indicating the signature of this method.
|
||||
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
|
||||
for i := range methods {
|
||||
method := typ.Underlying().(*types.Interface).Method(i)
|
||||
methods[i] = c.getMethodSignature(method)
|
||||
}
|
||||
|
||||
value := llvm.ConstArray(methods[0].Type(), methods)
|
||||
value := llvm.ConstArray(c.i8ptrType, methods)
|
||||
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
@@ -219,7 +295,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
}
|
||||
|
||||
// getMethodSignature returns a global variable which is a reference to an
|
||||
// external *i16 indicating the indicating the signature of this method. It is
|
||||
// external *i8 indicating the indicating the signature of this method. It is
|
||||
// used during the interface lowering pass.
|
||||
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := ir.MethodSignature(method)
|
||||
@@ -300,7 +376,7 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
||||
// Continue after the if statement.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), 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
|
||||
@@ -385,6 +461,9 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||
if f.LLVMFn.LastParam().Name() == "parentHandle" {
|
||||
wrapper.LastParam().SetName("parentHandle")
|
||||
}
|
||||
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
||||
fn: f,
|
||||
wrapper: wrapper,
|
||||
@@ -403,7 +482,7 @@ func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
|
||||
// add debug info if needed
|
||||
if c.Debug {
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
|
||||
// will be lowered to a real interrupt during interrupt lowering.
|
||||
//
|
||||
// This two-stage approach allows unused interrupts to be optimized away if
|
||||
// necessary.
|
||||
func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
// Get the interrupt number, which must be a compile-time constant.
|
||||
id, ok := instr.Args[0].(*ssa.Const)
|
||||
if !ok {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant")
|
||||
}
|
||||
|
||||
// Get the func value, which also must be a compile time constant.
|
||||
// Note that bound functions are allowed if the function has a pointer
|
||||
// receiver and is a global. This is rather strict but still allows for
|
||||
// idiomatic Go code.
|
||||
funcValue := c.getValue(frame, instr.Args[1])
|
||||
if funcValue.IsAConstant().IsNil() {
|
||||
// Try to determine the cause of the non-constantness for a nice error
|
||||
// message.
|
||||
switch instr.Args[1].(type) {
|
||||
case *ssa.MakeClosure:
|
||||
// This may also be a bound method.
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
||||
}
|
||||
// Fall back to a generic error.
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt function must be constant")
|
||||
}
|
||||
|
||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||
// type are lowered in the interrupt lowering pass.
|
||||
globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := c.getLLVMType(globalType)
|
||||
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
if global := c.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program")
|
||||
}
|
||||
global := llvm.AddGlobal(c.mod, globalLLVMType, globalName)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0})
|
||||
global.SetInitializer(initializer)
|
||||
|
||||
// Add debug info to the interrupt global.
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(instr.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
||||
LinkageName: globalName,
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Line: pos.Line,
|
||||
Type: c.getDIType(globalType),
|
||||
Expr: c.dibuilder.CreateExpression(nil),
|
||||
LocalToUnit: false,
|
||||
})
|
||||
global.AddMetadata(0, diglobal)
|
||||
}
|
||||
|
||||
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
|
||||
// member of type int.
|
||||
num := llvm.ConstPtrToInt(global, c.intType)
|
||||
interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
||||
|
||||
// Add dummy "use" call for AVR, because interrupts may be used even though
|
||||
// they are never referenced again. This is unlike Cortex-M or the RISC-V
|
||||
// PLIC where each interrupt must be enabled using the interrupt number, and
|
||||
// thus keeps the Interrupt object alive.
|
||||
// This call is removed during interrupt lowering.
|
||||
if strings.HasPrefix(c.Triple(), "avr") {
|
||||
useFn := c.mod.NamedFunction("runtime/interrupt.use")
|
||||
if useFn.IsNil() {
|
||||
useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
||||
useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType)
|
||||
}
|
||||
c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "")
|
||||
}
|
||||
|
||||
return interrupt, nil
|
||||
}
|
||||
+36
-103
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -22,115 +23,47 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
return uses
|
||||
}
|
||||
|
||||
// createEntryBlockAlloca creates a new alloca in the entry block, even though
|
||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
||||
// after the last instruction in the current block. Also, it adds lifetime
|
||||
// information to the IR signalling that the alloca won't be used before this
|
||||
// point.
|
||||
// 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 after you're done with it.
|
||||
func (c *Compiler) createEntryBlockAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
c.builder.SetInsertPointBefore(currentBlock.Parent().EntryBasicBlock().FirstInstruction())
|
||||
alloca = c.builder.CreateAlloca(t, name)
|
||||
c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
bitcast = c.builder.CreateBitCast(alloca, c.i8ptrType, name+".bitcast")
|
||||
size = llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(t), false)
|
||||
c.builder.CreateCall(c.getLifetimeStartFunc(), []llvm.Value{size, bitcast}, "")
|
||||
return
|
||||
// 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) {
|
||||
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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) {
|
||||
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
|
||||
}
|
||||
|
||||
// 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
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
||||
}
|
||||
|
||||
// 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(buf []byte, name string, elementType llvm.Type) llvm.Value {
|
||||
globalType := llvm.ArrayType(elementType, len(buf))
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
ch := uint64(buf[i])
|
||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
||||
}
|
||||
global.SetInitializer(value)
|
||||
return global
|
||||
}
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Package llvmutil contains utility functions used across multiple compiler
|
||||
// packages. For example, they may be used by both the compiler pacakge and
|
||||
// transformation packages.
|
||||
//
|
||||
// Normally, utility packages are avoided. However, in this case, the utility
|
||||
// functions are non-trivial and hard to get right. Copying them to multiple
|
||||
// places would be a big risk if only one of them is updated.
|
||||
package llvmutil
|
||||
|
||||
import "tinygo.org/x/go-llvm"
|
||||
|
||||
// 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 CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
|
||||
currentBlock := builder.GetInsertBlock()
|
||||
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
||||
if entryBlock.FirstInstruction().IsNil() {
|
||||
builder.SetInsertPointAtEnd(entryBlock)
|
||||
} else {
|
||||
builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
||||
}
|
||||
alloca := builder.CreateAlloca(t, name)
|
||||
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 CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
ctx := t.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
alloca = CreateEntryBlockAlloca(builder, t, name)
|
||||
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
||||
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
||||
func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
|
||||
alloca := CreateEntryBlockAlloca(builder, t, name)
|
||||
builder.SetInsertPointBefore(inst)
|
||||
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
||||
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
||||
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
||||
builder.SetInsertPointBefore(next)
|
||||
} else {
|
||||
builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||
}
|
||||
builder.CreateCall(getLifetimeEndFunc(mod), []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 EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
|
||||
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
// first if it doesn't exist yet.
|
||||
func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
|
||||
fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
|
||||
ctx := mod.Context()
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
||||
fn = llvm.AddFunction(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 getLifetimeEndFunc(mod llvm.Module) llvm.Value {
|
||||
fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||
ctx := mod.Context()
|
||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
if fn.IsNil() {
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
||||
fn = llvm.AddFunction(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 SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||
oldBlock := afterInst.InstructionParent()
|
||||
newBlock := afterInst.Type().Context().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.
|
||||
builder.SetInsertPointAtEnd(newBlock)
|
||||
for _, inst := range nextInstructions {
|
||||
inst.RemoveFromParentAsInstruction()
|
||||
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 {
|
||||
builder.SetInsertPointBefore(phi)
|
||||
newPhi := 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
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package llvmutil
|
||||
|
||||
// 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 (
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"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 EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
|
||||
valueTypes := make([]llvm.Type, len(values))
|
||||
for i, value := range values {
|
||||
valueTypes[i] = value.Type()
|
||||
}
|
||||
packedType := ctx.StructType(valueTypes, false)
|
||||
|
||||
// Allocate memory for the packed data.
|
||||
var packedAlloc, packedHeapAlloc llvm.Value
|
||||
size := targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
return llvm.ConstPointerNull(i8ptrType)
|
||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
||||
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
|
||||
} else if size <= targetData.TypeAllocSize(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 builder.CreateIntToPtr(values[0], 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, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
|
||||
} else {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
||||
alloc := mod.NamedFunction("runtime.alloc")
|
||||
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
}, "")
|
||||
if config.NeedsStackObjects() {
|
||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||
builder.CreateCall(trackPointer, []llvm.Value{
|
||||
packedHeapAlloc,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
}, "")
|
||||
}
|
||||
packedAlloc = 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(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
if packedHeapAlloc.IsNil() {
|
||||
// Load value (as *i8) from the alloca.
|
||||
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
|
||||
result := builder.CreateLoad(packedAlloc, "")
|
||||
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||
EmitLifetimeEnd(builder, mod, 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 EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||
|
||||
packedType := ctx.StructType(valueTypes, false)
|
||||
|
||||
// Get a correctly-typed pointer to the packed data.
|
||||
var packedAlloc, packedRawAlloc llvm.Value
|
||||
size := 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{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
||||
} else if size <= targetData.TypeAllocSize(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{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||
}
|
||||
// Fallback: load it using an alloca.
|
||||
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
|
||||
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
|
||||
builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||
packedAlloc = 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 = 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 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(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
values[i] = builder.CreateLoad(gep, "")
|
||||
}
|
||||
if !packedRawAlloc.IsNil() {
|
||||
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
|
||||
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
|
||||
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
|
||||
}
|
||||
return values
|
||||
}
|
||||
+15
-13
@@ -15,7 +15,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||
// will be zeroed by the hashmap get implementation if the key is not
|
||||
// present in the map.
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createEntryBlockAlloca(llvmValueType, "hashmap.value")
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
|
||||
// Do the lookup. How it is done depends on the key type.
|
||||
var commaOkValue llvm.Value
|
||||
@@ -27,21 +27,21 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
// key can be compared with runtime.memequal
|
||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||
// growth.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapKeySize, mapKeyPtr}, "")
|
||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
} else {
|
||||
// Not trivially comparable using memcmp.
|
||||
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
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.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{mapValueSize, mapValuePtr}, "")
|
||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||
@@ -54,7 +54,7 @@ func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Valu
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valuePtr, valueSize := c.createEntryBlockAlloca(value.Type(), "hashmap.value")
|
||||
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
|
||||
c.builder.CreateStore(value, valueAlloca)
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
@@ -63,15 +63,15 @@ func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, p
|
||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
} else {
|
||||
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSize, valuePtr}, "")
|
||||
c.emitLifetimeEnd(valuePtr, valueSize)
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||
@@ -82,14 +82,14 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
keyAlloca, keyPtr, keySize := c.createEntryBlockAlloca(key.Type(), "hashmap.key")
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
params := []llvm.Value{m, keyPtr}
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{keySize, keyPtr}, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
return nil
|
||||
} else {
|
||||
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,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()
|
||||
|
||||
+57
-275
@@ -3,12 +3,13 @@ package compiler
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Run the LLVM optimizer over the module.
|
||||
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
|
||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
|
||||
builder := llvm.NewPassManagerBuilder()
|
||||
defer builder.Dispose()
|
||||
builder.SetOptLevel(optLevel)
|
||||
@@ -18,8 +19,16 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
}
|
||||
builder.AddCoroutinePassesToExtensionPoints()
|
||||
|
||||
if c.PanicStrategy == "trap" {
|
||||
c.replacePanicsWithTrap() // -panic=trap
|
||||
if c.PanicStrategy() == "trap" {
|
||||
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
|
||||
}
|
||||
|
||||
// run a check of all of our code
|
||||
if c.VerifyIR() {
|
||||
errs := c.checkModule()
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
// Run function passes for each function.
|
||||
@@ -36,19 +45,27 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
// Run some preparatory passes for the Go optimizer.
|
||||
goPasses := llvm.NewPassManager()
|
||||
defer goPasses.Dispose()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddGlobalDCEPass()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddConstantPropagationPass()
|
||||
goPasses.AddAggressiveDCEPass()
|
||||
goPasses.AddFunctionAttrsPass()
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
c.OptimizeMaps()
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
transform.OptimizeMaps(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.LowerInterfaces(c.mod)
|
||||
|
||||
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
@@ -56,8 +73,8 @@ 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")
|
||||
@@ -77,19 +94,30 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
return []error{err}
|
||||
}
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
c.LowerInterfaces()
|
||||
c.LowerFuncValues()
|
||||
transform.LowerInterfaces(c.mod)
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
return []error{err}
|
||||
}
|
||||
errs := transform.LowerInterruptRegistrations(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if c.VerifyIR() {
|
||||
if errs := c.checkModule(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("optimizations caused a verification failure")
|
||||
return []error{errors.New("optimizations caused a verification failure")}
|
||||
}
|
||||
|
||||
if sizeLevel >= 2 {
|
||||
@@ -102,6 +130,15 @@ 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) {
|
||||
@@ -115,268 +152,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
|
||||
builder.Populate(modPasses)
|
||||
modPasses.Run(c.mod)
|
||||
|
||||
if c.gcIsPrecise() {
|
||||
c.addGlobalsBitmap()
|
||||
hasGCPass := transform.AddGlobalsBitmap(c.mod)
|
||||
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||
if hasGCPass {
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("GC pass caused a verification failure")
|
||||
return []error{errors.New("GC pass caused a verification failure")}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
for _, use := range getUses(fn) {
|
||||
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||
panic("expected use of a panic function to be a call")
|
||||
}
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
c.builder.CreateCall(trap, 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.
|
||||
continue
|
||||
}
|
||||
size := heapalloc.Operand(0).ZExtValue()
|
||||
if size > 256 {
|
||||
// The maximum value for a stack allocation.
|
||||
// TODO: tune this, this is just a random value.
|
||||
continue
|
||||
}
|
||||
|
||||
// In general the pattern is:
|
||||
// %0 = call i8* @runtime.alloc(i32 %size)
|
||||
// %1 = bitcast i8* %0 to type*
|
||||
// (use %1 only)
|
||||
// But the bitcast might sometimes be dropped when allocating an *i8.
|
||||
// The 'bitcast' variable below is thus usually a bitcast of the
|
||||
// heapalloc but not always.
|
||||
bitcast := heapalloc // instruction that creates the value
|
||||
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
|
||||
// getting only bitcast use
|
||||
bitcast = uses[0]
|
||||
}
|
||||
if !c.doesEscape(bitcast) {
|
||||
// Insert alloca in the entry block. Do it here so that mem2reg can
|
||||
// promote it to a SSA value.
|
||||
fn := bitcast.InstructionParent().Parent()
|
||||
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
|
||||
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||
c.builder.CreateStore(zero, alloca)
|
||||
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||
if heapalloc != bitcast {
|
||||
bitcast.EraseFromParentAsInstruction()
|
||||
}
|
||||
heapalloc.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Very basic escape analysis.
|
||||
func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
for _, use := range uses {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAGetElementPtrInst() != nilValue {
|
||||
if c.doesEscape(use) {
|
||||
return true
|
||||
}
|
||||
} else if use.IsABitCastInst() != nilValue {
|
||||
// A bitcast escapes if the casted-to value escapes.
|
||||
if c.doesEscape(use) {
|
||||
return true
|
||||
}
|
||||
} else if use.IsALoadInst() != nilValue {
|
||||
// Load does not escape.
|
||||
} else if use.IsAStoreInst() != nilValue {
|
||||
// Store only escapes when the value is stored to, not when the
|
||||
// value is stored into another value.
|
||||
if use.Operand(0) == value {
|
||||
return true
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
if !c.hasFlag(use, value, "nocapture") {
|
||||
return true
|
||||
}
|
||||
} else if use.IsAICmpInst() != nilValue {
|
||||
// Comparing pointers don't let the pointer escape.
|
||||
// This is often a compiler-inserted nil check.
|
||||
} else {
|
||||
// Unknown instruction, might escape.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// does not escape
|
||||
return false
|
||||
}
|
||||
|
||||
// Check whether the given value (which is of pointer type) is never stored to.
|
||||
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||
if fn.IsNil() {
|
||||
// reflect.ValueOf is never used, so we can use the most efficient
|
||||
// encoding possible.
|
||||
for i, t := range typeSlice {
|
||||
t.num = uint64(i + 1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Assign typecodes the way the reflect package expects.
|
||||
fallbackIndex := 1
|
||||
namedTypes := make(map[string]int)
|
||||
for _, t := range typeSlice {
|
||||
if t.name[:5] != "type:" {
|
||||
panic("expected type name to start with 'type:'")
|
||||
}
|
||||
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||
// based on a types.Type value for this function.
|
||||
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||
// Note: see src/reflect/type.go for bit allocations.
|
||||
// A type can be named or unnamed. Example of both:
|
||||
// basic:~foo:uint64
|
||||
// basic:uint64
|
||||
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||
// contents of this type ID string. Allocate bits based on that, as
|
||||
// src/runtime/types.go expects.
|
||||
class := id[:strings.IndexByte(id, ':')]
|
||||
value := id[len(class)+1:]
|
||||
name := ""
|
||||
if value[0] == '~' {
|
||||
name = value[1:strings.IndexByte(value, ':')]
|
||||
value = value[len(name)+2:]
|
||||
}
|
||||
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: " + id)
|
||||
}
|
||||
if name != "" {
|
||||
// This type is named, set the upper bits to the name ID.
|
||||
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||
}
|
||||
return big.NewInt(num << 1)
|
||||
} else {
|
||||
// Complex types use the following bit pattern:
|
||||
// ...nxxx1
|
||||
// where xxx indicates the complex type (any non-basic type). The upper
|
||||
// bits contain whatever the type contains. Types that wrap a single
|
||||
// other type (channel, interface, pointer, slice) just contain the bits
|
||||
// of the wrapped type. Other types (like struct) have a different
|
||||
// method of encoding the contents of the type.
|
||||
var num *big.Int
|
||||
var classNumber int64
|
||||
switch class {
|
||||
case "chan":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 0
|
||||
case "interface":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 1
|
||||
case "pointer":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 2
|
||||
case "slice":
|
||||
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||
classNumber = 3
|
||||
case "array":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 4
|
||||
case "func":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 5
|
||||
case "map":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 6
|
||||
case "struct":
|
||||
num = big.NewInt(int64(*fallbackIndex))
|
||||
*fallbackIndex++
|
||||
classNumber = 7
|
||||
default:
|
||||
panic("unknown type kind: " + id)
|
||||
}
|
||||
if name == "" {
|
||||
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
} else {
|
||||
// TODO: store num in a sidetable
|
||||
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||
// type. If the name already has a number that number is returned, else a new
|
||||
// number is returned. The number is always non-zero.
|
||||
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||
if num, ok := namedTypes[name]; ok {
|
||||
return num
|
||||
} else {
|
||||
num = len(namedTypes) + 1
|
||||
namedTypes[name] = num
|
||||
return num
|
||||
}
|
||||
}
|
||||
+9
-29
@@ -63,12 +63,6 @@ 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())
|
||||
@@ -116,6 +110,9 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
if k == types.UnsafePointer {
|
||||
return s.PtrSize
|
||||
}
|
||||
if k == types.Invalid {
|
||||
return 0 // only relevant when there is a type error somewhere
|
||||
}
|
||||
panic("unknown basic type: " + t.String())
|
||||
case *types.Array:
|
||||
n := t.Len()
|
||||
@@ -143,29 +140,12 @@ func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
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)
|
||||
}
|
||||
// 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)
|
||||
case *types.Interface:
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
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() {
|
||||
typ := g.Type().(*types.Pointer).Elem()
|
||||
llvmType := c.getLLVMType(typ)
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
if !info.extern {
|
||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
||||
llvmGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
|
||||
// Set alignment from the //go:align comment.
|
||||
var alignInBits uint32
|
||||
if info.align < 0 || info.align&(info.align-1) != 0 {
|
||||
// Check for power-of-two (or 0).
|
||||
// See: https://stackoverflow.com/a/108360
|
||||
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
|
||||
} else {
|
||||
// Set the alignment only when it is a power of two.
|
||||
alignInBits = uint32(info.align) ^ uint32(info.align-1)
|
||||
if info.align > c.targetData.ABITypeAlignment(llvmType) {
|
||||
llvmGlobal.SetAlignment(info.align)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Debug() {
|
||||
// Add debug info.
|
||||
// TODO: this should be done for every global in the program, not just
|
||||
// the ones that are referenced from some code.
|
||||
pos := c.ir.Program.Fset.Position(g.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: g.RelString(nil),
|
||||
LinkageName: info.linkName,
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Line: pos.Line,
|
||||
Type: c.getDIType(typ),
|
||||
LocalToUnit: false,
|
||||
Expr: c.dibuilder.CreateExpression(nil),
|
||||
AlignInBits: alignInBits,
|
||||
})
|
||||
llvmGlobal.AddMetadata(0, diglobal)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
-16
@@ -4,7 +4,6 @@ package compiler
|
||||
// compiler builtins.
|
||||
|
||||
import (
|
||||
"go/constant"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -14,11 +13,11 @@ import (
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(call.Args[0].(*ssa.Const).Value)
|
||||
num := c.getValue(frame, call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH == "amd64":
|
||||
if c.GOOS == "darwin" {
|
||||
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.
|
||||
@@ -29,12 +28,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num += 0x2000000
|
||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{llvm.ConstInt(c.uintptrType, num, false)}
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
@@ -59,7 +58,33 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH == "arm" && c.GOOS == "linux":
|
||||
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{}
|
||||
@@ -81,7 +106,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
@@ -91,7 +116,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
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":
|
||||
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
@@ -111,7 +136,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, llvm.ConstInt(c.uintptrType, num, false))
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
@@ -124,10 +149,10 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
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)
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||
}
|
||||
switch c.GOOS {
|
||||
case "linux":
|
||||
switch c.GOOS() {
|
||||
case "linux", "freebsd":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
@@ -140,7 +165,7 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
@@ -156,12 +181,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(llvm.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS+"/"+c.GOARCH)
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
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) && !c.gcIsPrecise() {
|
||||
// Packed data fits in a pointer, so store it directly inside the
|
||||
// pointer.
|
||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
||||
// 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.builder.CreateAlloca(packedType, "")
|
||||
} else {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
packedHeapAlloc = c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "")
|
||||
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), "")
|
||||
return c.builder.CreateLoad(packedAlloc, "")
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an *i8.
|
||||
return c.builder.CreateBitCast(packedAlloc, c.i8ptrType, "")
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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) && !c.gcIsPrecise() {
|
||||
// Packed data stored directly in pointer.
|
||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
||||
// Keep this cast in SSA form.
|
||||
return []llvm.Value{c.builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
||||
}
|
||||
// Fallback: load it using an alloca.
|
||||
packedRawAlloc := c.builder.CreateAlloca(llvm.PointerType(c.i8ptrType, 0), "unpack.raw.alloc")
|
||||
packedRawValue := c.builder.CreateBitCast(ptr, llvm.PointerType(c.i8ptrType, 0), "unpack.raw.value")
|
||||
c.builder.CreateStore(packedRawValue, packedRawAlloc)
|
||||
packedAlloc = c.builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
||||
} 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] = c.getZeroValue(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, "")
|
||||
}
|
||||
return values
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
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/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||
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-20200104190746-1ff21df33566
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
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/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
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=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/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
|
||||
}
|
||||
+79
-2
@@ -3,15 +3,92 @@ package interp
|
||||
// This file provides useful types for errors encountered during IR evaluation.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// errUnreachable is returned when an unreachable instruction is executed. This
|
||||
// error should not be visible outside of the interp package.
|
||||
var errUnreachable = errors.New("interp: unreachable executed")
|
||||
|
||||
// Unsupported is the specific error that is returned when an unsupported
|
||||
// instruction is hit while trying to interpret all initializers.
|
||||
type Unsupported struct {
|
||||
Inst llvm.Value
|
||||
ImportPath string
|
||||
Inst llvm.Value
|
||||
Pos token.Position
|
||||
}
|
||||
|
||||
func (e Unsupported) Error() string {
|
||||
// TODO: how to return the actual instruction string?
|
||||
// It looks like LLVM provides no function for that...
|
||||
return "interp: unsupported instruction"
|
||||
return scanner.Error{
|
||||
Pos: e.Pos,
|
||||
Msg: "interp: unsupported instruction",
|
||||
}.Error()
|
||||
}
|
||||
|
||||
// unsupportedInstructionError returns a new "unsupported instruction" error for
|
||||
// the given instruction. It includes source location information, when
|
||||
// available.
|
||||
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
|
||||
return &Unsupported{
|
||||
ImportPath: e.packagePath,
|
||||
Inst: inst,
|
||||
Pos: getPosition(inst),
|
||||
}
|
||||
}
|
||||
|
||||
// Error encapsulates compile-time interpretation errors with an associated
|
||||
// import path. The errors may not have a precise location attached.
|
||||
type Error struct {
|
||||
ImportPath string
|
||||
Errs []scanner.Error
|
||||
}
|
||||
|
||||
// Error returns the string of the first error in the list of errors.
|
||||
func (e Error) Error() string {
|
||||
return e.Errs[0].Error()
|
||||
}
|
||||
|
||||
// errorAt returns an error value for the currently interpreted package at the
|
||||
// location of the instruction. The location information may not be complete as
|
||||
// it depends on debug information in the IR.
|
||||
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
|
||||
return Error{
|
||||
ImportPath: e.packagePath,
|
||||
Errs: []scanner.Error{errorAt(inst, msg)},
|
||||
}
|
||||
}
|
||||
|
||||
// errorAt returns an error value at the location of the instruction.
|
||||
// The location information may not be complete as it depends on debug
|
||||
// information in the IR.
|
||||
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||
return scanner.Error{
|
||||
Pos: getPosition(inst),
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// getPosition returns the position information for the given instruction, as
|
||||
// far as it is available.
|
||||
func getPosition(inst llvm.Value) token.Position {
|
||||
if inst.IsAInstruction().IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
loc := inst.InstructionDebugLoc()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.LocationScope().ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.LocationLine()),
|
||||
Column: int(loc.LocationColumn()),
|
||||
}
|
||||
}
|
||||
|
||||
+171
-49
@@ -4,21 +4,17 @@ package interp
|
||||
// functions.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
*Eval
|
||||
fn llvm.Value
|
||||
pkgName string
|
||||
locals map[llvm.Value]Value
|
||||
*evalPackage
|
||||
fn llvm.Value
|
||||
locals map[llvm.Value]Value
|
||||
}
|
||||
|
||||
var ErrUnreachable = errors.New("interp: unreachable executed")
|
||||
|
||||
// evalBasicBlock evaluates a single basic block, returning the return value (if
|
||||
// ending with a ret instruction), a list of outgoing basic blocks (if not
|
||||
// ending with a ret instruction), or an error on failure.
|
||||
@@ -79,14 +75,14 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
|
||||
|
||||
default:
|
||||
return nil, nil, &Unsupported{inst}
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
|
||||
// Memory operators
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloca")
|
||||
alloca.SetInitializer(getZeroValue(allocType))
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
@@ -101,7 +97,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
value = operand.Load()
|
||||
}
|
||||
if value.Type() != inst.Type() {
|
||||
panic("interp: load: type does not match")
|
||||
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
|
||||
}
|
||||
fr.locals[inst] = fr.getValue(value)
|
||||
case !inst.IsAStoreInst().IsNil():
|
||||
@@ -125,15 +121,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
// Not a constant operation.
|
||||
// This should be detected by the scanner, but isn't at the
|
||||
// moment.
|
||||
panic("todo: non-const gep")
|
||||
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
|
||||
}
|
||||
indices[i] = uint32(operand.Value().ZExtValue())
|
||||
}
|
||||
result := value.GetElementPtr(indices)
|
||||
if result.Type() != inst.Type() {
|
||||
println(" expected:", inst.Type().String())
|
||||
println(" actual: ", result.Type().String())
|
||||
panic("interp: gep: type does not match")
|
||||
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
|
||||
}
|
||||
fr.locals[inst] = result
|
||||
|
||||
@@ -176,6 +170,20 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
||||
// Special case for runtime.trackPointer calls.
|
||||
// Note: this might not be entirely sound in some rare cases
|
||||
// where the map is stored in a dirty global.
|
||||
uses := getUses(inst)
|
||||
if len(uses) == 1 {
|
||||
use := uses[0]
|
||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It is not possible in Go to bitcast a map value to a pointer.
|
||||
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
|
||||
}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
@@ -184,6 +192,33 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.IntPredicate()
|
||||
if predicate == llvm.IntEQ {
|
||||
var lhsZero, rhsZero bool
|
||||
var ok1, ok2 bool
|
||||
if 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.
|
||||
lhsZero, ok1 = isPointerNil(lhs)
|
||||
rhsZero, ok2 = isPointerNil(rhs)
|
||||
}
|
||||
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
|
||||
lhsZero, ok1 = isZero(lhs)
|
||||
rhsZero, ok2 = isZero(rhs)
|
||||
}
|
||||
if ok1 && ok2 {
|
||||
if lhsZero && rhsZero {
|
||||
// Both are zero, so this icmp is always evaluated to true.
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
||||
continue
|
||||
}
|
||||
if lhsZero != rhsZero {
|
||||
// Only one of them is zero, 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
|
||||
@@ -214,13 +249,13 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
if size != typeSize {
|
||||
// allocate an array
|
||||
if size%typeSize != 0 {
|
||||
return nil, nil, &Unsupported{inst}
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
elementCount = int(size / typeSize)
|
||||
allocType = llvm.ArrayType(allocType, elementCount)
|
||||
}
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.pkgName+"$alloc")
|
||||
alloc.SetInitializer(getZeroValue(allocType))
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
|
||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
@@ -237,24 +272,55 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
valueSize := inst.Operand(1).ZExtValue()
|
||||
fr.locals[inst] = &MapValue{
|
||||
Eval: fr.Eval,
|
||||
PkgName: fr.pkgName,
|
||||
PkgName: fr.packagePath,
|
||||
KeySize: int(keySize),
|
||||
ValueSize: int(valueSize),
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
// "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, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
|
||||
fr.getLocal(inst.Operand(2)).Value(), // key.len
|
||||
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
|
||||
fr.getLocal(inst.Operand(4)).Value(), // context
|
||||
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
m := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key
|
||||
fr.getLocal(inst.Operand(2)).Value(), // value
|
||||
fr.getLocal(inst.Operand(3)).Value(), // context
|
||||
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
@@ -271,7 +337,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$stringconcat")
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
@@ -279,10 +345,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, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, fr.errorAt(inst, "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, fr.errorAt(inst, "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))
|
||||
@@ -294,7 +403,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.pkgName+"$bytes")
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
@@ -302,7 +411,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
sliceType := inst.Type()
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := 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
|
||||
@@ -311,16 +420,16 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
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")
|
||||
return nil, nil, fr.errorAt(inst, "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")
|
||||
return nil, nil, fr.errorAt(inst, "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")
|
||||
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
|
||||
}
|
||||
methodSet = methodSet.Operand(0).Initializer()
|
||||
|
||||
@@ -349,6 +458,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "llvm.lifetime."):
|
||||
// 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.
|
||||
@@ -388,7 +501,10 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
params = append(params, local)
|
||||
}
|
||||
var ret Value
|
||||
scanResult := fr.Eval.hasSideEffects(callee)
|
||||
scanResult, err := fr.hasSideEffects(callee)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
||||
// Side effect is bounded. This means the operation invokes
|
||||
// side effects (like calling an external function) but it
|
||||
@@ -411,7 +527,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
// compile time.
|
||||
// * Unbounded: cannot call at runtime so we'll try to
|
||||
// interpret anyway and hope for the best.
|
||||
ret, err = fr.function(callee, params, fr.pkgName, indent+" ")
|
||||
ret, err = fr.function(callee, params, indent+" ")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -421,7 +537,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
}
|
||||
default:
|
||||
// function pointers, etc.
|
||||
return nil, nil, &Unsupported{inst}
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
case !inst.IsAExtractValueInst().IsNil():
|
||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||
@@ -431,7 +547,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = fr.getValue(newValue)
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, errors.New("cannot handle extractvalue with not exactly 1 index")
|
||||
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
|
||||
}
|
||||
@@ -444,7 +560,7 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, errors.New("cannot handle insertvalue with not exactly 1 index")
|
||||
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
|
||||
}
|
||||
@@ -457,21 +573,25 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
// conditional branch (if/then/else)
|
||||
cond := fr.getLocal(inst.Operand(0)).Value()
|
||||
if cond.Type() != fr.Mod.Context().Int1Type() {
|
||||
panic("expected an i1 in a branch instruction")
|
||||
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
|
||||
}
|
||||
thenBB := inst.Operand(1)
|
||||
elseBB := inst.Operand(2)
|
||||
if !cond.IsConstant() {
|
||||
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.IsAInstruction().IsNil() {
|
||||
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
|
||||
}
|
||||
if !cond.IsAConstantExpr().IsNil() {
|
||||
// This may happen when the instruction builder could not
|
||||
// const-fold some instructions.
|
||||
return nil, nil, fr.errorAt(inst, "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:
|
||||
return nil, nil, fr.errorAt(inst, "branch was not true or false")
|
||||
}
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||
// unconditional branch (goto)
|
||||
@@ -479,10 +599,11 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
|
||||
case !inst.IsAUnreachableInst().IsNil():
|
||||
// Unreachable was reached (e.g. after a call to panic()).
|
||||
// Report this as an error, as it is not supposed to happen.
|
||||
return nil, nil, ErrUnreachable
|
||||
// This is a sentinel error value.
|
||||
return nil, nil, errUnreachable
|
||||
|
||||
default:
|
||||
return nil, nil, &Unsupported{inst}
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,6 +617,7 @@ func (fr *frame) getLocal(v llvm.Value) Value {
|
||||
} else if value := fr.getValue(v); value != nil {
|
||||
return value
|
||||
} else {
|
||||
// This should not happen under normal circumstances.
|
||||
panic("cannot find value")
|
||||
}
|
||||
}
|
||||
|
||||
+25
-16
@@ -7,7 +7,6 @@ package interp
|
||||
// methods.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -22,9 +21,17 @@ type Eval struct {
|
||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||
}
|
||||
|
||||
// evalPackage encapsulates the Eval type for just a single package. The Eval
|
||||
// type keeps state across the whole program, the evalPackage type keeps extra
|
||||
// state for the currently interpreted package.
|
||||
type evalPackage struct {
|
||||
*Eval
|
||||
packagePath string
|
||||
}
|
||||
|
||||
// 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:")
|
||||
}
|
||||
@@ -32,7 +39,7 @@ 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{}{},
|
||||
}
|
||||
@@ -56,7 +63,7 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
break // ret void
|
||||
}
|
||||
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
||||
return errors.New("expected all instructions in " + name + " to be direct calls")
|
||||
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
|
||||
}
|
||||
initCalls = append(initCalls, inst)
|
||||
}
|
||||
@@ -66,13 +73,17 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
for _, call := range initCalls {
|
||||
initName := call.CalledValue().Name()
|
||||
if !strings.HasSuffix(initName, ".init") {
|
||||
return errors.New("expected all instructions in " + name + " to be *.init() calls")
|
||||
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
fn := call.CalledValue()
|
||||
call.EraseFromParentAsInstruction()
|
||||
_, err := e.Function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, pkgName)
|
||||
if err == ErrUnreachable {
|
||||
evalPkg := evalPackage{
|
||||
Eval: e,
|
||||
packagePath: pkgName,
|
||||
}
|
||||
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
|
||||
if err == errUnreachable {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
@@ -83,16 +94,14 @@ func Run(mod llvm.Module, targetData llvm.TargetData, debug bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Eval) Function(fn llvm.Value, params []Value, pkgName string) (Value, error) {
|
||||
return e.function(fn, params, pkgName, "")
|
||||
}
|
||||
|
||||
func (e *Eval) function(fn llvm.Value, params []Value, pkgName, indent string) (Value, error) {
|
||||
// function interprets the given function. The params are the function params
|
||||
// and the indent is the string indentation to use when dumping all interpreted
|
||||
// instructions.
|
||||
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
|
||||
fr := frame{
|
||||
Eval: e,
|
||||
fn: fn,
|
||||
pkgName: pkgName,
|
||||
locals: make(map[llvm.Value]Value),
|
||||
evalPackage: e,
|
||||
fn: fn,
|
||||
locals: make(map[llvm.Value]Value),
|
||||
}
|
||||
for i, param := range fn.Params() {
|
||||
fr.locals[param] = params[i]
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
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",
|
||||
"consteval",
|
||||
"map",
|
||||
} {
|
||||
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
|
||||
}
|
||||
+65
-21
@@ -1,11 +1,28 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type sideEffectSeverity int
|
||||
|
||||
func (severity sideEffectSeverity) String() string {
|
||||
switch severity {
|
||||
case sideEffectInProgress:
|
||||
return "in progress"
|
||||
case sideEffectNone:
|
||||
return "none"
|
||||
case sideEffectLimited:
|
||||
return "limited"
|
||||
case sideEffectAll:
|
||||
return "all"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
|
||||
sideEffectNone // no side effects at all (pure)
|
||||
@@ -23,26 +40,36 @@ type sideEffectResult struct {
|
||||
// hasSideEffects scans this function and all descendants, recursively. It
|
||||
// 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":
|
||||
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, error) {
|
||||
name := fn.Name()
|
||||
switch {
|
||||
case name == "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
case "runtime.nanotime":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "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 "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
case name == "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.sliceCopy":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case strings.HasPrefix(name, "llvm.lifetime."):
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
}
|
||||
if fn.IsDeclaration() {
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
}
|
||||
if se, ok := e.sideEffectFuncs[fn]; ok {
|
||||
return se
|
||||
return se, nil
|
||||
}
|
||||
result := &sideEffectResult{
|
||||
severity: sideEffectInProgress,
|
||||
@@ -53,6 +80,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAInstruction().IsNil() {
|
||||
// Should not happen in valid IR.
|
||||
panic("not an instruction")
|
||||
}
|
||||
|
||||
@@ -69,7 +97,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
switch inst.InstructionOpcode() {
|
||||
case llvm.IndirectBr, llvm.Invoke:
|
||||
// Not emitted by the compiler.
|
||||
panic("unknown instructions")
|
||||
return nil, e.errorAt(inst, "unknown instructions")
|
||||
case llvm.Call:
|
||||
child := inst.CalledValue()
|
||||
if !child.IsAInlineAsm().IsNil() {
|
||||
@@ -95,7 +123,10 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
}
|
||||
continue
|
||||
}
|
||||
childSideEffects := e.hasSideEffects(child)
|
||||
childSideEffects, err := e.hasSideEffects(child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch childSideEffects.severity {
|
||||
case sideEffectInProgress, sideEffectNone:
|
||||
// no side effects or recursive function - continue scanning
|
||||
@@ -109,7 +140,16 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
case llvm.Load, llvm.Store:
|
||||
case llvm.Load:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
}
|
||||
case llvm.Store:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
@@ -128,7 +168,7 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
|
||||
// No side effect was reported for this function.
|
||||
result.severity = sideEffectNone
|
||||
}
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||
@@ -143,6 +183,7 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
|
||||
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
||||
user := use.User()
|
||||
if user.IsAInstruction().IsNil() {
|
||||
// Should not happen in valid IR.
|
||||
panic("user not an instruction")
|
||||
}
|
||||
switch user.InstructionOpcode() {
|
||||
@@ -162,10 +203,13 @@ func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llv
|
||||
// Already handled in (*Eval).hasSideEffects.
|
||||
continue
|
||||
}
|
||||
// But a store might also store to an alloca, in which case all uses
|
||||
// of the alloca (possibly indirect through a GEP, bitcast, etc.)
|
||||
// must be marked dirty.
|
||||
panic("todo: store")
|
||||
// This store might affect all kinds of values. While it is
|
||||
// certainly possible to traverse through all of them, the easiest
|
||||
// option right now is to just assume the worst and say that this
|
||||
// function has side effects.
|
||||
// TODO: traverse through all stores and mark all relevant allocas /
|
||||
// globals dirty.
|
||||
return true
|
||||
default:
|
||||
// All instructions that take 0 or more operands (1 or more if it
|
||||
// was a use) and produce a result.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
var scanTestTable = []struct {
|
||||
name string
|
||||
severity sideEffectSeverity
|
||||
mentionsGlobals []string
|
||||
}{
|
||||
{"returnsConst", sideEffectNone, nil},
|
||||
{"returnsArg", sideEffectNone, nil},
|
||||
{"externalCallOnly", sideEffectNone, nil},
|
||||
{"externalCallAndReturn", sideEffectLimited, nil},
|
||||
{"externalCallBranch", sideEffectLimited, nil},
|
||||
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
|
||||
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
|
||||
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
|
||||
{"getDirtyPointer", sideEffectLimited, nil},
|
||||
{"storeToPointer", sideEffectLimited, nil},
|
||||
}
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the input IR.
|
||||
path := "testdata/scan.ll"
|
||||
ctx := llvm.NewContext()
|
||||
buf, err := llvm.NewMemoryBufferFromFile(path)
|
||||
os.Stat(path) // make sure this file is tracked by `go test` caching
|
||||
if err != nil {
|
||||
t.Fatalf("could not read file %s: %v", path, err)
|
||||
}
|
||||
mod, err := ctx.ParseIR(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("could not load module:\n%v", err)
|
||||
}
|
||||
|
||||
// Check all to-be-tested functions.
|
||||
for _, tc := range scanTestTable {
|
||||
// Create an eval object, for testing.
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
|
||||
// Mark some globals dirty, for testing.
|
||||
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
|
||||
|
||||
// Scan for side effects.
|
||||
fn := mod.NamedFunction(tc.name)
|
||||
if fn.IsNil() {
|
||||
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
|
||||
continue
|
||||
}
|
||||
evalPkg := &evalPackage{e, "testdata"}
|
||||
result, err := evalPkg.hasSideEffects(fn)
|
||||
if err != nil {
|
||||
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
|
||||
}
|
||||
|
||||
// Check whether the result is what we expect.
|
||||
if result.severity != tc.severity {
|
||||
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
|
||||
}
|
||||
|
||||
// Check whether the mentioned globals match with what we'd expect.
|
||||
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
|
||||
for global := range result.mentionsGlobals {
|
||||
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
|
||||
}
|
||||
sort.Strings(mentionsGlobalNames)
|
||||
globalsMismatch := false
|
||||
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
|
||||
globalsMismatch = true
|
||||
} else {
|
||||
for i, globalName := range mentionsGlobalNames {
|
||||
if tc.mentionsGlobals[i] != globalName {
|
||||
globalsMismatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if globalsMismatch {
|
||||
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
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
|
||||
@main.nonConst1 = global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = 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"()
|
||||
|
||||
; test the following pattern:
|
||||
; func someValue() int // extern function
|
||||
; var nonConst1 = [4]int{someValue(), 0, 0, 0}
|
||||
%value1 = call i64 @someValue()
|
||||
%gep1 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||
store i64 %value1, i64* %gep1
|
||||
|
||||
; Test that the global really is marked dirty:
|
||||
; var nonConst2 = nonConst1[0]
|
||||
%gep2 = getelementptr [4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0
|
||||
%value2 = load i64, i64* %gep2
|
||||
store i64 %value2, i64* @main.nonConst2
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @"main.init#1"() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i64 @someValue()
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = local_unnamed_addr 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.printint64(i64 5)
|
||||
call void @runtime.printnl()
|
||||
%value1 = call i64 @someValue()
|
||||
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||
store i64 %value2, i64* @main.nonConst2
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @main() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.printint64(i64 3)
|
||||
call void @runtime.printnl()
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i64 @someValue() local_unnamed_addr
|
||||
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"
|
||||
|
||||
@intToPtrResult = global i8 0
|
||||
@ptrToIntResult = global i8 0
|
||||
|
||||
define void @runtime.initAll() {
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init() {
|
||||
call void @testIntToPtr()
|
||||
call void @testPtrToInt()
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testIntToPtr() {
|
||||
%nil = icmp eq i8* inttoptr (i64 1024 to i8*), null
|
||||
br i1 %nil, label %a, label %b
|
||||
a:
|
||||
; should not be reached
|
||||
store i8 1, i8* @intToPtrResult
|
||||
ret void
|
||||
b:
|
||||
; should be reached
|
||||
store i8 2, i8* @intToPtrResult
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @testPtrToInt() {
|
||||
%zero = icmp eq i64 ptrtoint (i8* @ptrToIntResult to i64), 0
|
||||
br i1 %zero, label %a, label %b
|
||||
a:
|
||||
; should not be reached
|
||||
store i8 1, i8* @ptrToIntResult
|
||||
ret void
|
||||
b:
|
||||
; should be reached
|
||||
store i8 2, i8* @ptrToIntResult
|
||||
ret void
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@intToPtrResult = local_unnamed_addr global i8 2
|
||||
@ptrToIntResult = local_unnamed_addr global i8 2
|
||||
|
||||
define void @runtime.initAll() local_unnamed_addr {
|
||||
ret void
|
||||
}
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime._string = type { i8*, i32 }
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
|
||||
@main.m = global %runtime.hashmap* null
|
||||
@main.binaryMap = global %runtime.hashmap* null
|
||||
@main.stringMap = global %runtime.hashmap* null
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
|
||||
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @llvm.lifetime.end.p0i8(i64, i8*)
|
||||
declare void @llvm.lifetime.start.p0i8(i64, i8*)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
; Test that hashmap optimizations generally work (even with lifetimes).
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca %runtime._string
|
||||
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
|
||||
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
|
||||
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
|
||||
store i8 1, i8* %hashmap.key
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
|
||||
|
||||
; Other tests, that can be done in a separate function.
|
||||
call void @main.testNonConstantBinarySet()
|
||||
call void @main.testNonConstantStringSet()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with binary keys).
|
||||
define internal void @main.testNonConstantBinarySet() {
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapBinarySet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
|
||||
; Do the binary set to the newly loaded map.
|
||||
store i8 1, i8* %hashmap.key
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with string keys).
|
||||
define internal void @main.testNonConstantStringSet() {
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapStringSet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
|
||||
; Do the string set to the newly loaded map.
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
Vendored
+28
@@ -0,0 +1,28 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
|
||||
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
|
||||
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] }
|
||||
@"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
|
||||
@"main$alloca.2" = internal global i8 1
|
||||
@"main$alloca.3" = internal global i8 2
|
||||
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
|
||||
@"main$alloca.5" = internal global i8 2
|
||||
@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 }
|
||||
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null)
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
define i64 @returnsConst() {
|
||||
ret i64 0
|
||||
}
|
||||
|
||||
define i64 @returnsArg(i64 %arg) {
|
||||
ret i64 %arg
|
||||
}
|
||||
|
||||
declare i64 @externalCall()
|
||||
|
||||
define i64 @externalCallOnly() {
|
||||
%result = call i64 @externalCall()
|
||||
ret i64 0
|
||||
}
|
||||
|
||||
define i64 @externalCallAndReturn() {
|
||||
%result = call i64 @externalCall()
|
||||
ret i64 %result
|
||||
}
|
||||
|
||||
define i64 @externalCallBranch() {
|
||||
%result = call i64 @externalCall()
|
||||
%zero = icmp eq i64 %result, 0
|
||||
br i1 %zero, label %if.then, label %if.done
|
||||
|
||||
if.then:
|
||||
ret i64 2
|
||||
|
||||
if.done:
|
||||
ret i64 4
|
||||
}
|
||||
|
||||
@cleanGlobalInt = global i64 5
|
||||
define i64 @readCleanGlobal() {
|
||||
%global = load i64, i64* @cleanGlobalInt
|
||||
ret i64 %global
|
||||
}
|
||||
|
||||
@dirtyGlobalInt = global i64 5
|
||||
define i64 @readDirtyGlobal() {
|
||||
%global = load i64, i64* @dirtyGlobalInt
|
||||
ret i64 %global
|
||||
}
|
||||
|
||||
declare i64* @getDirtyPointer()
|
||||
|
||||
define void @storeToPointer() {
|
||||
%ptr = call i64* @getDirtyPointer()
|
||||
store i64 3, i64* %ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
@functionPointer = global i64()* null
|
||||
define i64 @callFunctionPointer() {
|
||||
%fp = load i64()*, i64()** @functionPointer
|
||||
%result = call i64 %fp()
|
||||
ret i64 %result
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user