mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-01 09:37:48 +00:00
Compare commits
499 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1573826005 | |||
| ecd8c2d902 | |||
| a116fd0dc6 | |||
| a590d791bd | |||
| cb147b9475 | |||
| c830f878c6 | |||
| c7c874a487 | |||
| c528a168ee | |||
| 88b9c27dbf | |||
| 37ee4bea40 | |||
| ca4f251050 | |||
| 1e92e5f6c6 | |||
| 3eb9dca695 | |||
| 53794cf339 | |||
| da6c14481f | |||
| fb5516604d | |||
| e9f1ed701a | |||
| 6315db21f7 | |||
| 485a9284e7 | |||
| 602d3d7c78 | |||
| 409688e67a | |||
| d348db4a0d | |||
| 95ab7cb8d1 | |||
| 32de906f6d | |||
| eaab05fc43 | |||
| 4d1945b467 | |||
| fd9422d218 | |||
| bbbe7d43ce | |||
| b3f1dacbb9 | |||
| 5fa1e7163a | |||
| 4b1f92600f | |||
| ca39bc9f35 | |||
| f985f2c376 | |||
| f0936ffccb | |||
| 97d48e5c02 | |||
| a7c53cce06 | |||
| 2d224ae049 | |||
| 6c6fea5387 | |||
| 98bd947817 | |||
| 255f35671d | |||
| 8e88e560a1 | |||
| d45497691f | |||
| ad73986070 | |||
| 931f87f96a | |||
| 192a32f8d9 | |||
| 972f4254eb | |||
| 7d83e2ee5c | |||
| 0f2f73be53 | |||
| a2cc5715ba | |||
| 59d53182bb | |||
| 25c7bfd404 | |||
| 04f520040e | |||
| f57e9622fd | |||
| c25a7cc747 | |||
| 5e5ce98d42 | |||
| a3c4421f39 | |||
| 58565b42cc | |||
| ca7c849da3 | |||
| 6c1301688b | |||
| d05103668f | |||
| 4f7b23c2b7 | |||
| cfae2d4f9a | |||
| 55789fd2c2 | |||
| 478c592b13 | |||
| ab47cea055 | |||
| 98e70c9b19 | |||
| 7434e5a2c7 | |||
| 65c1978965 | |||
| e834d78871 | |||
| 03481789b0 | |||
| 73cf187552 | |||
| b40703e986 | |||
| 00ea0b1d57 | |||
| efa0410075 | |||
| 607d824211 | |||
| 8cc7c6d572 | |||
| cdba4fa8cc | |||
| 0565b7c0e0 | |||
| 444dded92c | |||
| 42785e08e8 | |||
| 2d633e3a28 | |||
| 64d048c47c | |||
| c8e231bc0b | |||
| b00cfc001e | |||
| e127ceac67 | |||
| e5453ebe27 | |||
| 0e267dd230 | |||
| 96e863f0f3 | |||
| 75298bb84b | |||
| e65592599c | |||
| e02f308d43 | |||
| bfe3f68647 | |||
| 2bb70812a8 | |||
| 293f4ea7bc | |||
| c3032660c9 | |||
| d94f42f6e2 | |||
| d8ac7ccaae | |||
| 64058c3efb | |||
| e107efa63f | |||
| 8e33f1c9eb | |||
| 1913cb76a5 | |||
| cd628bcde6 | |||
| d1f445735c | |||
| 28a2ad4757 | |||
| f2e8d7112c | |||
| 52d640967b | |||
| 87e48c1057 | |||
| b406b81416 | |||
| 3a458ec75c | |||
| 7764797061 | |||
| 5f9e339cf3 | |||
| c017ed2242 | |||
| d62a9e24e5 | |||
| b092856238 | |||
| 15d77119c9 | |||
| 95af444896 | |||
| 1f5e4e79aa | |||
| 9912dd6db1 | |||
| 42ec3e2469 | |||
| 4e610a0ee7 | |||
| af65c006e6 | |||
| 98f117fca4 | |||
| 4c95febeee | |||
| 793a3175d3 | |||
| 78d030aa7a | |||
| 62f9f61664 | |||
| 5b82125765 | |||
| 2f248bbf8b | |||
| c5ea1fde61 | |||
| 36dffb5554 | |||
| e8c4c4a865 | |||
| 8b79e82686 | |||
| 22eeed2da1 | |||
| cfbc9be9bf | |||
| 722a3a5c94 | |||
| ed2db8a26d | |||
| fd8938c634 | |||
| fa3dd41a4f | |||
| 003c96edc0 | |||
| ac54302301 | |||
| c43a41165a | |||
| 3145c2747e | |||
| 2c4b507d34 | |||
| ee167f15de | |||
| 422ebeeec7 | |||
| c93ddb630b | |||
| 3edcdb5f0d | |||
| ec325c0643 | |||
| 87c2ccb0b9 | |||
| 6e1eb28ed0 | |||
| 45cf2a5a1a | |||
| 423cae86df | |||
| ea6b65e360 | |||
| e3b98dabfd | |||
| e312cb0fe7 | |||
| 541d8dcd2e | |||
| c8742e2b96 | |||
| 711889bc3f | |||
| 70f8eeaca0 | |||
| 3339d0f47e | |||
| a38b5c4e01 | |||
| b20905da13 | |||
| 95e4dcfb53 | |||
| 841f19f49e | |||
| 2085ffb334 | |||
| b67351babe | |||
| aa5b8d0df7 | |||
| 7c949ad386 | |||
| 1646326164 | |||
| e44cb7e519 | |||
| d1e96fd0a8 | |||
| ff5d0c9886 | |||
| d7ec9ddcd2 | |||
| e7c6bd3730 | |||
| 9f5066aa6f | |||
| 3b24fedf92 | |||
| 8cd2a462b9 | |||
| 25b045d0a7 | |||
| 78acbdf0d9 | |||
| 617e2791ef | |||
| a0908ff55b | |||
| 2f1f8fb075 | |||
| 6f61b83ad5 | |||
| 8dfefb46d1 | |||
| 959442dc82 | |||
| c1aa152a63 | |||
| dd3d8a363a | |||
| bb509ec91d | |||
| 944f022060 | |||
| cd517a30af | |||
| 52d8655eec | |||
| f5786941e5 | |||
| 1f73941c43 | |||
| abeab51d00 | |||
| 9ef75f17bf | |||
| c3992bd77b | |||
| f79e66ac2e | |||
| 4eac212695 | |||
| b661882391 | |||
| 321488dcfe | |||
| 80caf2dab2 | |||
| c466465c32 | |||
| 404b65941a | |||
| 25f3adb47e | |||
| b9043b649d | |||
| 7b761fac78 | |||
| c47cdfa66f | |||
| 768a15c1dd | |||
| c1c3be1aa7 | |||
| b2e72c96f4 | |||
| f706219996 | |||
| 6152a661e8 | |||
| f1a5743f77 | |||
| 9f52fe4e4a | |||
| cb886a35c9 | |||
| 5707022951 | |||
| bd212cc000 | |||
| 6dd5666ed1 | |||
| f145663464 | |||
| 0d66475e10 | |||
| 22b143eba8 | |||
| 3fe13a72bd | |||
| d919905c96 | |||
| 96b1b76483 | |||
| f234df7a50 | |||
| e6d5c26df5 | |||
| ae59e7703e | |||
| e587b1d1b4 | |||
| 57271d7eaa | |||
| 8383552552 | |||
| aa8e12c464 | |||
| e7a05b6e74 | |||
| 2fd8f103ab | |||
| 33f76d1c2e | |||
| ea8f7ba1f9 | |||
| 0ffe5ac2fa | |||
| 7bac93aab3 | |||
| b61751e429 | |||
| 25dac32a88 | |||
| 56cf69a66b | |||
| 04d12bf2ba | |||
| 0b7957d612 | |||
| 61243f6c57 | |||
| 49ec3eb58e | |||
| fa6c1b69ce | |||
| 1e9a41dc94 | |||
| a30671751f | |||
| c246978dd7 | |||
| 72acda22b0 | |||
| f880950c3e | |||
| 1bed192de0 | |||
| 896a848001 | |||
| fb03787b73 | |||
| 83a949647f | |||
| 99a41bec4e | |||
| 312f5d3833 | |||
| 35bf0746a1 | |||
| 8d93b9e545 | |||
| b44d41d9ec | |||
| 4be9802d26 | |||
| 90b42799a2 | |||
| 71bbe93ab2 | |||
| 0535c1bbad | |||
| 4f6d598ea8 | |||
| 16e7dd83a3 | |||
| f23ba3b023 | |||
| 2aa2e750b9 | |||
| bcce296ca3 | |||
| c5ec955081 | |||
| 5d334922d7 | |||
| e9f9a4b750 | |||
| 1406453350 | |||
| 6d3c11627c | |||
| f800f7507c | |||
| 46a7993fb8 | |||
| 9f3dcf3733 | |||
| c849bccb83 | |||
| cffe424849 | |||
| 51938e9d1c | |||
| 19dec048b0 | |||
| bbb2909283 | |||
| aa7c7b7bd9 | |||
| c7bd5405c3 | |||
| dc981ce509 | |||
| c522569378 | |||
| 0db4b13e37 | |||
| 2709d38d63 | |||
| 71d1b70ab7 | |||
| 24676d4366 | |||
| e2f532709f | |||
| dc1ff80e10 | |||
| b5205cc3ca | |||
| a075cbedf5 | |||
| ef613a5db7 | |||
| 1571b8fd34 | |||
| a41b72578b | |||
| 9f3f9d05b8 | |||
| f9865a08bc | |||
| 13db2c13e5 | |||
| ce8ad3650a | |||
| b0b84c48ec | |||
| 5a4dcfb367 | |||
| 99b129d366 | |||
| 303acf5ed6 | |||
| 7d1ce24be5 | |||
| 774c86e21b | |||
| 0cabd4de69 | |||
| 56bbc5bf6d | |||
| 0b44d0bcc5 | |||
| 6275b3a8d1 | |||
| 34b50efdcd | |||
| 42088f938e | |||
| c60c36f0a8 | |||
| e3aa13c2a6 | |||
| b0366743cd | |||
| c54bdf8b51 | |||
| 1fd4669bee | |||
| e36dfe58ac | |||
| 138befd8a2 | |||
| 3d13f9cfe1 | |||
| c4191da2a5 | |||
| 3fdd1a9249 | |||
| 869baca117 | |||
| 9c3e479432 | |||
| 6e480e189d | |||
| dcff8b6478 | |||
| 6862942c99 | |||
| abc478c294 | |||
| 66f76833ad | |||
| 24976a6974 | |||
| e76729c6f3 | |||
| 35c8707867 | |||
| 16e30c97d8 | |||
| 9841ac4acd | |||
| c299386906 | |||
| afcc9d6608 | |||
| 9f06798cb9 | |||
| ce407ddf1b | |||
| b689f14bb2 | |||
| 4141a9f431 | |||
| 52a20cf3bb | |||
| 1aac8a0cb1 | |||
| ba634c4cc7 | |||
| 681be2d4e3 | |||
| 01b917fb11 | |||
| d274e299ac | |||
| fcdaa83368 | |||
| 289e1aa197 | |||
| af02c09b56 | |||
| 801bd2a7ff | |||
| 9c8ef5c48f | |||
| ab41c79de7 | |||
| d3d4acaadf | |||
| 676a78776a | |||
| 8a54615a09 | |||
| 74fe213b15 | |||
| 2e9c3a1d8d | |||
| 5502182642 | |||
| f4b4dd8d62 | |||
| 9f5bd2c460 | |||
| d6cdf8ca28 | |||
| 6c5409bd17 | |||
| cca0eab3da | |||
| e161d5a82c | |||
| 1fb47a2670 | |||
| f1210caba8 | |||
| 5279bebf57 | |||
| 7842e6940d | |||
| 06f231468d | |||
| 3d6921b0e1 | |||
| 9612af466b | |||
| 868933e67c | |||
| f3bdebe2a6 | |||
| 5bae55d755 | |||
| 0bad2c9ff2 | |||
| 5642d72fbe | |||
| 92ed645a11 | |||
| 32a5d46c57 | |||
| d8cc48b09b | |||
| 9bd36597d6 | |||
| d85ac4b3cc | |||
| 3010466c55 | |||
| 36db75b366 | |||
| a5e2b27884 | |||
| a5cf704d83 | |||
| a90865506d | |||
| a867b56e5f | |||
| f159429152 | |||
| a848d720db | |||
| dbc438b2ae | |||
| c4d642007e | |||
| da0161d6ab | |||
| 5af4c073cd | |||
| 154c7c691b | |||
| 65caf777dd | |||
| a4d0877cf0 | |||
| 6ec868710b | |||
| 086645153e | |||
| cf930f652e | |||
| 0dcff35d3a | |||
| 75f13491b6 | |||
| 533d2c9ce0 | |||
| 2f4200a01b | |||
| 939b393325 | |||
| a5ee1ba4b3 | |||
| fb0bb69f62 | |||
| 5917b8baa2 | |||
| 30df912565 | |||
| e9d549d211 | |||
| 6ad631539d | |||
| cda5fffd98 | |||
| ce539ce583 | |||
| 43a31467d3 | |||
| ae92ea149c | |||
| 4568de556e | |||
| 7a4ccd916f | |||
| 4cc1cdf672 | |||
| bb27bbcb41 | |||
| 9c2d2b662b | |||
| 098f900363 | |||
| caf35cfc41 | |||
| abb09e869e | |||
| bb58783158 | |||
| 7abc67107d | |||
| 2540172cc5 | |||
| 0c4f0b1ebf | |||
| 15361c829e | |||
| 3f680b75f3 | |||
| 9a7e633997 | |||
| 39b1f8b6f5 | |||
| 9aa50853b8 | |||
| 9ca0e3f2d1 | |||
| 3cdc110462 | |||
| 7cc687d416 | |||
| ce57a034c3 | |||
| 30bee3afef | |||
| b1d24a72c1 | |||
| 6e24c86320 | |||
| 19a0270303 | |||
| 47410a4b54 | |||
| 0d9c46b59e | |||
| f93b28057a | |||
| 691185f5f4 | |||
| 163df7670a | |||
| 77c70d2758 | |||
| 7c4e83f5c0 | |||
| db27541b1a | |||
| 3bb994da9f | |||
| b3bd891ee0 | |||
| 387bca8e32 | |||
| d424b3d7ea | |||
| c20328472b | |||
| 171f793c1e | |||
| 3364da6f25 | |||
| e99b8a24fe | |||
| 69e1aa4878 | |||
| 7a78b2dc0e | |||
| 2b1d4ce96e | |||
| 3e40b08ba0 | |||
| ffeff55706 | |||
| 1dec9dcbc4 | |||
| ff833ef998 | |||
| 6eeebfeb5c | |||
| 6ab0106af3 | |||
| e690ff0d8c | |||
| 06564cbdb2 | |||
| 47dc76fc34 | |||
| d382f3a259 | |||
| 47dc1087e8 | |||
| c7d8223ab7 | |||
| 3eb33dff5d | |||
| bb146edb47 | |||
| ed9b97cc0d | |||
| b40f250530 | |||
| 184175378f | |||
| d8dbe5748a | |||
| c2bfe6bc8d | |||
| 9a015f4f64 | |||
| 9ad2315079 | |||
| 431e51b8a0 | |||
| 67de8b490d | |||
| bfa29f17da | |||
| 7123941df0 | |||
| 05d2f2c412 | |||
| 9a12d129ab | |||
| 0ecfe9eade | |||
| f50ad3585d | |||
| d39c7abb4d | |||
| 9e61e6fe4d | |||
| d1f90ef59c | |||
| 54602fe0c3 | |||
| c10dcd429c | |||
| b713001313 | |||
| 1096596b69 | |||
| 7b601b3e3c | |||
| ec54e7763d | |||
| 41afb77080 | |||
| 13fe668929 | |||
| 2a72262c33 |
+75
-135
@@ -28,6 +28,7 @@ commands:
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
|
||||
install-node:
|
||||
steps:
|
||||
- run:
|
||||
@@ -44,6 +45,13 @@ commands:
|
||||
command: |
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
sudo apt install ./google-chrome-stable_current_amd64.deb
|
||||
install-wasmtime:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install wasmtime"
|
||||
command: |
|
||||
curl https://wasmtime.dev/install.sh -sSf | bash
|
||||
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
|
||||
install-xtensa-toolchain:
|
||||
parameters:
|
||||
variant:
|
||||
@@ -60,24 +68,27 @@ commands:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-10-v1
|
||||
- llvm-source-11-v2
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-10-v1
|
||||
key: llvm-source-11-v2
|
||||
paths:
|
||||
- llvm-project
|
||||
- llvm-project/clang/lib/Headers
|
||||
- llvm-project/clang/include
|
||||
- llvm-project/lld/include
|
||||
- llvm-project/llvm/include
|
||||
build-wasi-libc:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-v3
|
||||
- wasi-libc-sysroot-v4
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-v3
|
||||
key: wasi-libc-sysroot-v4
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
test-linux:
|
||||
@@ -91,6 +102,7 @@ commands:
|
||||
llvm: "<<parameters.llvm>>"
|
||||
- install-node
|
||||
- install-chrome
|
||||
- install-wasmtime
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
@@ -99,13 +111,13 @@ commands:
|
||||
- run: go install -tags=llvm<<parameters.llvm>> .
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-systemclang-v2
|
||||
- wasi-libc-sysroot-systemclang-v3
|
||||
- run: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-systemclang-v2
|
||||
key: wasi-libc-sysroot-systemclang-v3
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest XTENSA=0
|
||||
- run: make tinygo-test
|
||||
@@ -123,9 +135,9 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -133,7 +145,9 @@ commands:
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||
- install-node
|
||||
- install-wasmtime
|
||||
- install-xtensa-toolchain:
|
||||
variant: "linux-amd64"
|
||||
- restore_cache:
|
||||
@@ -143,25 +157,26 @@ commands:
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-10-linux-v1-assert
|
||||
- llvm-build-11-linux-v4-assert
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# install dependencies
|
||||
sudo apt-get install cmake clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
sudo apt-get install cmake ninja-build
|
||||
# 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
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-10-linux-v1-assert
|
||||
key: llvm-build-11-linux-v4-assert
|
||||
paths:
|
||||
llvm-build
|
||||
- run: make ASSERT=1
|
||||
@@ -169,6 +184,11 @@ commands:
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make ASSERT=1 test
|
||||
environment:
|
||||
# Note: -p=2 limits parallelism to two jobs at a time, which is
|
||||
# necessary to keep memory consumption down and avoid OOM (for a
|
||||
# 2CPU/4GB executor).
|
||||
GOFLAGS: -p=2
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
@@ -183,9 +203,9 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -193,7 +213,9 @@ commands:
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||
- install-node
|
||||
- install-wasmtime
|
||||
- install-xtensa-toolchain:
|
||||
variant: "linux-amd64"
|
||||
- restore_cache:
|
||||
@@ -203,25 +225,26 @@ commands:
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-10-linux-v1
|
||||
- llvm-build-11-linux-v4-noassert
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# install dependencies
|
||||
sudo apt-get install cmake clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
sudo apt-get install cmake ninja-build
|
||||
# hack ninja to use less jobs
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-10-linux-v1
|
||||
key: llvm-build-11-linux-v4-noassert
|
||||
paths:
|
||||
llvm-build
|
||||
- build-wasi-libc
|
||||
@@ -263,51 +286,58 @@ commands:
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.14.darwin-amd64.tar.gz -o go1.14.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
|
||||
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||
- install-xtensa-toolchain:
|
||||
variant: "macos"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v3-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-10-macos-v1
|
||||
- llvm-source-11-macos-v3
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-10-macos-v1
|
||||
key: llvm-source-11-macos-v3
|
||||
paths:
|
||||
- llvm-project
|
||||
- llvm-project/clang/lib/Headers
|
||||
- llvm-project/clang/include
|
||||
- llvm-project/lld/include
|
||||
- llvm-project/llvm/include
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-10-macos-v1
|
||||
- llvm-build-11-macos-v5
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# install dependencies
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-10-macos-v1
|
||||
key: llvm-build-11-macos-v5
|
||||
paths:
|
||||
llvm-build
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-macos-v2
|
||||
- wasi-libc-sysroot-macos-v4
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-macos-v2
|
||||
key: wasi-libc-sysroot-macos-v4
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run:
|
||||
@@ -329,137 +359,47 @@ commands:
|
||||
tinygo version
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
arch-release:
|
||||
steps:
|
||||
- run:
|
||||
name: Install dependencies
|
||||
command: pacman -Sy --noconfirm openssh git pacman-contrib binutils
|
||||
- run:
|
||||
name: Create TinyGo user
|
||||
command: useradd -m tinygo
|
||||
- run:
|
||||
name: Start SSH Agent
|
||||
user: tinygo
|
||||
command: eval $(ssh-agent -s)
|
||||
- run:
|
||||
name: Add ARCH_RELEASE_SSH_PRIVATE_KEY identity
|
||||
user: tinygo
|
||||
command: echo "${ARCH_RELEASE_SSH_PRIVATE_KEY}" | tr -d '\r' | ssh-add -
|
||||
- run:
|
||||
name: Create SSH directory
|
||||
user: tinygo
|
||||
command: mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
- run:
|
||||
name: Add aur.archlinux.org to known hosts
|
||||
user: tinygo
|
||||
command: ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
|
||||
- run:
|
||||
name: Clone tinygo-bin repo
|
||||
user: tinygo
|
||||
command: git clone ssh://aur@aur.archlinux.org/tinygo-bin.git ~/tinygo-bin
|
||||
- run:
|
||||
name: Update package version
|
||||
user: tinygo
|
||||
command: sed -i -E "s/(pkgver=)(.*)$/\1${CIRCLE_TAG}/" ~/tinygo-bin/PKGBUILD
|
||||
- run:
|
||||
name: Update file checksums
|
||||
user: tinygo
|
||||
command: cd ~/tinygo-bin && updpkgsums
|
||||
- run:
|
||||
name: Update .SRCINFO
|
||||
user: tinygo
|
||||
command: cd ~/tinygo-bin && makepkg --printsrcinfo > .SRCINFO
|
||||
# Commit the update
|
||||
- run:
|
||||
name: Set git commit config
|
||||
user: tinygo
|
||||
command: |
|
||||
git config --global user.email "tinygo-bot@tinygo.org" &&
|
||||
git config --global user.name "TinyGo Release Bot"
|
||||
- run:
|
||||
name: Commit and push changes
|
||||
user: tinygo
|
||||
command: |
|
||||
cd ~/tinygo-bin &&
|
||||
git commit -a -m "Update tinygo-bin to v${CIRCLE_TAG}" &&
|
||||
git push origin master
|
||||
|
||||
jobs:
|
||||
test-llvm9-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "9"
|
||||
test-llvm10-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go113:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go114:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go115:
|
||||
docker:
|
||||
- image: circleci/golang:1.15-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
llvm: "10"
|
||||
test-llvm11-go116:
|
||||
docker:
|
||||
- image: circleci/golang:1.16-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "11"
|
||||
assert-test-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-stretch
|
||||
- image: circleci/golang:1.17-stretch
|
||||
steps:
|
||||
- assert-test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-stretch
|
||||
- image: circleci/golang:1.17-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
xcode: "11.1.0" # macOS 10.14
|
||||
steps:
|
||||
- build-macos
|
||||
arch-release:
|
||||
docker:
|
||||
- image: archlinux:latest
|
||||
steps:
|
||||
- arch-release
|
||||
|
||||
|
||||
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm9-go111
|
||||
- test-llvm10-go112
|
||||
- test-llvm10-go113
|
||||
- test-llvm10-go114
|
||||
- test-llvm10-go115
|
||||
- test-llvm11-go116
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
release:
|
||||
jobs:
|
||||
- arch-release:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
# Runs on every semver release
|
||||
only: /v[0-9]+(\.[0-9]+)*(-.*)*/
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
build/
|
||||
llvm-*/
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
name: CI for tinygo-dev docker container
|
||||
on:
|
||||
push:
|
||||
branches: [ dev, fix-docker-llvm-build ]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to GHCR/Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
tinygo/tinygo-dev
|
||||
ghcr.io/${{ github.repository }}/tinygo-dev
|
||||
tags: |
|
||||
type=sha,format=long
|
||||
type=raw,value=latest
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
- name: Log in to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
@@ -16,6 +16,15 @@ src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/kendryte/*.go
|
||||
src/device/kendryte/*.s
|
||||
src/device/rp/*.go
|
||||
src/device/rp/*.s
|
||||
vendor
|
||||
llvm-build
|
||||
llvm-project
|
||||
|
||||
# Ignore files generated by smoketest
|
||||
test.gba
|
||||
test.hex
|
||||
test.nro
|
||||
test.wasm
|
||||
wasm.wasm
|
||||
@@ -20,3 +20,6 @@
|
||||
[submodule "lib/picolibc"]
|
||||
path = lib/picolibc
|
||||
url = https://github.com/keith-packard/picolibc.git
|
||||
[submodule "lib/stm32-svd"]
|
||||
path = lib/stm32-svd
|
||||
url = https://github.com/tinygo-org/stm32-svd
|
||||
|
||||
+1
-6
@@ -23,7 +23,7 @@ different guide:
|
||||
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+)
|
||||
* Go (1.15+)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git
|
||||
* CMake
|
||||
@@ -43,11 +43,6 @@ 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
|
||||
|
||||
+402
@@ -1,3 +1,405 @@
|
||||
0.20.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- add support for Go 1.17
|
||||
- improve Go version detection
|
||||
- add support for the Black Magic Probe (BMP)
|
||||
- add a flag for creating cpu profiles
|
||||
* **compiler**
|
||||
- `builder:` list libraries at the end of the linker command
|
||||
- `builder:` strip debug information at link time instead of at compile time
|
||||
- `builder:` add missing error check for `ioutil.TempFile()`
|
||||
- `builder:` simplify running of jobs
|
||||
- `compiler:` move LLVM math builtin support into the compiler
|
||||
- `compiler:` move math aliases from the runtime to the compiler
|
||||
- `compiler:` add aliases for many hashing packages
|
||||
- `compiler:` add `*ssa.MakeSlice` bounds tests
|
||||
- `compiler:` fix max possible slice
|
||||
- `compiler:` add support for new language features of Go 1.17
|
||||
- `compiler:` fix equally named structs in different scopes
|
||||
- `compiler:` avoid zero-sized alloca in channel operations
|
||||
- `interp:` don't ignore array indices for untyped objects
|
||||
- `interp:` keep reverted package initializers in order
|
||||
- `interp:` fix bug in compiler-time/run-time package initializers
|
||||
- `loader:` fix panic in CGo files with syntax errors
|
||||
- `transform:` improve GC stack slot pass to work around a bug
|
||||
* **standard library**
|
||||
- `crypto/rand`: switch to `arc4random_buf`
|
||||
- `math:` fix `math.Max` and `math.Min`
|
||||
- `math/big`: fix undefined symbols error
|
||||
- `net:` add MAC address implementation
|
||||
- `os:` implement `os.Executable`
|
||||
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
|
||||
- `reflect:` add StructField.IsExported method
|
||||
- `runtime:` reset heapptr to heapStart after preinit()
|
||||
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
|
||||
- `testing:` add subset implementation of Benchmark
|
||||
- `testing:` test testing package using `tinygo test`
|
||||
- `testing:` add support for the `-test.v` flag
|
||||
* **targets**
|
||||
- `386:` bump minimum requirement to the Pentium 4
|
||||
- `arm:` switch to Thumb instruction set on ARM
|
||||
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
|
||||
- `baremetal`,`wasm`: support command line params and environment variables
|
||||
- `cortexm:` fix stack overflow because of unaligned stacks
|
||||
- `esp32c3:` add support for the ESP32-C3 from Espressif
|
||||
- `nrf52840:` fix ram size
|
||||
- `nxpmk66f18:` fix a suspicious bitwise operation
|
||||
- `rp2040:` add SPI support
|
||||
- `rp2040:` add I2C support
|
||||
- `rp2040:` add PWM implementation
|
||||
- `rp2040:` add openocd configuration
|
||||
- `stm32:` add support for PortMask* functions for WS2812 support
|
||||
- `unix:` fix time base for time.Now()
|
||||
- `unix:` check for mmap error and act accordingly
|
||||
- `wasm:` override dlmalloc heap implementation from wasi-libc
|
||||
- `wasm:` align heap to 16 bytes
|
||||
- `wasm:` add support for the crypto/rand package
|
||||
* **boards**
|
||||
- add `DefaultUART` to adafruit boards
|
||||
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
|
||||
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
|
||||
- `feather-nrf52:` fix pin definition of uart
|
||||
- `feather-rp2040:` add pin name definition
|
||||
- `gameboy-advance:` fix ROM header
|
||||
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
|
||||
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
|
||||
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
|
||||
|
||||
|
||||
0.19.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- don't consider compile-only tests as failing
|
||||
- add -test flag for `tinygo list`
|
||||
- escape commands while printing them with the -x flag
|
||||
- make flash-command portable and safer to use
|
||||
- use `extended-remote` instead of `remote` in GDB
|
||||
- detect specific serial port IDs based on USB vid/pid
|
||||
- add a flag to the command line to select the serial implementation
|
||||
* **compiler**
|
||||
- `cgo`: improve constant parser
|
||||
- `compiler`: support chained interrupt handlers
|
||||
- `compiler`: add support for running a builtin in a goroutine
|
||||
- `compiler`: do not emit nil checks for loading closure variables
|
||||
- `compiler`: skip context parameter when starting regular goroutine
|
||||
- `compiler`: refactor method names
|
||||
- `compiler`: add function and global section pragmas
|
||||
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
|
||||
- `interp`: ignore inline assembly in markExternal
|
||||
- `interp`: fix a bug in pointer cast workaround
|
||||
- `loader`: fix testing a main package
|
||||
* **standard library**
|
||||
- `crypto/rand`: replace this package with a TinyGo version
|
||||
- `machine`: make USBCDC global a pointer
|
||||
- `machine`: make UART objects pointer receivers
|
||||
- `machine`: define Serial as the default output
|
||||
- `net`: add initial support for net.IP
|
||||
- `net`: add more net compatibility
|
||||
- `os`: add stub for os.ReadDir
|
||||
- `os`: add FileMode constants from Go 1.16
|
||||
- `os`: add stubs required for net/http
|
||||
- `os`: implement process related functions
|
||||
- `reflect`: implement AppendSlice
|
||||
- `reflect`: add stubs required for net/http
|
||||
- `runtime`: make task.Data a 64-bit integer to avoid overflow
|
||||
- `runtime`: expose memory stats
|
||||
- `sync`: implement NewCond
|
||||
- `syscall`: fix int type in libc version
|
||||
* **targets**
|
||||
- `cortexm`: do not disable interrupts on abort
|
||||
- `cortexm`: bump default stack size to 2048 bytes
|
||||
- `nrf`: avoid heap allocation in waitForEvent
|
||||
- `nrf`: don't trigger a heap allocation in SPI.Transfer
|
||||
- `nrf52840`: add support for flashing with the BOSSA tool
|
||||
- `rp2040`: add support for GPIO input
|
||||
- `rp2040`: add basic support for ADC
|
||||
- `rp2040`: gpio and adc pin definitions
|
||||
- `rp2040`: implement UART
|
||||
- `rp2040`: patch elf to checksum 2nd stage boot
|
||||
- `stm32`: add PWM for most chips
|
||||
- `stm32`: add support for pin interrupts
|
||||
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
|
||||
- `wasi`: remove wasm build tag
|
||||
* **boards**
|
||||
- `feather-rp2040`: add support for this board
|
||||
- `feather-nrf52840-sense`: add board definition for this board
|
||||
- `pca10059`: support flashing from Windows
|
||||
- `nano-rp2040`: add this board
|
||||
- `nano-33-ble`: add support for this board
|
||||
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
|
||||
- `qtpy`: add pin for neopixels
|
||||
- all: add definition for ws2812 for supported boards
|
||||
|
||||
|
||||
0.18.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- drop support for Go 1.11 and 1.12
|
||||
- throw an error when no target is specified on Windows
|
||||
- improve error messages in `getDefaultPort()`, support for multiple ports
|
||||
- remove `-cflags` and `-ldflags` flags
|
||||
- implement `-ldflags="-X ..."`
|
||||
- add `-print-allocs` flag that lets you print all heap allocations
|
||||
- openocd commands in tinygo command line
|
||||
- add `-llvm-features` parameter
|
||||
- match `go test` output
|
||||
- discover USB ports only, this will ignore f.ex. bluetooth
|
||||
- use physicmal path instead of cached GOROOT in function getGoroot
|
||||
- add goroot for snap installs
|
||||
* **compiler**
|
||||
- `builder`: add support for `-opt=0`
|
||||
- `builder`, `compiler`: compile and cache packages in parallel
|
||||
- `builder`: run interp per package
|
||||
- `builder`: cache C and assembly file outputs
|
||||
- `builder`: add support for `-x` flag to print commands
|
||||
- `builder`: add optsize attribute while building the package
|
||||
- `builder`: run function passes per package
|
||||
- `builder`: hard code Clang compiler
|
||||
- `compiler`: do not use `llvm.GlobalContext()`
|
||||
- `compiler`: remove SimpleDCE pass
|
||||
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
|
||||
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
|
||||
- `compiler`: do not check for impossible type asserts
|
||||
- `compiler`: fix use of global context: `llvm.Int32Type()`
|
||||
- `compiler`: add interface IR test
|
||||
- `compiler`: fix lack of method name in interface matching
|
||||
- `compiler`: fix "fragment covers entire variable" bug
|
||||
- `compiler`: optimize string literals and globals
|
||||
- `compiler`: decouple func lowering from interface type codes
|
||||
- `compiler`: add function attributes to some runtime calls
|
||||
- `compiler`: improve position information in error messages
|
||||
- `cgo`: add support for CFLAGS in .c files
|
||||
- `interp`: support GEP on fixed (MMIO) addresses
|
||||
- `interp`: handle `(reflect.Type).Elem()`
|
||||
- `interp`: add support for runtime.interfaceMethod
|
||||
- `interp`: make toLLVMValue return an error instead of panicking
|
||||
- `interp`: add support for switch statement
|
||||
- `interp`: fix phi instruction
|
||||
- `interp`: remove map support
|
||||
- `interp`: support extractvalue/insertvalue with multiple operands
|
||||
- `transform`: optimize string comparisons against ""
|
||||
- `transform`: optimize `reflect.Type` `Implements()` method
|
||||
- `transform`: fix bug in interface lowering when signatures are renamed
|
||||
- `transform`: don't rely on struct name of `runtime.typecodeID`
|
||||
- `transform`: use IPSCCP pass instead of the constant propagation pass
|
||||
- `transform`: fix func lowering assertion failure
|
||||
- `transform`: do not lower zero-sized alloc to alloca
|
||||
- `transform`: split interface and reflect lowering
|
||||
* **standard library**
|
||||
- `runtime`: add dummy debug package
|
||||
- `machine`: fix data shift/mask in newUSBSetup
|
||||
- `machine`: make `machine.I2C0` and similar objects pointers
|
||||
- `machine`: unify usbcdc code
|
||||
- `machine`: refactor PWM support
|
||||
- `machine`: avoid heap allocations in USB code
|
||||
- `reflect`: let `reflect.Type` be of interface type
|
||||
- `reflect`: implement a number of stub functions
|
||||
- `reflect`: check for access in the `Interface` method call
|
||||
- `reflect`: fix `AssignableTo` and `Implements` methods
|
||||
- `reflect`: implement `Value.CanAddr`
|
||||
- `reflect`: implement `Sizeof` and `Alignof` for func values
|
||||
- `reflect`: implement `New` function
|
||||
- `runtime`: implement command line arguments in hosted environments
|
||||
- `runtime`: implement environment variables for Linux
|
||||
- `runtime`: improve timers on nrf, and samd chips
|
||||
* **targets**
|
||||
- all: use -Qunused-arguments only for assembly files
|
||||
- `atmega1280`: add PWM support
|
||||
- `attiny`: remove dummy UART
|
||||
- `atsamd21`: improve SPI
|
||||
- `atsamd51`: fix PWM support in atsamd51p20
|
||||
- `atsamd5x`: improve SPI
|
||||
- `atsamd51`, `atsame5x`: unify samd51 and same5x
|
||||
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
|
||||
- `atsame5x`: add support for CAN
|
||||
- `avr`: remove I2C stubs from attiny support
|
||||
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
|
||||
- `cortexm`: add `__isr_vector` symbol
|
||||
- `cortexm`: disable FPU on Cortex-M4
|
||||
- `cortexm`: clean up Cortex-M target files
|
||||
- `fe310`: fix SPI read
|
||||
- `gameboy-advance`: Fix RGBA color interpretation
|
||||
- `nrf52833`: add PWM support
|
||||
- `stm32l0`: use unified UART logic
|
||||
- `stm32`: move f103 (bluepill) to common i2c code
|
||||
- `stm32`: separate altfunc selection for UART Tx/Rx
|
||||
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
|
||||
- `stm32`: make SPI CLK fast to fix data issue
|
||||
- `stm32`: support SPI on L4 series
|
||||
- `unix`: avoid possible heap allocation with `-opt=0`
|
||||
- `unix`: use conservative GC by default
|
||||
- `unix`: use the tasks scheduler instead of coroutines
|
||||
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
|
||||
- `wasi`: darwin: support basic file io based on libc
|
||||
- `wasm`: only export explicitly exported functions
|
||||
- `wasm`: use WASI ABI for exit function
|
||||
- `wasm`: scan globals conservatively
|
||||
* **boards**
|
||||
- `arduino-mega1280`: add support for the Arduino Mega 1280
|
||||
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
|
||||
- `atsame54-xpro`: add initial support this board
|
||||
- `feather-m4-can`: add initial support for this board
|
||||
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
|
||||
- `lgt92`: update to new UART structure
|
||||
- `microbit`: remove LED constant
|
||||
- `microbit-v2`: add support for S113 SoftDevice
|
||||
- `nucleol432`: add support for this board
|
||||
- `nucleo-l031k6`: add this board
|
||||
- `pca10059`: initial support for this board
|
||||
- `qtpy`: fix msd-volume-name
|
||||
- `qtpy`: fix i2c setting
|
||||
- `teensy40`: move txBuffer allocation to UART declaration
|
||||
- `teensy40`: add UART0 as alias for UART1
|
||||
|
||||
|
||||
0.17.0
|
||||
|
||||
---
|
||||
* **command line**
|
||||
- switch to LLVM 11 for static builds
|
||||
- support gdb debugging with AVR
|
||||
- add support for additional openocd commands
|
||||
- add `-x` flag to print commands
|
||||
- use LLVM 11 by default when linking LLVM dynamically
|
||||
- update go-llvm to use LLVM 11 on macOS
|
||||
- bump go.bug.st/serial to version 1.1.2
|
||||
- do not build LLVM with libxml to work around a bugo on macOS
|
||||
- add support for Go 1.16
|
||||
- support gdb daemonization on Windows
|
||||
- remove support for LLVM 9, to fix CI
|
||||
- kill OpenOCD if it does not exit with a regular quit signal
|
||||
- support `-ocd-output` on Windows
|
||||
* **compiler**
|
||||
- `builder`: parallelize most of the build
|
||||
- `builder`: remove unused cacheKey parameter
|
||||
- `builder`: add -mcpu flag while building libraries
|
||||
- `builder`: wait for running jobs to finish
|
||||
- `cgo`: add support for variadic functions
|
||||
- `compiler`: fix undefined behavior in wordpack
|
||||
- `compiler`: fix incorrect "exported function" panic
|
||||
- `compiler`: fix non-int integer constants (fixing a crash)
|
||||
- `compiler`: refactor and add tests
|
||||
- `compiler`: emit a nil check when slicing an array pointer
|
||||
- `compiler`: saturate float-to-int conversions
|
||||
- `compiler`: test float to int conversions and fix upper-bound calculation
|
||||
- `compiler`: support all kinds of deferred builtins
|
||||
- `compiler`: remove ir package
|
||||
- `compiler`: remove unnecessary main.main call workaround
|
||||
- `compiler`: move the setting of attributes to getFunction
|
||||
- `compiler`: create runtime types lazily when needed
|
||||
- `compiler`: move settings to a separate Config struct
|
||||
- `compiler`: work around an ARM backend bug in LLVM
|
||||
- `interp`: rewrite entire package
|
||||
- `interp`: fix alignment of untyped globals
|
||||
- `loader`: use name "main" for the main package
|
||||
- `loader`: support imports from vendor directories
|
||||
- `stacksize`: add support for DW_CFA_offset_extended
|
||||
- `transform`: show better error message in coroutines lowering
|
||||
* **standard library**
|
||||
- `machine`: accept configuration struct for ADC parameters
|
||||
- `machine`: make I2C.Configure signature consistent
|
||||
- `reflect`: implement PtrTo
|
||||
- `runtime`: refactor to simplify stack switching
|
||||
- `runtime`: put metadata at the top end of the heap
|
||||
* **targets**
|
||||
- `atsam`: add a length check to findPinPadMapping
|
||||
- `atsam`: improve USBCDC
|
||||
- `atsam`: avoid infinite loop when USBCDC is disconnected
|
||||
- `avr`: add SPI support for Atmega based chips
|
||||
- `avr`: use Clang for compiling C and assembly files
|
||||
- `esp32`: implement task based scheduler
|
||||
- `esp32`: enable the FPU
|
||||
- `esp8266`: implement task based scheduler
|
||||
- `esp`: add compiler-rt library
|
||||
- `esp`: add picolibc
|
||||
- `nrf`: refactor code a bit to reduce duplication
|
||||
- `nrf`: use SPIM peripheral instead of the legacy SPI peripheral
|
||||
- `nrf`: update nrfx submodule to latest commit
|
||||
- `nrf52840`: ensure that USB CDC interface is only initialized once
|
||||
- `nrf52840`: improve USBCDC
|
||||
- `stm32`: use stm32-rs SVDs which are of much higher quality
|
||||
- `stm32`: harmonization of UART logic
|
||||
- `stm32`: replace I2C addressable interface with simpler type
|
||||
- `stm32`: fix i2c and add stm32f407 i2c
|
||||
- `stm32`: revert change that adds support for channels in interrupts
|
||||
- `wasm`: implement a growable heap
|
||||
- `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString()
|
||||
- `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR
|
||||
- `wasi`: support env variables based on libc
|
||||
- `wasi`: specify wasi-libc in a different way, to improve error message
|
||||
* **boards**
|
||||
- `matrixportal-m4`: add support for board Adafruit Matrix Portal M4
|
||||
- `mkr1000`: add this board
|
||||
- `nucleo-f722ze`: add this board
|
||||
- `clue`: correct volume name and add alias for release version of Adafruit Clue board
|
||||
- `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR)
|
||||
- `microbit-v2`: add initial support based on work done by @alankrantas thank you!
|
||||
- `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device
|
||||
- `nicenano`: nice!nano board support
|
||||
- `circuitplay-bluefruit`: correct internal I2C pin mapping
|
||||
- `clue`: correct for lack of low frequency crystal
|
||||
- `digispark`: split off attiny85 target
|
||||
- `nucleo-l552ze`: implementation with CLOCK, LED, and UART
|
||||
- `nrf52840-mdk-usb-dongle`: add this board
|
||||
|
||||
0.16.0
|
||||
---
|
||||
|
||||
* **command-line**
|
||||
- add initial support for LLVM 11
|
||||
- make lib64 clang include path check more robust
|
||||
- `build`: improve support for GOARCH=386 and add tests
|
||||
- `gdb`: add support for qemu-user targets
|
||||
- `test`: support non-host tests
|
||||
- `test`: add support for -c and -o flags
|
||||
- `test`: implement some benchmark stubs
|
||||
* **compiler**
|
||||
- `builder`: improve detection of clang on Fedora
|
||||
- `compiler`: fix floating point comparison bugs
|
||||
- `compiler`: implement negate for complex numbers
|
||||
- `loader`: fix linkname in test binaries
|
||||
- `transform`: add missing return pointer restore for regular coroutine tail
|
||||
calls
|
||||
* **standard library**
|
||||
- `machine`: switch default frequency to 4MHz
|
||||
- `machine`: clarify caller's responsibility in `SetInterrupt`
|
||||
- `os`: add `LookupEnv()` stub
|
||||
- `reflect`: implement `Swapper`
|
||||
- `runtime`: fix UTF-8 decoding
|
||||
- `runtime`: gc: use raw stack access whenever possible
|
||||
- `runtime`: use dedicated printfloat32
|
||||
- `runtime`: allow ranging over a nil map
|
||||
- `runtime`: avoid device/nxp dependency in HardFault handler
|
||||
- `testing`: implement dummy Helper method
|
||||
- `testing`: add Run method
|
||||
* **targets**
|
||||
- `arm64`: add support for SVCall intrinsic
|
||||
- `atsamd51`: avoid panic when configuring SPI with SDI=NoPin
|
||||
- `avr`: properly support the `.rodata` section
|
||||
- `esp8266`: implement `Pin.Get` function
|
||||
- `nintendoswitch`: fix crash when printing long lines (> 120)
|
||||
- `nintendoswitch`: add env parser and removed unused stuff
|
||||
- `nrf`: add I2C error checking
|
||||
- `nrf`: give more flexibility in picking SPI speeds
|
||||
- `nrf`: fix nrf52832 flash size
|
||||
- `stm32f103`: support wakeups from interrupts
|
||||
- `stm32f405`: add SPI support
|
||||
- `stm32f405`: add I2C support
|
||||
- `wasi`: add support for this target
|
||||
- `wasi`: use 'generic' ABI by default
|
||||
- `wasi`: remove --no-threads flag from wasm-ld
|
||||
- `wasm`: add instanceof support for WebAssembly
|
||||
- `wasm`: use fixed length buffer for putchar
|
||||
* **boards**
|
||||
- `d1mini`: add this ESP8266 based board
|
||||
- `esp32`: use board definitions instead of chip names
|
||||
- `qtpy`: add board definition for Adafruit QTPy
|
||||
- `teensy40`: add this board
|
||||
|
||||
0.15.0
|
||||
---
|
||||
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
|
||||
FROM golang:1.14 AS tinygo-base
|
||||
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself.
|
||||
FROM golang:1.17 AS tinygo-base
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-10-dev libclang-10-dev lld-10 git
|
||||
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
|
||||
|
||||
COPY . /tinygo
|
||||
|
||||
@@ -29,7 +29,7 @@ COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y make clang-10 libllvm10 lld-10 && \
|
||||
apt-get install -y make clang-11 libllvm11 lld-11 && \
|
||||
make wasi-libc
|
||||
|
||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||
@@ -61,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-10 && \
|
||||
apt-get install -y apt-utils make clang-11 && \
|
||||
make gen-device-nrf && make gen-device-stm32
|
||||
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
@@ -73,7 +73,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
|
||||
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device
|
||||
|
||||
CMD ["tinygo"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2020 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009-2021 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.
|
||||
|
||||
@@ -9,25 +9,10 @@ CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
||||
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
||||
|
||||
# Try to autodetect LLVM build tools.
|
||||
ifneq (, $(shell command -v llvm-build/bin/clang 2> /dev/null))
|
||||
CLANG ?= $(abspath llvm-build/bin/clang)
|
||||
else
|
||||
CLANG ?= clang-10
|
||||
endif
|
||||
ifneq (, $(shell command -v llvm-build/bin/llvm-ar 2> /dev/null))
|
||||
LLVM_AR ?= $(abspath llvm-build/bin/llvm-ar)
|
||||
else ifneq (, $(shell command -v llvm-ar-10 2> /dev/null))
|
||||
LLVM_AR ?= llvm-ar-10
|
||||
else
|
||||
LLVM_AR ?= llvm-ar
|
||||
endif
|
||||
ifneq (, $(shell command -v llvm-build/bin/llvm-nm 2> /dev/null))
|
||||
LLVM_NM ?= $(abspath llvm-build/bin/llvm-nm)
|
||||
else ifneq (, $(shell command -v llvm-nm-10 2> /dev/null))
|
||||
LLVM_NM ?= llvm-nm-10
|
||||
else
|
||||
LLVM_NM ?= llvm-nm
|
||||
endif
|
||||
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
|
||||
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
|
||||
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
|
||||
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
|
||||
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
@@ -37,7 +22,7 @@ export GOROOT = $(shell $(GO) env GOROOT)
|
||||
MD5SUM = md5sum
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
||||
|
||||
# Use CCACHE for LLVM if possible
|
||||
ifneq (, $(shell command -v ccache 2> /dev/null))
|
||||
@@ -51,7 +36,7 @@ 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-nxp gen-device-avr
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
|
||||
@@ -63,62 +48,69 @@ ifeq ($(OS),Windows_NT)
|
||||
# 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
|
||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
|
||||
|
||||
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
|
||||
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) $^
|
||||
LIBCLANG_NAME = libclang
|
||||
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
else ifeq ($(shell uname -s),FreeBSD)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
else
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
|
||||
# Libraries that should be linked in for the statically linked Clang.
|
||||
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
||||
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
# Libraries that should be linked in for the statically linked LLD.
|
||||
LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML
|
||||
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
|
||||
|
||||
# Other libraries that are needed to link TinyGo.
|
||||
EXTRA_LIB_NAMES = LLVMInterpreter
|
||||
|
||||
# These build targets appear to be the only ones necessary to build all TinyGo
|
||||
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
|
||||
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
|
||||
# library path (for ninja).
|
||||
# This list also includes a few tools that are necessary as part of the full
|
||||
# TinyGo build.
|
||||
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIBCLANG_NAME) $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)))
|
||||
|
||||
# For static linking.
|
||||
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_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++14
|
||||
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++14 -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)
|
||||
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -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 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_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing 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-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
|
||||
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
|
||||
ifneq ($(STM32), 0)
|
||||
gen-device: gen-device-stm32
|
||||
endif
|
||||
|
||||
gen-device-avr:
|
||||
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
||||
@@ -132,6 +124,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||
|
||||
gen-device-esp: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/esp
|
||||
|
||||
gen-device-nrf: build/gen-device-svd
|
||||
@@ -155,23 +148,26 @@ gen-device-kendryte: build/gen-device-svd
|
||||
GO111MODULE=off $(GO) fmt ./src/device/kendryte
|
||||
|
||||
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/
|
||||
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||
|
||||
gen-device-rp: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/rp
|
||||
|
||||
# Get LLVM sources.
|
||||
$(LLVM_PROJECTDIR)/README.md:
|
||||
git clone -b xtensa_release_10.0.1 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
llvm-source: $(LLVM_PROJECTDIR)/README.md
|
||||
$(LLVM_PROJECTDIR)/llvm:
|
||||
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
llvm-source: $(LLVM_PROJECTDIR)/llvm
|
||||
|
||||
# Configure LLVM.
|
||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
|
||||
|
||||
|
||||
# Build wasi-libc sysroot
|
||||
@@ -188,16 +184,35 @@ tinygo:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
|
||||
|
||||
test: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
TEST_PACKAGES = \
|
||||
container/heap \
|
||||
container/list \
|
||||
container/ring \
|
||||
crypto/des \
|
||||
crypto/md5 \
|
||||
crypto/sha1 \
|
||||
crypto/sha256 \
|
||||
crypto/sha512 \
|
||||
encoding \
|
||||
encoding/ascii85 \
|
||||
encoding/base32 \
|
||||
encoding/hex \
|
||||
hash/adler32 \
|
||||
hash/fnv \
|
||||
hash/crc64 \
|
||||
math \
|
||||
math/cmplx \
|
||||
testing \
|
||||
text/scanner \
|
||||
unicode/utf8 \
|
||||
|
||||
# Test known-working standard library packages.
|
||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||
# implied -v flag).
|
||||
# TODO: parallelize, and only show failing tests (no implied -v flag).
|
||||
.PHONY: tinygo-test
|
||||
tinygo-test:
|
||||
$(TINYGO) test container/list
|
||||
$(TINYGO) test container/ring
|
||||
$(TINYGO) test text/scanner
|
||||
$(TINYGO) test $(TEST_PACKAGES)
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
@@ -221,6 +236,8 @@ smoketest:
|
||||
@$(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=pca10040 examples/memstats
|
||||
@$(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/pininterrupt
|
||||
@@ -234,8 +251,6 @@ smoketest:
|
||||
# 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=hifive1-qemu examples/serial
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||
@@ -253,12 +268,14 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
|
||||
@$(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
|
||||
@@ -267,6 +284,10 @@ smoketest:
|
||||
@$(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=pca10059 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10059 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
|
||||
@@ -275,22 +296,16 @@ smoketest:
|
||||
@$(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=stm32f4disco-1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
|
||||
@$(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=clue_alpha examples/blinky1
|
||||
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
|
||||
@$(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=grandcentral-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(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
|
||||
@@ -307,8 +322,6 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=particle-xenon 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
|
||||
@@ -329,8 +342,38 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
# test pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -338,8 +381,32 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
|
||||
ifneq ($(STM32), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=lgt92 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=nucleo-f722ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze 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=stm32f4disco-1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(AVR), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -349,6 +416,10 @@ ifneq ($(AVR), 0)
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@@ -357,13 +428,17 @@ ifneq ($(AVR), 0)
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(XTENSA), 0)
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32-wroom-32 examples/blinky1
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
@@ -373,8 +448,16 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
|
||||
@$(MD5SUM) test.nro
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
|
||||
@$(MD5SUM) test.hex
|
||||
ifneq ($(OS),Windows_NT)
|
||||
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
|
||||
endif
|
||||
|
||||
|
||||
wasmtest:
|
||||
$(GO) test ./tests/wasm
|
||||
|
||||
@@ -43,50 +43,76 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following 44 microcontroller boards are currently supported:
|
||||
The following 70 microcontroller boards are currently supported:
|
||||
|
||||
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
|
||||
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
|
||||
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
|
||||
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
|
||||
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
|
||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
||||
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
|
||||
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
|
||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
|
||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
||||
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
|
||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
||||
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
||||
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
|
||||
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
|
||||
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
|
||||
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
|
||||
* [BBC micro:bit](https://microbit.org/)
|
||||
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
|
||||
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
||||
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
|
||||
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
|
||||
* [Nintendo Switch](https://www.nintendo.com/switch/)
|
||||
* [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)
|
||||
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
|
||||
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
|
||||
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
|
||||
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
||||
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
|
||||
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
||||
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
|
||||
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
|
||||
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
||||
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
|
||||
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
|
||||
* [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 "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
|
||||
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
|
||||
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
|
||||
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-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/)
|
||||
|
||||
@@ -148,6 +174,6 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
|
||||
|
||||
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/10.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
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/11.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
|
||||
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
|
||||
|
||||
+9
-9
@@ -12,27 +12,23 @@ jobs:
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.15'
|
||||
version: '1.17'
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
- task: Cache@2
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-10-windows-v1
|
||||
key: llvm-source-11-windows-v1
|
||||
path: llvm-project
|
||||
- task: Bash@3
|
||||
displayName: Download LLVM source
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
make llvm-source
|
||||
# Workaround for bad symlinks:
|
||||
# https://github.com/microsoft/azure-pipelines-tasks/issues/13418
|
||||
rm -f llvm-project/libcxx/test/std/input.output/filesystems/Inputs/static_test_env/bad_symlink
|
||||
script: make llvm-source
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM build
|
||||
inputs:
|
||||
key: llvm-build-10-windows-v1
|
||||
key: llvm-build-11-windows-v5
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
@@ -41,7 +37,11 @@ jobs:
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
choco install ninja
|
||||
# hack ninja to use fewer jobs
|
||||
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- task: Bash@3
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache wasi-libc sysroot
|
||||
inputs:
|
||||
key: wasi-libc-sysroot-v3
|
||||
key: wasi-libc-sysroot-v4
|
||||
path: lib/wasi-libc/sysroot
|
||||
- task: Bash@3
|
||||
displayName: Build wasi-libc
|
||||
|
||||
+791
-256
File diff suppressed because it is too large
Load Diff
+6
-19
@@ -30,10 +30,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
|
||||
// Try to load a given file from the cache. Return "", nil if no cached file can
|
||||
// be found (or the file is stale), return the absolute path if there is a cache
|
||||
// and return an error on I/O errors.
|
||||
//
|
||||
// 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) {
|
||||
func cacheLoad(name string, sourceFiles []string) (string, error) {
|
||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||
cacheStat, err := os.Stat(cachepath)
|
||||
if os.IsNotExist(err) {
|
||||
@@ -58,9 +55,7 @@ func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||
|
||||
// Store the file located at tmppath in the cache with the given name. The
|
||||
// tmppath may or may not be gone afterwards.
|
||||
//
|
||||
// Note: the configKey is ignored, see cacheLoad.
|
||||
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
|
||||
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
||||
// get the last modified time
|
||||
if len(sourceFiles) == 0 {
|
||||
panic("cache: no source files")
|
||||
@@ -74,24 +69,16 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
|
||||
return "", err
|
||||
}
|
||||
cachepath := filepath.Join(dir, name)
|
||||
err = moveFile(tmppath, cachepath)
|
||||
err = copyFile(tmppath, cachepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cachepath, nil
|
||||
}
|
||||
|
||||
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||
// the old file is removed.
|
||||
func moveFile(src, dst string) error {
|
||||
err := os.Rename(src, dst)
|
||||
if err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
// Failed to move, probably a different filesystem.
|
||||
// Do a copy + remove.
|
||||
// copyFile copies the given file from src to dst. It can copy over
|
||||
// a possibly already existing file at the destination.
|
||||
func copyFile(src, dst string) error {
|
||||
inf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
package builder
|
||||
|
||||
// This file implements a wrapper around the C compiler (Clang) which uses a
|
||||
// build cache.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// compileAndCacheCFile compiles a C or assembly file using a build cache.
|
||||
// Compiling the same file again (if nothing changed, including included header
|
||||
// files) the output is loaded from the build cache instead.
|
||||
//
|
||||
// Its operation is a bit complex (more complex than Go package build caching)
|
||||
// because the list of file dependencies is only known after the file is
|
||||
// compiled. However, luckily compilers have a flag to write a list of file
|
||||
// dependencies in Makefile syntax which can be used for caching.
|
||||
//
|
||||
// Because of this complexity, every file has in fact two cached build outputs:
|
||||
// the file itself, and the list of dependencies. Its operation is as follows:
|
||||
//
|
||||
// depfile = hash(path, compiler, cflags, ...)
|
||||
// if depfile exists:
|
||||
// outfile = hash of all files and depfile name
|
||||
// if outfile exists:
|
||||
// # cache hit
|
||||
// return outfile
|
||||
// # cache miss
|
||||
// tmpfile = compile file
|
||||
// read dependencies (side effect of compile)
|
||||
// write depfile
|
||||
// outfile = hash of all files and depfile name
|
||||
// rename tmpfile to outfile
|
||||
//
|
||||
// There are a few edge cases that are not handled:
|
||||
// - If a file is added to an include path, that file may be included instead of
|
||||
// some other file. This would be fixed by also including lookup failures in the
|
||||
// dependencies file, but I'm not aware of a compiler which does that.
|
||||
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
||||
// details.
|
||||
// - A header file may be changed to add/remove an include. This invalidates the
|
||||
// depfile but without invalidating its name. For this reason, the depfile is
|
||||
// written on each new compilation (even when it seems unnecessary). However, it
|
||||
// could in rare cases lead to a stale file fetched from the cache.
|
||||
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
|
||||
// Hash input file.
|
||||
fileHash, err := hashFile(abspath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Create cache key for the dependencies file.
|
||||
buf, err := json.Marshal(struct {
|
||||
Path string
|
||||
Hash string
|
||||
Flags []string
|
||||
LLVMVersion string
|
||||
}{
|
||||
Path: abspath,
|
||||
Hash: fileHash,
|
||||
Flags: cflags,
|
||||
LLVMVersion: llvm.Version,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
depfileNameHashBuf := sha512.Sum512_224(buf)
|
||||
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
|
||||
|
||||
// Load dependencies file, if possible.
|
||||
depfileName := "dep-" + depfileNameHash + ".json"
|
||||
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
||||
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
|
||||
var dependencies []string // sorted list of dependency paths
|
||||
if err == nil {
|
||||
// There is a dependency file, that's great!
|
||||
// Parse it first.
|
||||
err := json.Unmarshal(depfileBuf, &dependencies)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
|
||||
}
|
||||
|
||||
// Obtain hashes of all the files listed as a dependency.
|
||||
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
|
||||
if err == nil {
|
||||
if _, err := os.Stat(outpath); err == nil {
|
||||
return outpath, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
// expected either nil or IsNotExist
|
||||
return "", err
|
||||
}
|
||||
|
||||
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
objTmpFile.Close()
|
||||
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
depTmpFile.Close()
|
||||
flags := append([]string{}, cflags...) // copy cflags
|
||||
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
|
||||
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
||||
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
|
||||
// If this is an assembly file (.s or .S, lowercase or uppercase), then
|
||||
// we'll need to add -Qunused-arguments because many parameters are
|
||||
// relevant to C, not assembly. And with -Werror, having meaningless
|
||||
// flags (for the assembler) is a compiler error.
|
||||
flags = append(flags, "-Qunused-arguments")
|
||||
}
|
||||
if printCommands != nil {
|
||||
printCommands("clang", flags...)
|
||||
}
|
||||
err = runCCompiler(flags...)
|
||||
if err != nil {
|
||||
return "", &commandError{"failed to build", abspath, err}
|
||||
}
|
||||
|
||||
// Create sorted and uniqued slice of dependencies.
|
||||
dependencyPaths, err := readDepFile(depTmpFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
|
||||
dependencySet := make(map[string]struct{}, len(dependencyPaths))
|
||||
var dependencySlice []string
|
||||
for _, path := range dependencyPaths {
|
||||
if _, ok := dependencySet[path]; ok {
|
||||
continue
|
||||
}
|
||||
dependencySet[path] = struct{}{}
|
||||
dependencySlice = append(dependencySlice, path)
|
||||
}
|
||||
sort.Strings(dependencySlice)
|
||||
|
||||
// Write dependencies file.
|
||||
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
_, err = f.Write(buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(f.Name(), depfileCachePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Move temporary object file to final location.
|
||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(objTmpFile.Name(), outpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// Create a cache path (a path in GOCACHE) to store the output of a compiler
|
||||
// job. This path is based on the dep file name (which is a hash of metadata
|
||||
// including compiler flags) and the hash of all input files in the paths slice.
|
||||
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
|
||||
// Hash all input files.
|
||||
fileHashes := make(map[string]string, len(paths))
|
||||
for _, path := range paths {
|
||||
hash, err := hashFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileHashes[path] = hash
|
||||
}
|
||||
|
||||
// Calculate a cache key based on the above hashes.
|
||||
buf, err := json.Marshal(struct {
|
||||
DepfileHash string
|
||||
FileHashes map[string]string
|
||||
}{
|
||||
DepfileHash: depfileNameHash,
|
||||
FileHashes: fileHashes,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
outFileNameBuf := sha512.Sum512_224(buf)
|
||||
cacheKey := hex.EncodeToString(outFileNameBuf[:])
|
||||
|
||||
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// hashFile hashes the given file path and returns the hash as a hex string.
|
||||
func hashFile(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
fileHasher := sha512.New512_224()
|
||||
_, err = io.Copy(fileHasher, f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(fileHasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
|
||||
// file is assumed to have a single target named deps.
|
||||
//
|
||||
// There are roughly three make syntax variants:
|
||||
// - BSD make, which doesn't support any escaping. This means that many special
|
||||
// characters are not supported in file names.
|
||||
// - GNU make, which supports escaping using a backslash but when it fails to
|
||||
// find a file it tries to fall back with the literal path name (to match BSD
|
||||
// make).
|
||||
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
||||
// any weird characters.
|
||||
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
||||
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
||||
// is at least somewhat sane. This last format isn't perfect either: it does not
|
||||
// correctly handle filenames with quote marks in them. Those are generally not
|
||||
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
||||
// it's the most sane of any of the formats so readDepFile will use that format.
|
||||
func readDepFile(filename string) ([]string, error) {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseDepFile(string(buf))
|
||||
}
|
||||
|
||||
func parseDepFile(s string) ([]string, error) {
|
||||
// This function makes no attempt at parsing anything other than Clang -MD
|
||||
// -MV output.
|
||||
|
||||
// For Windows: replace CRLF with LF to make the logic below simpler.
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
|
||||
// Collapse all lines ending in a backslash. These backslashes are really
|
||||
// just a way to continue a line without making very long lines.
|
||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
||||
|
||||
// Only use the first line, which is expected to begin with "deps:".
|
||||
line := strings.SplitN(s, "\n", 2)[0]
|
||||
if !strings.HasPrefix(line, "deps:") {
|
||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
||||
}
|
||||
line = strings.TrimSpace(line[len("deps:"):])
|
||||
|
||||
var deps []string
|
||||
for line != "" {
|
||||
if line[0] == '"' {
|
||||
// File path is quoted. Path ends with double quote.
|
||||
// This does not handle double quotes in path names, which is a
|
||||
// problem on non-Windows systems.
|
||||
line = line[1:]
|
||||
end := strings.IndexByte(line, '"')
|
||||
if end < 0 {
|
||||
return nil, errors.New("readDepFile: path is incorrectly quoted")
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end+1:])
|
||||
deps = append(deps, dep)
|
||||
} else {
|
||||
// File path is not quoted. Path ends in space or EOL.
|
||||
end := strings.IndexFunc(line, unicode.IsSpace)
|
||||
if end < 0 {
|
||||
// last dependency
|
||||
deps = append(deps, line)
|
||||
break
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end:])
|
||||
deps = append(deps, dep)
|
||||
}
|
||||
}
|
||||
return deps, nil
|
||||
}
|
||||
+21
-18
@@ -101,7 +101,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
|
||||
// Target Options
|
||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
|
||||
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||
|
||||
// Use the default target triple if unspecified.
|
||||
@@ -132,13 +132,19 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
|
||||
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);
|
||||
Opts.DwarfDebugFlags =
|
||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
||||
Opts.DwarfDebugProducer =
|
||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
||||
Opts.DebugCompilationDir =
|
||||
std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir));
|
||||
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
||||
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
|
||||
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
|
||||
auto Split = StringRef(Arg).split('=');
|
||||
Opts.DebugPrefixMap.insert(
|
||||
{std::string(Split.first), std::string(Split.second)});
|
||||
}
|
||||
|
||||
// Frontend Options
|
||||
if (Args.hasArg(OPT_INPUT)) {
|
||||
@@ -154,8 +160,9 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
}
|
||||
}
|
||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
||||
Opts.OutputPath = Args.getLastArgValue(OPT_o);
|
||||
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
|
||||
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
|
||||
Opts.SplitDwarfOutput =
|
||||
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
|
||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
||||
StringRef Name = A->getValue();
|
||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
||||
@@ -183,8 +190,9 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
||||
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
|
||||
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
|
||||
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
|
||||
Opts.RelocationModel =
|
||||
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
|
||||
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
|
||||
Opts.IncrementalLinkerCompatible =
|
||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
||||
@@ -314,12 +322,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
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::string FS = llvm::join(Opts.Features, ",");
|
||||
|
||||
std::unique_ptr<MCStreamer> Str;
|
||||
|
||||
@@ -383,7 +386,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||
Str.get()->SwitchSection(AsmLabel);
|
||||
Str.get()->EmitZeros(1);
|
||||
Str.get()->emitZeros(1);
|
||||
}
|
||||
|
||||
// Assembly to object compilation should leverage assembly info.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitDepFile(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{`deps: foo bar`, []string{"foo", "bar"}},
|
||||
{`deps: foo "bar"`, []string{"foo", "bar"}},
|
||||
{`deps: "foo" bar`, []string{"foo", "bar"}},
|
||||
{`deps: "foo bar"`, []string{"foo bar"}},
|
||||
{`deps: "foo bar" `, []string{"foo bar"}},
|
||||
{"deps: foo\nbar", []string{"foo"}},
|
||||
{"deps: foo \\\nbar", []string{"foo", "bar"}},
|
||||
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
|
||||
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
|
||||
} {
|
||||
out, err := parseDepFile(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("test #%d failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(out, tc.out) {
|
||||
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <clang/FrontendTool/Utils.h>
|
||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
||||
#include <llvm/Option/Option.h>
|
||||
#include <llvm/Support/Host.h>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
|
||||
+12
-2
@@ -17,18 +17,28 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.OpenOCDCommands != nil {
|
||||
// Override the OpenOCDCommands from the target spec if specified on
|
||||
// the command-line
|
||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
||||
}
|
||||
|
||||
goroot := goenv.Get("GOROOT")
|
||||
if goroot == "" {
|
||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
|
||||
major, minor, err := goenv.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 > 15 {
|
||||
return nil, fmt.Errorf("requires go version 1.11 through 1.15, got go%d.%d", major, minor)
|
||||
if major != 1 || minor < 15 || minor > 17 {
|
||||
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
|
||||
}
|
||||
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
|
||||
return &compileopts.Config{
|
||||
Options: options,
|
||||
Target: spec,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
|
||||
elfFile, err := elf.Open(executable)
|
||||
if err != nil {
|
||||
return nil, elf.FileHeader{}, err
|
||||
}
|
||||
defer elfFile.Close()
|
||||
|
||||
section := elfFile.Section(sectionName)
|
||||
if section == nil {
|
||||
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
|
||||
}
|
||||
|
||||
data, err := section.Data()
|
||||
|
||||
return data, elfFile.FileHeader, err
|
||||
}
|
||||
|
||||
func replaceElfSection(executable string, sectionName string, data []byte) error {
|
||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
elfFile, err := elf.Open(executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer elfFile.Close()
|
||||
|
||||
section := elfFile.Section(sectionName)
|
||||
if section == nil {
|
||||
return fmt.Errorf("could not find %s section", sectionName)
|
||||
}
|
||||
|
||||
// Implicitly check for compressed sections
|
||||
if section.Size != section.FileSize {
|
||||
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
|
||||
}
|
||||
|
||||
// Only permit complete replacement of section
|
||||
if section.Size != uint64(len(data)) {
|
||||
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
|
||||
}
|
||||
|
||||
// Write the replacement section data
|
||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
||||
return err
|
||||
}
|
||||
+29
-9
@@ -6,6 +6,9 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||
@@ -26,6 +29,7 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
||||
|
||||
// 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.
|
||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||
for _, cmdName := range commands["clang"] {
|
||||
binpath, err := exec.LookPath(cmdName)
|
||||
if err == nil {
|
||||
@@ -40,22 +44,38 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
||||
// Example executable:
|
||||
// /usr/lib/llvm-9/bin/clang
|
||||
// Example include path:
|
||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||
// /usr/lib/llvm-9/lib64/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 {
|
||||
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
|
||||
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
|
||||
// Example include path:
|
||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
|
||||
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
|
||||
if err64 != nil && err32 != nil {
|
||||
// Unexpected.
|
||||
continue
|
||||
}
|
||||
dirnames := make([]string, len(dirs))
|
||||
for i, d := range dirs {
|
||||
dirnames[i] = d.Name()
|
||||
dirnames := make([]string, len(dirs64)+len(dirs32))
|
||||
dirCount := 0
|
||||
for _, d := range dirs32 {
|
||||
name := d.Name()
|
||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
|
||||
dirCount++
|
||||
}
|
||||
}
|
||||
for _, d := range dirs64 {
|
||||
name := d.Name()
|
||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
|
||||
dirCount++
|
||||
}
|
||||
}
|
||||
sort.Strings(dirnames)
|
||||
// Check for the highest version first.
|
||||
for i := len(dirnames) - 1; i >= 0; i-- {
|
||||
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
|
||||
for i := dirCount - 1; i >= 0; i-- {
|
||||
path := filepath.Join(dirnames[i], "include")
|
||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||
if err == nil {
|
||||
return path
|
||||
|
||||
+19
-6
@@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
// An added benefit is that we don't need to check for errors all the time.
|
||||
outf := &bytes.Buffer{}
|
||||
|
||||
// Chip IDs. Source:
|
||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
|
||||
chip_id := map[string]uint16{
|
||||
"esp32": 0x0000,
|
||||
"esp32c3": 0x0005,
|
||||
}[format]
|
||||
|
||||
// Image header.
|
||||
switch format {
|
||||
case "esp32":
|
||||
case "esp32", "esp32c3":
|
||||
// Header format:
|
||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
|
||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
|
||||
// Note: not adding a SHA256 hash as the binary is modified by
|
||||
// esptool.py while flashing and therefore the hash won't be valid
|
||||
// anymore.
|
||||
binary.Write(outf, binary.LittleEndian, struct {
|
||||
magic uint8
|
||||
segment_count uint8
|
||||
@@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
entry_addr uint32
|
||||
wp_pin uint8
|
||||
spi_pin_drv [3]uint8
|
||||
reserved [11]uint8
|
||||
chip_id uint16
|
||||
min_chip_rev uint8
|
||||
reserved [8]uint8
|
||||
hash_appended bool
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
||||
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
chip_id: chip_id,
|
||||
hash_appended: true, // add a SHA256 hash
|
||||
})
|
||||
case "esp8266":
|
||||
@@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
outf.Write(make([]byte, 15-outf.Len()%16))
|
||||
outf.WriteByte(checksum)
|
||||
|
||||
if format == "esp32" {
|
||||
if format != "esp8266" {
|
||||
// SHA256 hash (to protect against image corruption, not for security).
|
||||
hash := sha256.Sum256(outf.Bytes())
|
||||
outf.Write(hash[:])
|
||||
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package builder
|
||||
|
||||
// This file implements a job runner for the compiler, which runs jobs in
|
||||
// parallel while taking care of dependencies.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set to true to enable logging in the job runner. This may help to debug
|
||||
// concurrency or performance issues.
|
||||
const jobRunnerDebug = false
|
||||
|
||||
type jobState uint8
|
||||
|
||||
const (
|
||||
jobStateQueued jobState = iota // not yet running
|
||||
jobStateRunning // running
|
||||
jobStateFinished // finished running
|
||||
)
|
||||
|
||||
// compileJob is a single compiler job, comparable to a single Makefile target.
|
||||
// It is used to orchestrate various compiler tasks that can be run in parallel
|
||||
// but that have dependencies and thus have limitations in how they can be run.
|
||||
type compileJob struct {
|
||||
description string // description, only used for logging
|
||||
dependencies []*compileJob
|
||||
result string // result (path)
|
||||
run func(*compileJob) (err error)
|
||||
state jobState
|
||||
err error // error if finished
|
||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
||||
}
|
||||
|
||||
// dummyCompileJob returns a new *compileJob that produces an output without
|
||||
// doing anything. This can be useful where a *compileJob producing an output is
|
||||
// expected but nothing needs to be done, for example for a load from a cache.
|
||||
func dummyCompileJob(result string) *compileJob {
|
||||
return &compileJob{
|
||||
description: "<dummy>",
|
||||
result: result,
|
||||
}
|
||||
}
|
||||
|
||||
// readyToRun returns whether this job is ready to run: it is itself not yet
|
||||
// started and all dependencies are finished.
|
||||
func (job *compileJob) readyToRun() bool {
|
||||
if job.state != jobStateQueued {
|
||||
// Already running or finished, so shouldn't be run again.
|
||||
return false
|
||||
}
|
||||
|
||||
// Check dependencies.
|
||||
for _, dep := range job.dependencies {
|
||||
if dep.state != jobStateFinished {
|
||||
// A dependency is not finished, so this job has to wait until it
|
||||
// is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All conditions are satisfied.
|
||||
return true
|
||||
}
|
||||
|
||||
// runJobs runs the indicated job and all its dependencies. For every job, all
|
||||
// the dependencies are run first. It returns the error of the first job that
|
||||
// fails.
|
||||
// It runs all jobs in the order of the dependencies slice, depth-first.
|
||||
// Therefore, if some jobs are preferred to run before others, they should be
|
||||
// ordered as such in the job dependencies.
|
||||
func runJobs(job *compileJob) error {
|
||||
// Create a slice of jobs to run, where all dependencies are run in order.
|
||||
jobs := []*compileJob{}
|
||||
addedJobs := map[*compileJob]struct{}{}
|
||||
var addJobs func(*compileJob)
|
||||
addJobs = func(job *compileJob) {
|
||||
if _, ok := addedJobs[job]; ok {
|
||||
return
|
||||
}
|
||||
for _, dep := range job.dependencies {
|
||||
addJobs(dep)
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
addedJobs[job] = struct{}{}
|
||||
}
|
||||
addJobs(job)
|
||||
|
||||
// Create channels to communicate with the workers.
|
||||
doneChan := make(chan *compileJob)
|
||||
workerChan := make(chan *compileJob)
|
||||
defer close(workerChan)
|
||||
|
||||
// Start a number of workers.
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## starting worker", i)
|
||||
}
|
||||
go jobWorker(workerChan, doneChan)
|
||||
}
|
||||
|
||||
// Send each job in the jobs slice to a worker, taking care of job
|
||||
// dependencies.
|
||||
numRunningJobs := 0
|
||||
var totalTime time.Duration
|
||||
start := time.Now()
|
||||
for {
|
||||
// If there are free workers, try starting a new job (if one is
|
||||
// available). If it succeeds, try again to fill the entire worker pool.
|
||||
if numRunningJobs < runtime.NumCPU() {
|
||||
jobToRun := nextJob(jobs)
|
||||
if jobToRun != nil {
|
||||
// Start job.
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## start: ", jobToRun.description)
|
||||
}
|
||||
jobToRun.state = jobStateRunning
|
||||
workerChan <- jobToRun
|
||||
numRunningJobs++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// When there are no jobs running, all jobs in the jobs slice must have
|
||||
// been finished. Therefore, the work is done.
|
||||
if numRunningJobs == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Wait until a job is finished.
|
||||
job := <-doneChan
|
||||
job.state = jobStateFinished
|
||||
numRunningJobs--
|
||||
totalTime += job.duration
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
|
||||
}
|
||||
if job.err != nil {
|
||||
// Wait for running jobs to finish.
|
||||
for numRunningJobs != 0 {
|
||||
<-doneChan
|
||||
numRunningJobs--
|
||||
}
|
||||
// Return error of first failing job.
|
||||
return job.err
|
||||
}
|
||||
}
|
||||
|
||||
// Some statistics, if debugging.
|
||||
if jobRunnerDebug {
|
||||
// Total duration of running all jobs.
|
||||
duration := time.Since(start)
|
||||
fmt.Println("## total: ", duration)
|
||||
|
||||
// The individual time of each job combined. On a multicore system, this
|
||||
// should be lower than the total above.
|
||||
fmt.Println("## job sum: ", totalTime)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextJob returns the first ready-to-run job.
|
||||
// This is an implementation detail of runJobs.
|
||||
func nextJob(jobs []*compileJob) *compileJob {
|
||||
for _, job := range jobs {
|
||||
if job.readyToRun() {
|
||||
return job
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// jobWorker is the goroutine that runs received jobs.
|
||||
// This is an implementation detail of runJobs.
|
||||
func jobWorker(workerChan, doneChan chan *compileJob) {
|
||||
for job := range workerChan {
|
||||
start := time.Now()
|
||||
if job.run != nil {
|
||||
err := job.run(job)
|
||||
if err != nil {
|
||||
job.err = err
|
||||
}
|
||||
}
|
||||
job.duration = time.Since(start)
|
||||
doneChan <- job
|
||||
}
|
||||
}
|
||||
+72
-29
@@ -1,7 +1,6 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -40,34 +39,61 @@ func (l *Library) sourcePaths(target string) []string {
|
||||
}
|
||||
|
||||
// Load the library archive, possibly generating and caching it if needed.
|
||||
func (l *Library) Load(target string) (path string, err error) {
|
||||
// The resulting file is stored in the provided tmpdir, which is expected to be
|
||||
// removed after the Load call.
|
||||
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
||||
job, err := l.load(target, "", tmpdir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = runJobs(job)
|
||||
return job.result, err
|
||||
}
|
||||
|
||||
// load returns a compile job to build this library file for the given target
|
||||
// and CPU. It may return a dummy compileJob if the library build is already
|
||||
// cached. The path is stored as job.result but is only valid after the job has
|
||||
// been run.
|
||||
// The provided tmpdir will be used to store intermediary files and possibly the
|
||||
// output archive file, it is expected to be removed after use.
|
||||
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
|
||||
// Try to load a precompiled library.
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled library for this OS/architecture. Return the path
|
||||
// directly.
|
||||
return precompiledPath, nil
|
||||
return dummyCompileJob(precompiledPath), nil
|
||||
}
|
||||
|
||||
outfile := l.name + "-" + target + ".a"
|
||||
var outfile string
|
||||
if cpu != "" {
|
||||
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
||||
} else {
|
||||
outfile = l.name + "-" + target + ".a"
|
||||
}
|
||||
|
||||
// Try to fetch this library from the cache.
|
||||
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
|
||||
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
||||
// Cache hit.
|
||||
return path, err
|
||||
return dummyCompileJob(path), nil
|
||||
}
|
||||
// Cache miss, build it now.
|
||||
|
||||
dirPrefix := "tinygo-" + l.name
|
||||
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Precalculate the flags to the compiler invocation.
|
||||
// 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.
|
||||
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
if cpu != "" {
|
||||
args = append(args, "-mcpu="+cpu)
|
||||
}
|
||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
||||
}
|
||||
@@ -78,28 +104,45 @@ func (l *Library) Load(target string) (path string, err error) {
|
||||
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
||||
}
|
||||
|
||||
// Compile all sources.
|
||||
// Create job to put all the object files in a single archive. This archive
|
||||
// file is the (static) library file.
|
||||
var objs []string
|
||||
arpath := filepath.Join(dir, l.name+".a")
|
||||
job = &compileJob{
|
||||
description: "ar " + l.name + ".a",
|
||||
result: arpath,
|
||||
run: func(*compileJob) error {
|
||||
// Create an archive of all object files.
|
||||
err := makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store this archive in the cache.
|
||||
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// Create jobs to compile all sources. These jobs are depended upon by the
|
||||
// archive job above, so must be run first.
|
||||
for _, srcpath := range l.sourcePaths(target) {
|
||||
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
|
||||
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
||||
objs = append(objs, objpath)
|
||||
// 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 := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
|
||||
if err != nil {
|
||||
return "", &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
job.dependencies = append(job.dependencies, &compileJob{
|
||||
description: "compile " + srcpath,
|
||||
run: func(*compileJob) error {
|
||||
var compileArgs []string
|
||||
compileArgs = append(compileArgs, args...)
|
||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
||||
err := runCCompiler(compileArgs...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Put all the object files in a single archive. This archive file will be
|
||||
// used to statically link this library.
|
||||
arpath := filepath.Join(dir, l.name+".a")
|
||||
err = makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Store this archive in the cache.
|
||||
return cacheStore(arpath, outfile, commands["clang"][0], l.sourcePaths(target))
|
||||
return job, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
)
|
||||
|
||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
|
||||
|
||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
||||
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
|
||||
|
||||
if options.PrintCommands != nil {
|
||||
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
||||
cmd.Stdout = ioutil.Discard
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+7
-1
@@ -61,7 +61,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
||||
|
||||
progs := make(progSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
|
||||
continue
|
||||
}
|
||||
progs = append(progs, prog)
|
||||
@@ -73,6 +73,12 @@ func extractROM(path string) (uint64, []byte, error) {
|
||||
|
||||
var rom []byte
|
||||
for _, prog := range progs {
|
||||
romEnd := progs[0].Paddr + uint64(len(rom))
|
||||
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
||||
// Sometimes, the linker seems to insert a bit of padding between
|
||||
// segments. Simply zero-fill these parts.
|
||||
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
||||
}
|
||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
||||
if diff > maxPadBytes {
|
||||
|
||||
+4
-13
@@ -9,8 +9,8 @@ import (
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if hasBuiltinTools && command == "clang" {
|
||||
func runCCompiler(flags ...string) error {
|
||||
if hasBuiltinTools {
|
||||
// Compile this with the internal Clang compiler.
|
||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
if headerPath == "" {
|
||||
@@ -23,17 +23,8 @@ func runCCompiler(command string, flags ...string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// 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()
|
||||
// Compile this with an external invocation of the Clang compiler.
|
||||
return execCommand(commands["clang"], flags...)
|
||||
}
|
||||
|
||||
// link invokes a linker with the given name and flags.
|
||||
|
||||
+33
-16
@@ -41,7 +41,9 @@ type cgoPackage struct {
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
ldflags []string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
visitedFiles map[string][]byte
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
@@ -54,9 +56,10 @@ type constantInfo struct {
|
||||
// functionInfo stores some information about a CGo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
variadic bool
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||
@@ -155,9 +158,10 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files. If there is one or more error, it returns these in the []error slice
|
||||
// but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
|
||||
// files, the CFLAGS and LDFLAGS found in #cgo lines, and a map of file hashes
|
||||
// of the accessed C header files. If there is one or more error, it returns
|
||||
// these in the []error slice but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, map[string][]byte, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
@@ -169,13 +173,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||
enums: map[string]enumInfo{},
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
|
||||
|
||||
// Add a new location for the following file.
|
||||
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||
generatedTokenPos.SetLines([]int{0})
|
||||
@@ -184,7 +184,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Find the absolute path for this package.
|
||||
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
||||
if err != nil {
|
||||
return nil, nil, []error{
|
||||
return nil, nil, nil, nil, []error{
|
||||
scanner.Error{
|
||||
Pos: fset.Position(files[0].Pos()),
|
||||
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
||||
@@ -359,7 +359,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
continue
|
||||
}
|
||||
makePathsAbsolute(flags, packagePath)
|
||||
cflags = append(cflags, flags...)
|
||||
p.cflags = append(p.cflags, flags...)
|
||||
case "LDFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
@@ -382,6 +382,13 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
}
|
||||
}
|
||||
|
||||
// Define CFlags that will be used while parsing the package.
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
|
||||
cflagsForCGo = append(cflagsForCGo, p.cflags...)
|
||||
|
||||
// Process all CGo imports.
|
||||
for _, genDecl := range statements {
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
@@ -391,7 +398,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflagsForCGo, position.Filename, position.Line)
|
||||
}
|
||||
|
||||
// Declare functions found by libclang.
|
||||
@@ -426,7 +433,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
return p.generated, p.ldflags, p.errors
|
||||
return p.generated, p.cflags, p.ldflags, p.visitedFiles, p.errors
|
||||
}
|
||||
|
||||
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
||||
@@ -484,6 +491,16 @@ func (p *cgoPackage) addFuncDecls() {
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
if fn.variadic {
|
||||
decl.Doc = &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
&ast.Comment{
|
||||
Slash: fn.pos,
|
||||
Text: "//go:variadic",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
|
||||
+19
-13
@@ -5,13 +5,13 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -23,15 +23,7 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
|
||||
// normalizeResult normalizes Go source code that comes out of tests across
|
||||
// platforms and Go versions.
|
||||
func normalizeResult(result string) string {
|
||||
actual := strings.Replace(result, "\r\n", "\n", -1)
|
||||
|
||||
// Make sure all functions are wrapped, even those that would otherwise be
|
||||
// single-line functions. This is necessary because Go 1.14 changed the way
|
||||
// such functions are wrapped and it's important to have consistent test
|
||||
// results.
|
||||
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
|
||||
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
|
||||
|
||||
actual := strings.ReplaceAll(result, "\r\n", "\n")
|
||||
return actual
|
||||
}
|
||||
|
||||
@@ -41,6 +33,20 @@ func TestCGo(t *testing.T) {
|
||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Skip tests that require specific Go version.
|
||||
if name == "errors" {
|
||||
ok := false
|
||||
for _, version := range build.Default.ReleaseTags {
|
||||
if version == "go1.16" {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Skip("Results for errors test are only valid for Go 1.16+")
|
||||
}
|
||||
}
|
||||
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
fset := token.NewFileSet()
|
||||
@@ -50,7 +56,7 @@ func TestCGo(t *testing.T) {
|
||||
}
|
||||
|
||||
// Process the AST with CGo.
|
||||
cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
cgoAST, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
|
||||
// Check the AST for type errors.
|
||||
var typecheckErrors []error
|
||||
@@ -98,7 +104,7 @@ func TestCGo(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("could not read expected output: %v", err)
|
||||
}
|
||||
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
|
||||
expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n")
|
||||
|
||||
// Check whether the output is as expected.
|
||||
if expected != actual {
|
||||
@@ -139,7 +145,7 @@ func formatDiagnostic(err error) string {
|
||||
msg := err.Error()
|
||||
if runtime.GOOS == "windows" {
|
||||
// Fix Windows path slashes.
|
||||
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
|
||||
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
|
||||
}
|
||||
return "// " + msg + "\n"
|
||||
}
|
||||
|
||||
+164
-74
@@ -11,105 +11,184 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
|
||||
precedences = map[token.Token]int{
|
||||
token.ADD: precedenceAdd,
|
||||
token.SUB: precedenceAdd,
|
||||
token.MUL: precedenceMul,
|
||||
token.QUO: precedenceMul,
|
||||
token.REM: precedenceMul,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
precedenceLowest = iota + 1
|
||||
precedenceAdd
|
||||
precedenceMul
|
||||
precedencePrefix
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This must be done in an init function to avoid an initialization order
|
||||
// failure.
|
||||
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
|
||||
token.IDENT: parseIdent,
|
||||
token.INT: parseBasicLit,
|
||||
token.FLOAT: parseBasicLit,
|
||||
token.STRING: parseBasicLit,
|
||||
token.CHAR: parseBasicLit,
|
||||
token.LPAREN: parseParenExpr,
|
||||
token.SUB: parseUnaryExpr,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
expr, err := parseConstExpr(t, precedenceLowest)
|
||||
t.Next()
|
||||
if t.curToken != token.EOF {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: "unexpected token " + t.token.String(),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
|
||||
}
|
||||
}
|
||||
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:
|
||||
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
|
||||
if t.curToken == token.EOF {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "empty constant",
|
||||
}
|
||||
default:
|
||||
}
|
||||
prefix := prefixParseFns[t.curToken]
|
||||
if prefix == nil {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: fmt.Sprintf("unexpected token %s", t.token),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
|
||||
}
|
||||
}
|
||||
leftExpr, err := prefix(t)
|
||||
|
||||
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
|
||||
switch t.peekToken {
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
|
||||
t.Next()
|
||||
leftExpr, err = parseBinaryExpr(t, leftExpr)
|
||||
}
|
||||
}
|
||||
|
||||
return leftExpr, err
|
||||
}
|
||||
|
||||
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
return &ast.Ident{
|
||||
NamePos: t.curPos,
|
||||
Name: "C." + t.curValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
return &ast.BasicLit{
|
||||
ValuePos: t.curPos,
|
||||
Kind: t.curToken,
|
||||
Value: t.curValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
lparen := t.curPos
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t, precedenceLowest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Next()
|
||||
if t.curToken != token.RPAREN {
|
||||
return nil, unexpectedToken(t, token.RPAREN)
|
||||
}
|
||||
expr := &ast.ParenExpr{
|
||||
Lparen: lparen,
|
||||
X: x,
|
||||
Rparen: t.curPos,
|
||||
}
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
|
||||
expression := &ast.BinaryExpr{
|
||||
X: left,
|
||||
Op: t.curToken,
|
||||
OpPos: t.curPos,
|
||||
}
|
||||
precedence := precedences[t.curToken]
|
||||
t.Next()
|
||||
right, err := parseConstExpr(t, precedence)
|
||||
expression.Y = right
|
||||
return expression, err
|
||||
}
|
||||
|
||||
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
expression := &ast.UnaryExpr{
|
||||
OpPos: t.curPos,
|
||||
Op: t.curToken,
|
||||
}
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t, precedencePrefix)
|
||||
expression.X = x
|
||||
return expression, err
|
||||
}
|
||||
|
||||
// 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),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, 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
|
||||
curPos, peekPos token.Pos
|
||||
fset *token.FileSet
|
||||
curToken, peekToken token.Token
|
||||
curValue, peekValue 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,
|
||||
peekPos: start,
|
||||
fset: fset,
|
||||
buf: buf,
|
||||
peekToken: token.ILLEGAL,
|
||||
}
|
||||
t.Next() // Parse the first token.
|
||||
// Parse the first two tokens (cur and peek).
|
||||
t.Next()
|
||||
t.Next()
|
||||
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))
|
||||
// The previous peek is now the current token.
|
||||
t.curPos = t.peekPos
|
||||
t.curToken = t.peekToken
|
||||
t.curValue = t.peekValue
|
||||
|
||||
// Parse the next peek token.
|
||||
t.peekPos += token.Pos(len(t.curValue))
|
||||
for {
|
||||
if len(t.buf) == 0 {
|
||||
t.token = token.EOF
|
||||
t.peekToken = token.EOF
|
||||
return
|
||||
}
|
||||
c := t.buf[0]
|
||||
@@ -118,17 +197,28 @@ func (t *tokenizer) Next() {
|
||||
// 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.peekPos++
|
||||
t.buf = t.buf[1:]
|
||||
case c == '(' || c == ')':
|
||||
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
|
||||
// Single-character tokens.
|
||||
// TODO: ++ (increment) and -- (decrement) operators.
|
||||
switch c {
|
||||
case '(':
|
||||
t.token = token.LPAREN
|
||||
t.peekToken = token.LPAREN
|
||||
case ')':
|
||||
t.token = token.RPAREN
|
||||
t.peekToken = token.RPAREN
|
||||
case '+':
|
||||
t.peekToken = token.ADD
|
||||
case '-':
|
||||
t.peekToken = token.SUB
|
||||
case '*':
|
||||
t.peekToken = token.MUL
|
||||
case '/':
|
||||
t.peekToken = token.QUO
|
||||
case '%':
|
||||
t.peekToken = token.REM
|
||||
}
|
||||
t.value = t.buf[:1]
|
||||
t.peekValue = t.buf[:1]
|
||||
t.buf = t.buf[1:]
|
||||
return
|
||||
case c >= '0' && c <= '9':
|
||||
@@ -146,17 +236,17 @@ func (t *tokenizer) Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekValue = 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")
|
||||
t.peekToken = token.FLOAT
|
||||
t.peekValue = strings.TrimRight(t.peekValue, "f")
|
||||
} else {
|
||||
t.token = token.INT
|
||||
t.value = strings.TrimRight(t.value, "uUlL")
|
||||
t.peekToken = token.INT
|
||||
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
|
||||
}
|
||||
return
|
||||
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
||||
@@ -170,9 +260,9 @@ func (t *tokenizer) Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
t.token = token.IDENT
|
||||
t.peekToken = token.IDENT
|
||||
return
|
||||
case c == '"':
|
||||
// String constant. Find the first '"' character that is not
|
||||
@@ -188,8 +278,8 @@ func (t *tokenizer) Next() {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.STRING
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekToken = token.STRING
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
case c == '\'':
|
||||
@@ -206,12 +296,12 @@ func (t *tokenizer) Next() {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.CHAR
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekToken = token.CHAR
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
default:
|
||||
t.token = token.ILLEGAL
|
||||
t.peekToken = token.ILLEGAL
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+19
-2
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
|
||||
{`(5)`, `(5)`},
|
||||
{`(((5)))`, `(5)`},
|
||||
{`)`, `error: 1:1: unexpected token )`},
|
||||
{`5)`, `error: 1:2: unexpected token )`},
|
||||
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
|
||||
{" \t)", `error: 1:4: unexpected token )`},
|
||||
{`5.8f`, `5.8`},
|
||||
{`foo`, `C.foo`},
|
||||
@@ -30,7 +30,24 @@ func TestParseConst(t *testing.T) {
|
||||
{`'a'`, `'a'`},
|
||||
{`0b10`, `0b10`},
|
||||
{`0x1234_5678`, `0x1234_5678`},
|
||||
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
|
||||
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
|
||||
// Binary operators.
|
||||
{`5+5`, `5 + 5`},
|
||||
{`5-5`, `5 - 5`},
|
||||
{`5*5`, `5 * 5`},
|
||||
{`5/5`, `5 / 5`},
|
||||
{`5%5`, `5 % 5`},
|
||||
{`(5/5)`, `(5 / 5)`},
|
||||
{`1 - 2`, `1 - 2`},
|
||||
{`1 - 2 + 3`, `1 - 2 + 3`},
|
||||
{`1 - 2 * 3`, `1 - 2*3`},
|
||||
{`(1 - 2) * 3`, `(1 - 2) * 3`},
|
||||
{`1 * 2 - 3`, `1*2 - 3`},
|
||||
{`1 * (2 - 3)`, `1 * (2 - 3)`},
|
||||
// Unary operators.
|
||||
{`-5`, `-5`},
|
||||
{`-5-2`, `-5 - 2`},
|
||||
{`5 - - 2`, `5 - -2`},
|
||||
} {
|
||||
fset := token.NewFileSet()
|
||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||
|
||||
+37
-4
@@ -4,6 +4,7 @@ package cgo
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
@@ -56,6 +57,7 @@ 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);
|
||||
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -114,6 +116,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
// Report parser and type errors.
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
@@ -134,10 +137,36 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
}
|
||||
}
|
||||
|
||||
// Extract information required by CGo.
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
|
||||
// Determine files read during CGo processing, for caching.
|
||||
inclusionCallback := func(includedFile C.CXFile) {
|
||||
// Get full file path.
|
||||
path := getString(C.clang_getFileName(includedFile))
|
||||
|
||||
// Get contents of file (that should be in-memory).
|
||||
size := C.size_t(0)
|
||||
rawData := C.clang_getFileContents(unit, includedFile, &size)
|
||||
if rawData == nil {
|
||||
// Sanity check. This should (hopefully) never trigger.
|
||||
panic("libclang: file contents was not loaded")
|
||||
}
|
||||
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
|
||||
|
||||
// Hash the contents if it isn't hashed yet.
|
||||
if _, ok := p.visitedFiles[path]; !ok {
|
||||
// already stored
|
||||
sum := sha512.Sum512_224(data)
|
||||
p.visitedFiles[path] = sum[:]
|
||||
}
|
||||
}
|
||||
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
|
||||
defer storedRefs.Remove(inclusionCallbackRef)
|
||||
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
|
||||
}
|
||||
|
||||
//export tinygo_clang_globals_visitor
|
||||
@@ -152,12 +181,10 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{
|
||||
pos: pos,
|
||||
pos: pos,
|
||||
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
|
||||
}
|
||||
p.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
@@ -774,3 +801,9 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
//export tinygo_clang_inclusion_visitor
|
||||
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
|
||||
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
|
||||
callback(includedFile)
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
// +build !byollvm
|
||||
// +build !llvm9
|
||||
// +build !llvm10
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// +build !byollvm
|
||||
// +build llvm10
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -1,14 +0,0 @@
|
||||
// +build !byollvm
|
||||
// +build llvm9
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#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/llvm9/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/llvm9/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
Vendored
+3
-3
@@ -1,13 +1,13 @@
|
||||
// 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 )
|
||||
// testdata/errors.go:13:23: unexpected token ), expected end of expression
|
||||
|
||||
// Type checking errors after CGo processing:
|
||||
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
|
||||
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
|
||||
// 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
|
||||
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
|
||||
|
||||
package main
|
||||
|
||||
|
||||
Vendored
+10
@@ -104,6 +104,10 @@ typedef struct {
|
||||
unsigned char e : 3;
|
||||
// Note that C++ allows bitfields bigger than the underlying type.
|
||||
} bitfield_t;
|
||||
|
||||
// Function signatures.
|
||||
void variadic0();
|
||||
void variadic2(int x, int y, ...);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -163,3 +167,9 @@ func accessUnion() {
|
||||
var _ *C.int = union2d.unionfield_i()
|
||||
var _ *[2]float64 = union2d.unionfield_d()
|
||||
}
|
||||
|
||||
// Test function signatures.
|
||||
func accessFunctions() {
|
||||
C.variadic0()
|
||||
C.variadic2(3, 5)
|
||||
}
|
||||
|
||||
Vendored
+10
-15
@@ -4,6 +4,11 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
func C.variadic0() //go:variadic
|
||||
func C.variadic2(x C.int, y C.int) //go:variadic
|
||||
var C.variadic0$funcaddr unsafe.Pointer
|
||||
var C.variadic2$funcaddr unsafe.Pointer
|
||||
|
||||
const C.option2A = 20
|
||||
const C.optionA = 0
|
||||
const C.optionB = 1
|
||||
@@ -62,9 +67,7 @@ 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) 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
|
||||
}
|
||||
@@ -100,15 +103,9 @@ type C.struct_type1 struct {
|
||||
}
|
||||
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))
|
||||
}
|
||||
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 }
|
||||
|
||||
@@ -133,9 +130,7 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
|
||||
|
||||
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_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))
|
||||
}
|
||||
|
||||
+96
-55
@@ -7,7 +7,6 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
@@ -22,29 +21,6 @@ type Config struct {
|
||||
TestConfig TestConfig
|
||||
}
|
||||
|
||||
// FuncValueImplementation is an enum for the particular implementations of Go
|
||||
// func values.
|
||||
type FuncValueImplementation int
|
||||
|
||||
// These constants describe the various possible implementations of Go func
|
||||
// values.
|
||||
const (
|
||||
FuncValueNone FuncValueImplementation = iota
|
||||
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct containing
|
||||
// the free variables, or it may be undef if the function being pointed to
|
||||
// doesn't need a context. The function pointer is a regular function
|
||||
// pointer.
|
||||
FuncValueDoubleword
|
||||
|
||||
// As funcValueDoubleword, but with the function pointer replaced by a
|
||||
// unique ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
FuncValueSwitch
|
||||
)
|
||||
|
||||
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||
func (c *Config) Triple() string {
|
||||
return c.Target.Triple
|
||||
@@ -79,7 +55,7 @@ func (c *Config) GOARCH() string {
|
||||
|
||||
// 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()}...)
|
||||
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
||||
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
@@ -104,12 +80,7 @@ func (c *Config) GC() string {
|
||||
if c.Target.GC != "" {
|
||||
return c.Target.GC
|
||||
}
|
||||
for _, tag := range c.Target.BuildTags {
|
||||
if tag == "baremetal" || tag == "wasm" {
|
||||
return "conservative"
|
||||
}
|
||||
}
|
||||
return "extalloc"
|
||||
return "conservative"
|
||||
}
|
||||
|
||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||
@@ -118,12 +89,12 @@ func (c *Config) NeedsStackObjects() bool {
|
||||
switch c.GC() {
|
||||
case "conservative", "extalloc":
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "baremetal" {
|
||||
return false
|
||||
if tag == "tinygo.wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -142,16 +113,59 @@ func (c *Config) Scheduler() string {
|
||||
return "coroutines"
|
||||
}
|
||||
|
||||
// Serial returns the serial implementation for this build configuration: uart,
|
||||
// usb (meaning USB-CDC), or none.
|
||||
func (c *Config) Serial() string {
|
||||
if c.Options.Serial != "" {
|
||||
return c.Options.Serial
|
||||
}
|
||||
if c.Target.Serial != "" {
|
||||
return c.Target.Serial
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
||||
// threshold as used in the LLVM optimization pipeline.
|
||||
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
||||
switch c.Options.Opt {
|
||||
case "none", "0":
|
||||
return 0, 0, 0 // -O0
|
||||
case "1":
|
||||
return 1, 0, 0 // -O1
|
||||
case "2":
|
||||
return 2, 0, 225 // -O2
|
||||
case "s":
|
||||
return 2, 1, 225 // -Os
|
||||
case "z":
|
||||
return 2, 2, 5 // -Oz, default
|
||||
default:
|
||||
// This is not shown to the user: valid choices are already checked as
|
||||
// part of Options.Verify(). It is here as a sanity check.
|
||||
panic("unknown optimization level: -opt=" + c.Options.Opt)
|
||||
}
|
||||
}
|
||||
|
||||
// FuncImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Config) FuncImplementation() FuncValueImplementation {
|
||||
// Always pick the switch implementation, as it allows the use of blocking
|
||||
// inside a function that is used as a func value.
|
||||
func (c *Config) FuncImplementation() string {
|
||||
switch c.Scheduler() {
|
||||
case "none", "coroutines":
|
||||
return FuncValueSwitch
|
||||
case "tasks":
|
||||
return FuncValueDoubleword
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct
|
||||
// containing the free variables, or it may be undef if the function
|
||||
// being pointed to doesn't need a context. The function pointer is a
|
||||
// regular function pointer.
|
||||
return "doubleword"
|
||||
case "none", "coroutines":
|
||||
// As "doubleword", but with the function pointer replaced by a unique
|
||||
// ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
// Pick the switch implementation with the coroutines scheduler, as it
|
||||
// allows the use of blocking inside a function that is used as a func
|
||||
// value.
|
||||
return "switch"
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
@@ -174,21 +188,29 @@ func (c *Config) AutomaticStackSize() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
||||
// calculates and patches in the checksum for the 2nd stage bootloader.
|
||||
func (c *Config) RP2040BootPatch() bool {
|
||||
if c.Target.RP2040BootPatch != nil {
|
||||
return *c.Target.RP2040BootPatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 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...)
|
||||
var cflags []string
|
||||
for _, flag := range c.Target.CFlags {
|
||||
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
|
||||
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
||||
}
|
||||
if c.Target.Libc == "picolibc" {
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
|
||||
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
|
||||
}
|
||||
if c.Debug() {
|
||||
cflags = append(cflags, "-g")
|
||||
}
|
||||
// Always emit debug information. It is optionally stripped at link time.
|
||||
cflags = append(cflags, "-g")
|
||||
return cflags
|
||||
}
|
||||
|
||||
@@ -198,17 +220,11 @@ func (c *Config) CFlags() []string {
|
||||
func (c *Config) LDFlags() []string {
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// Merge and adjust LDFlags.
|
||||
ldflags := append([]string{}, c.Options.LDFlags...)
|
||||
var ldflags []string
|
||||
for _, flag := range c.Target.LDFlags {
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -233,8 +249,9 @@ 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.
|
||||
// Debug returns whether debug (DWARF) information should be retained by the
|
||||
// linker. By default, debug information is retained but it can be removed with
|
||||
// the -no-debug flag.
|
||||
func (c *Config) Debug() bool {
|
||||
return c.Options.Debug
|
||||
}
|
||||
@@ -258,6 +275,11 @@ func (c *Config) BinaryFormat(ext string) string {
|
||||
// More information:
|
||||
// https://github.com/Microsoft/uf2
|
||||
return "uf2"
|
||||
case ".zip":
|
||||
if c.Target.BinaryFormat != "" {
|
||||
return c.Target.BinaryFormat
|
||||
}
|
||||
return "zip"
|
||||
default:
|
||||
// Use the ELF format for unrecognized file formats.
|
||||
return "elf"
|
||||
@@ -275,6 +297,9 @@ func (c *Config) Programmer() (method, openocdInterface string) {
|
||||
case "openocd", "msd", "command":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||
case "bmp":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, ""
|
||||
default:
|
||||
// The -programmer flag specifies something else, assume it specifies
|
||||
// the OpenOCD interface name.
|
||||
@@ -303,6 +328,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
||||
}
|
||||
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
||||
for _, cmd := range c.Target.OpenOCDCommands {
|
||||
args = append(args, "-c", cmd)
|
||||
}
|
||||
if c.Target.OpenOCDTransport != "" {
|
||||
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||
}
|
||||
@@ -329,6 +357,19 @@ func (c *Config) RelocationModel() string {
|
||||
return "static"
|
||||
}
|
||||
|
||||
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
|
||||
// the value is overridden by `-wasm-abi` flag if it is provided
|
||||
func (c *Config) WasmAbi() string {
|
||||
if c.Options.WasmAbi != "" {
|
||||
return c.Options.WasmAbi
|
||||
}
|
||||
return c.Target.WasmAbi
|
||||
}
|
||||
|
||||
func (c *Config) LLVMFeatures() string {
|
||||
return c.Options.LLVMFeatures
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
|
||||
+39
-18
@@ -2,37 +2,43 @@ package compileopts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
|
||||
validSerialOptions = []string{"none", "uart", "usb"}
|
||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||
validPanicStrategyOptions = []string{"print", "trap"}
|
||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
||||
)
|
||||
|
||||
// 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
|
||||
PrintStacks bool
|
||||
CFlags []string
|
||||
LDFlags []string
|
||||
Tags string
|
||||
WasmAbi string
|
||||
HeapSize int64
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
Target string
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
Serial string
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands func(cmd string, args ...string)
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintStacks bool
|
||||
Tags string
|
||||
WasmAbi string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
OpenOCDCommands []string
|
||||
LLVMFeatures string
|
||||
}
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
@@ -55,6 +61,15 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.Serial != "" {
|
||||
valid := isInArray(validSerialOptions, o.Serial)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
||||
o.Serial,
|
||||
strings.Join(validSerialOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if o.PrintSizes != "" {
|
||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||
if !valid {
|
||||
@@ -73,6 +88,12 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.Opt != "" {
|
||||
if !isInArray(validOptOptions, o.Opt) {
|
||||
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+94
-113
@@ -7,7 +7,9 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -29,7 +31,7 @@ type TargetSpec struct {
|
||||
BuildTags []string `json:"build-tags"`
|
||||
GC string `json:"gc"`
|
||||
Scheduler string `json:"scheduler"`
|
||||
Compiler string `json:"compiler"`
|
||||
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
|
||||
Linker string `json:"linker"`
|
||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||
Libc string `json:"libc"`
|
||||
@@ -39,10 +41,12 @@ type TargetSpec struct {
|
||||
LDFlags []string `json:"ldflags"`
|
||||
LinkerScript string `json:"linkerscript"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
Emulator []string `json:"emulator"`
|
||||
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
|
||||
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
||||
FlashCommand string `json:"flash-command"`
|
||||
GDB string `json:"gdb"`
|
||||
GDB []string `json:"gdb"`
|
||||
PortReset string `json:"flash-1200-bps-reset"`
|
||||
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
|
||||
FlashMethod string `json:"flash-method"`
|
||||
FlashVolume string `json:"msd-volume-name"`
|
||||
FlashFilename string `json:"msd-firmware-name"`
|
||||
@@ -51,105 +55,53 @@ type TargetSpec struct {
|
||||
OpenOCDInterface string `json:"openocd-interface"`
|
||||
OpenOCDTarget string `json:"openocd-target"`
|
||||
OpenOCDTransport string `json:"openocd-transport"`
|
||||
OpenOCDCommands []string `json:"openocd-commands"`
|
||||
JLinkDevice string `json:"jlink-device"`
|
||||
CodeModel string `json:"code-model"`
|
||||
RelocationModel string `json:"relocation-model"`
|
||||
WasmAbi string `json:"wasm-abi"`
|
||||
}
|
||||
|
||||
// copyProperties copies all properties that are set in spec2 into itself.
|
||||
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||
// TODO: simplify this using reflection? Inherits and BuildTags are special
|
||||
// cases, but the rest can simply be copied if set.
|
||||
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
|
||||
if spec2.Triple != "" {
|
||||
spec.Triple = spec2.Triple
|
||||
}
|
||||
if spec2.CPU != "" {
|
||||
spec.CPU = spec2.CPU
|
||||
}
|
||||
spec.Features = append(spec.Features, spec2.Features...)
|
||||
if spec2.GOOS != "" {
|
||||
spec.GOOS = spec2.GOOS
|
||||
}
|
||||
if spec2.GOARCH != "" {
|
||||
spec.GOARCH = spec2.GOARCH
|
||||
}
|
||||
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
|
||||
if spec2.GC != "" {
|
||||
spec.GC = spec2.GC
|
||||
}
|
||||
if spec2.Scheduler != "" {
|
||||
spec.Scheduler = spec2.Scheduler
|
||||
}
|
||||
if spec2.Compiler != "" {
|
||||
spec.Compiler = spec2.Compiler
|
||||
}
|
||||
if spec2.Linker != "" {
|
||||
spec.Linker = spec2.Linker
|
||||
}
|
||||
if spec2.RTLib != "" {
|
||||
spec.RTLib = spec2.RTLib
|
||||
}
|
||||
if spec2.Libc != "" {
|
||||
spec.Libc = spec2.Libc
|
||||
}
|
||||
if spec2.AutoStackSize != nil {
|
||||
spec.AutoStackSize = spec2.AutoStackSize
|
||||
}
|
||||
if spec2.DefaultStackSize != 0 {
|
||||
spec.DefaultStackSize = spec2.DefaultStackSize
|
||||
}
|
||||
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.FlashCommand != "" {
|
||||
spec.FlashCommand = spec2.FlashCommand
|
||||
}
|
||||
if spec2.GDB != "" {
|
||||
spec.GDB = spec2.GDB
|
||||
}
|
||||
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.UF2FamilyID != "" {
|
||||
spec.UF2FamilyID = spec2.UF2FamilyID
|
||||
}
|
||||
if spec2.BinaryFormat != "" {
|
||||
spec.BinaryFormat = spec2.BinaryFormat
|
||||
}
|
||||
if spec2.OpenOCDInterface != "" {
|
||||
spec.OpenOCDInterface = spec2.OpenOCDInterface
|
||||
}
|
||||
if spec2.OpenOCDTarget != "" {
|
||||
spec.OpenOCDTarget = spec2.OpenOCDTarget
|
||||
}
|
||||
if spec2.OpenOCDTransport != "" {
|
||||
spec.OpenOCDTransport = spec2.OpenOCDTransport
|
||||
}
|
||||
if spec2.JLinkDevice != "" {
|
||||
spec.JLinkDevice = spec2.JLinkDevice
|
||||
}
|
||||
if spec2.CodeModel != "" {
|
||||
spec.CodeModel = spec2.CodeModel
|
||||
}
|
||||
// overrideProperties overrides all properties that are set in child into itself using reflection.
|
||||
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
||||
specType := reflect.TypeOf(spec).Elem()
|
||||
specValue := reflect.ValueOf(spec).Elem()
|
||||
childValue := reflect.ValueOf(child).Elem()
|
||||
|
||||
if spec2.RelocationModel != "" {
|
||||
spec.RelocationModel = spec2.RelocationModel
|
||||
for i := 0; i < specType.NumField(); i++ {
|
||||
field := specType.Field(i)
|
||||
src := childValue.Field(i)
|
||||
dst := specValue.Field(i)
|
||||
|
||||
switch kind := field.Type.Kind(); kind {
|
||||
case reflect.String: // for strings, just copy the field of child to spec if not empty
|
||||
if src.Len() > 0 {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
|
||||
if src.Uint() != 0 {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Ptr: // for pointers, copy if not nil
|
||||
if !src.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Slice: // for slices...
|
||||
if src.Len() > 0 { // ... if not empty ...
|
||||
switch tag := field.Tag.Get("override"); tag {
|
||||
case "copy":
|
||||
// copy the field of child to spec
|
||||
dst.Set(src)
|
||||
case "append", "":
|
||||
// or append the field of child to spec
|
||||
dst.Set(reflect.AppendSlice(src, dst))
|
||||
default:
|
||||
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unknown field type : " + kind.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,11 +150,11 @@ func (spec *TargetSpec) resolveInherits() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newSpec.copyProperties(subtarget)
|
||||
newSpec.overrideProperties(subtarget)
|
||||
}
|
||||
|
||||
// When all properties are loaded, make sure they are properly inherited.
|
||||
newSpec.copyProperties(spec)
|
||||
newSpec.overrideProperties(spec)
|
||||
*spec = *newSpec
|
||||
|
||||
return nil
|
||||
@@ -220,6 +172,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
"386": "i386",
|
||||
"amd64": "x86_64",
|
||||
"arm64": "aarch64",
|
||||
"arm": "thumbv7",
|
||||
}[goarch]
|
||||
if llvmarch == "" {
|
||||
llvmarch = goarch
|
||||
@@ -268,6 +221,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
}
|
||||
goarch := map[string]string{ // map from LLVM arch to Go arch
|
||||
"i386": "386",
|
||||
"i686": "386",
|
||||
"x86_64": "amd64",
|
||||
"aarch64": "arm64",
|
||||
"armv7": "arm",
|
||||
@@ -279,44 +233,71 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
|
||||
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
|
||||
|
||||
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
if goos == "windows" {
|
||||
return nil, WindowsBuildNotSupportedErr
|
||||
}
|
||||
// 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",
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: "gdb",
|
||||
PortReset: "false",
|
||||
FlashMethod: "native",
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Scheduler: "tasks",
|
||||
Linker: "cc",
|
||||
DefaultStackSize: 1024 * 64, // 64kB
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: []string{"gdb"},
|
||||
PortReset: "false",
|
||||
}
|
||||
if goarch == "386" {
|
||||
spec.CPU = "pentium4"
|
||||
}
|
||||
if goos == "darwin" {
|
||||
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
|
||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||
} else {
|
||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
||||
}
|
||||
if goarch != "wasm" {
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+".S")
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
spec.GDB = []string{"gdb-multiarch"}
|
||||
if goarch == "arm" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
||||
spec.Linker = "arm-linux-gnueabihf-gcc"
|
||||
spec.GDB = "arm-linux-gnueabihf-gdb"
|
||||
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
||||
}
|
||||
if goarch == "arm64" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
||||
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||
spec.GDB = "aarch64-linux-gnu-gdb"
|
||||
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||
}
|
||||
if goarch == "386" {
|
||||
spec.CFlags = []string{"-m32"}
|
||||
spec.LDFlags = []string{"-m32"}
|
||||
if goarch == "386" && runtime.GOARCH == "amd64" {
|
||||
spec.CFlags = append(spec.CFlags, "-m32")
|
||||
spec.LDFlags = append(spec.LDFlags, "-m32")
|
||||
}
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// LookupGDB looks up a gdb executable.
|
||||
func (spec *TargetSpec) LookupGDB() (string, error) {
|
||||
if len(spec.GDB) == 0 {
|
||||
return "", errors.New("gdb not configured in the target specification")
|
||||
}
|
||||
for _, d := range spec.GDB {
|
||||
_, err := exec.LookPath(d)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package compileopts
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadTarget(t *testing.T) {
|
||||
_, err := LoadTarget("arduino")
|
||||
@@ -17,3 +20,67 @@ func TestLoadTarget(t *testing.T) {
|
||||
t.Error("LoadTarget failed for wrong reason:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideProperties(t *testing.T) {
|
||||
baseAutoStackSize := true
|
||||
base := &TargetSpec{
|
||||
GOOS: "baseGoos",
|
||||
CPU: "baseCpu",
|
||||
Features: []string{"bf1", "bf2"},
|
||||
BuildTags: []string{"bt1", "bt2"},
|
||||
Emulator: []string{"be1", "be2"},
|
||||
DefaultStackSize: 42,
|
||||
AutoStackSize: &baseAutoStackSize,
|
||||
}
|
||||
childAutoStackSize := false
|
||||
child := &TargetSpec{
|
||||
GOOS: "",
|
||||
CPU: "chlidCpu",
|
||||
Features: []string{"cf1", "cf2"},
|
||||
Emulator: []string{"ce1", "ce2"},
|
||||
AutoStackSize: &childAutoStackSize,
|
||||
DefaultStackSize: 64,
|
||||
}
|
||||
|
||||
base.overrideProperties(child)
|
||||
|
||||
if base.GOOS != "baseGoos" {
|
||||
t.Errorf("Overriding failed : got %v", base.GOOS)
|
||||
}
|
||||
if base.CPU != "chlidCpu" {
|
||||
t.Errorf("Overriding failed : got %v", base.CPU)
|
||||
}
|
||||
if !reflect.DeepEqual(base.Features, []string{"cf1", "cf2", "bf1", "bf2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.Features)
|
||||
}
|
||||
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
||||
}
|
||||
if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.Emulator)
|
||||
}
|
||||
if *base.AutoStackSize != false {
|
||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
||||
}
|
||||
if base.DefaultStackSize != 64 {
|
||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
||||
}
|
||||
|
||||
baseAutoStackSize = true
|
||||
base = &TargetSpec{
|
||||
AutoStackSize: &baseAutoStackSize,
|
||||
DefaultStackSize: 42,
|
||||
}
|
||||
child = &TargetSpec{
|
||||
AutoStackSize: nil,
|
||||
DefaultStackSize: 0,
|
||||
}
|
||||
base.overrideProperties(child)
|
||||
if *base.AutoStackSize != true {
|
||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
||||
}
|
||||
if base.DefaultStackSize != 42 {
|
||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package compiler
|
||||
|
||||
// This file defines alias functions for functions that are normally defined in
|
||||
// Go assembly.
|
||||
//
|
||||
// The Go toolchain defines many performance critical functions in assembly
|
||||
// instead of plain Go. This is a problem for TinyGo as it currently (as of
|
||||
// august 2021) is not able to compile these assembly files and even if it
|
||||
// could, it would not be able to make use of them for many targets that are
|
||||
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
|
||||
// functions are aliased to their generic Go implementation.
|
||||
// This results in slower than possible implementations, but at least they are
|
||||
// usable.
|
||||
|
||||
import "tinygo.org/x/go-llvm"
|
||||
|
||||
var stdlibAliases = map[string]string{
|
||||
// crypto packages
|
||||
"crypto/md5.block": "crypto/md5.blockGeneric",
|
||||
"crypto/sha1.block": "crypto/sha1.blockGeneric",
|
||||
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
|
||||
"crypto/sha256.block": "crypto/sha256.blockGeneric",
|
||||
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
|
||||
|
||||
// math package
|
||||
"math.Asin": "math.asin",
|
||||
"math.Asinh": "math.asinh",
|
||||
"math.Acos": "math.acos",
|
||||
"math.Acosh": "math.acosh",
|
||||
"math.Atan": "math.atan",
|
||||
"math.Atanh": "math.atanh",
|
||||
"math.Atan2": "math.atan2",
|
||||
"math.Cbrt": "math.cbrt",
|
||||
"math.Ceil": "math.ceil",
|
||||
"math.archCeil": "math.ceil",
|
||||
"math.Cos": "math.cos",
|
||||
"math.Cosh": "math.cosh",
|
||||
"math.Erf": "math.erf",
|
||||
"math.Erfc": "math.erfc",
|
||||
"math.Exp": "math.exp",
|
||||
"math.archExp": "math.exp",
|
||||
"math.Expm1": "math.expm1",
|
||||
"math.Exp2": "math.exp2",
|
||||
"math.archExp2": "math.exp2",
|
||||
"math.Floor": "math.floor",
|
||||
"math.archFloor": "math.floor",
|
||||
"math.Frexp": "math.frexp",
|
||||
"math.Hypot": "math.hypot",
|
||||
"math.archHypot": "math.hypot",
|
||||
"math.Ldexp": "math.ldexp",
|
||||
"math.Log": "math.log",
|
||||
"math.archLog": "math.log",
|
||||
"math.Log1p": "math.log1p",
|
||||
"math.Log10": "math.log10",
|
||||
"math.Log2": "math.log2",
|
||||
"math.Max": "math.max",
|
||||
"math.archMax": "math.max",
|
||||
"math.Min": "math.min",
|
||||
"math.archMin": "math.min",
|
||||
"math.Mod": "math.mod",
|
||||
"math.Modf": "math.modf",
|
||||
"math.archModf": "math.modf",
|
||||
"math.Pow": "math.pow",
|
||||
"math.Remainder": "math.remainder",
|
||||
"math.Sin": "math.sin",
|
||||
"math.Sinh": "math.sinh",
|
||||
"math.Sqrt": "math.sqrt",
|
||||
"math.archSqrt": "math.sqrt",
|
||||
"math.Tan": "math.tan",
|
||||
"math.Tanh": "math.tanh",
|
||||
"math.Trunc": "math.trunc",
|
||||
"math.archTrunc": "math.trunc",
|
||||
}
|
||||
|
||||
// createAlias implements the function (in the builder) as a call to the alias
|
||||
// function.
|
||||
func (b *builder) createAlias(alias llvm.Value) {
|
||||
if b.Debug {
|
||||
if b.fn.Syntax() != nil {
|
||||
// Create debug info file if present.
|
||||
b.difunc = b.attachDebugInfo(b.fn)
|
||||
}
|
||||
pos := b.program.Fset.Position(b.fn.Pos())
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
||||
}
|
||||
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
|
||||
b.SetInsertPointAtEnd(entryBlock)
|
||||
if b.llvmFn.Type() != alias.Type() {
|
||||
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
|
||||
b.CreateUnreachable()
|
||||
return
|
||||
}
|
||||
result := b.CreateCall(alias, b.llvmFn.Params(), "")
|
||||
if result.Type().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateRetVoid()
|
||||
} else {
|
||||
b.CreateRet(result)
|
||||
}
|
||||
}
|
||||
+62
-6
@@ -16,7 +16,7 @@ import (
|
||||
// slice. This is required by the Go language spec: an index out of bounds must
|
||||
// cause a panic.
|
||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
||||
if b.fn.IsNoBounds() {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
@@ -48,7 +48,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType
|
||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||
// logic is the same in both cases.
|
||||
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||
if b.fn.IsNoBounds() {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
@@ -101,10 +101,59 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
||||
}
|
||||
|
||||
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
||||
// conversions. This conversion was added in Go 1.17. For details, see:
|
||||
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
|
||||
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
|
||||
// From the spec:
|
||||
// > If the length of the slice is less than the length of the array, a
|
||||
// > run-time panic occurs.
|
||||
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
||||
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
||||
}
|
||||
|
||||
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
|
||||
// function must panic if the ptr/len parameters are invalid.
|
||||
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
|
||||
// From the documentation of unsafe.Slice:
|
||||
// > At run time, if len is negative, or if ptr is nil and len is not
|
||||
// > zero, a run-time panic occurs.
|
||||
// However, in practice, it is also necessary to check that the length is
|
||||
// not too big that a GEP wouldn't be possible without wrapping the pointer.
|
||||
// These two checks (non-negative and not too big) can be merged into one
|
||||
// using an unsiged greater than.
|
||||
|
||||
// Make sure the len value is at least as big as a uintptr.
|
||||
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
||||
if lenType.Info()&types.IsUnsigned != 0 {
|
||||
len = b.CreateZExt(len, b.uintptrType, "")
|
||||
} else {
|
||||
len = b.CreateSExt(len, b.uintptrType, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the maximum slice size, and therefore the maximum value of the
|
||||
// len parameter.
|
||||
maxSize := b.maxSliceSize(ptr.Type().ElementType())
|
||||
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
|
||||
|
||||
// Do the check. By using unsigned greater than for the length check, signed
|
||||
// negative values are also checked (which are very large numbers when
|
||||
// interpreted as signed values).
|
||||
zero := llvm.ConstInt(len.Type(), 0, false)
|
||||
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
|
||||
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
|
||||
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
||||
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
|
||||
}
|
||||
|
||||
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||
// check that the value is not too big for runtime.chanMake.
|
||||
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
||||
if b.fn.IsNoBounds() {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
@@ -159,6 +208,13 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
|
||||
}
|
||||
|
||||
switch inst := inst.(type) {
|
||||
case *ssa.Alloc:
|
||||
// An alloc is never nil.
|
||||
return
|
||||
case *ssa.FreeVar:
|
||||
// A free variable is allocated in a parent function and is thus never
|
||||
// nil.
|
||||
return
|
||||
case *ssa.IndexAddr:
|
||||
// This pointer is the result of an index operation into a slice or
|
||||
// array. Such slices/arrays are already bounds checked so the pointer
|
||||
@@ -189,7 +245,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
|
||||
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
||||
// This function assumes that the shift value is signed.
|
||||
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
||||
if b.fn.IsNoBounds() {
|
||||
if b.info.nobounds {
|
||||
// Function disabled bounds checking - skip shift check.
|
||||
return
|
||||
}
|
||||
@@ -212,8 +268,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
|
||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
|
||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now branch to the out-of-bounds or the regular block.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -35,6 +37,31 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
ptr := b.getValue(call.Args[0])
|
||||
old := b.getValue(call.Args[1])
|
||||
newVal := b.getValue(call.Args[2])
|
||||
if strings.HasSuffix(name, "64") {
|
||||
arch := strings.Split(b.Triple, "-")[0]
|
||||
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
|
||||
// Work around a bug in LLVM, at least LLVM 11:
|
||||
// https://reviews.llvm.org/D95891
|
||||
// Check for armv6m, armv7, armv7em, and perhaps others.
|
||||
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
|
||||
if compareAndSwap.IsNil() {
|
||||
// Declare the function if it isn't already declared.
|
||||
i64Type := b.ctx.Int64Type()
|
||||
fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false)
|
||||
compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType)
|
||||
}
|
||||
actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "")
|
||||
// The __sync_val_compare_and_swap_8 function returns the old
|
||||
// value. However, we shouldn't return the old value, we should
|
||||
// return whether the compare/exchange was successful. This is
|
||||
// easily done by comparing the returned (actual) old value with
|
||||
// the expected old value passed to
|
||||
// __sync_val_compare_and_swap_8.
|
||||
swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "")
|
||||
return swapped, true
|
||||
}
|
||||
}
|
||||
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
|
||||
swapped := b.CreateExtractValue(tuple, 1, "")
|
||||
return swapped, true
|
||||
|
||||
+28
-15
@@ -4,6 +4,7 @@ import (
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -34,14 +35,14 @@ const (
|
||||
|
||||
// createCall creates a new call to runtime.<fnName> with the given arguments.
|
||||
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||
fullName := "runtime." + fnName
|
||||
fn := b.mod.NamedFunction(fullName)
|
||||
if fn.IsNil() {
|
||||
panic("trying to call non-existent function: " + fullName)
|
||||
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
|
||||
llvmFn := b.getFunction(fn)
|
||||
if llvmFn.IsNil() {
|
||||
panic("trying to call non-existent function: " + fn.RelString(nil))
|
||||
}
|
||||
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
||||
return b.createCall(fn, args, name)
|
||||
return b.createCall(llvmFn, args, name)
|
||||
}
|
||||
|
||||
// createCall creates a call to the given function with the arguments possibly
|
||||
@@ -57,10 +58,10 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
|
||||
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// parameter list.
|
||||
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(t, name, goType)
|
||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
return fieldInfos
|
||||
} else {
|
||||
@@ -104,7 +105,7 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(v.Type(), "", nil)
|
||||
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
fields := b.flattenAggregate(v)
|
||||
if len(fields) != len(fieldInfos) {
|
||||
@@ -123,12 +124,15 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
|
||||
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
||||
// with the passed in type if this is not possible.
|
||||
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
typeFlags := getTypeFlags(goType)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
|
||||
var paramInfos []paramInfo
|
||||
for i, subfield := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(subfield) == 0 {
|
||||
continue
|
||||
}
|
||||
suffix := strconv.Itoa(i)
|
||||
if goType != nil {
|
||||
// Try to come up with a good suffix for this struct field,
|
||||
@@ -151,7 +155,7 @@ func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramIn
|
||||
suffix = []string{"context", "funcptr"}[i]
|
||||
}
|
||||
}
|
||||
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
for i := range subInfos {
|
||||
subInfos[i].flags |= typeFlags
|
||||
}
|
||||
@@ -217,8 +221,11 @@ func extractSubfield(t types.Type, field int) types.Type {
|
||||
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
var fields []uint64
|
||||
for fieldIndex, field := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||
for i := range suboffsets {
|
||||
@@ -237,8 +244,11 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
||||
for i := range v.Type().StructElementTypes() {
|
||||
var fields []llvm.Value
|
||||
for i, field := range v.Type().StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
subfield := b.CreateExtractValue(v, i, "")
|
||||
subfields := b.flattenAggregate(subfield)
|
||||
fields = append(fields, subfields...)
|
||||
@@ -265,10 +275,13 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
|
||||
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
flattened := flattenAggregateType(t, "", nil)
|
||||
flattened := b.flattenAggregateType(t, "", nil)
|
||||
if len(flattened) <= maxFieldsPerParam {
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(subtyp) == 0 {
|
||||
continue
|
||||
}
|
||||
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
value = b.CreateInsertValue(value, structField, i, "")
|
||||
|
||||
+29
-11
@@ -32,8 +32,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
|
||||
// store value-to-send
|
||||
valueType := b.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
}
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
@@ -46,7 +53,9 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
if !isZeroSize {
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
}
|
||||
|
||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
@@ -56,7 +65,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
ch := b.getValue(unop.X)
|
||||
|
||||
// Allocate memory to receive into.
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
}
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
@@ -64,9 +80,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// Do the receive.
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
||||
received := b.CreateLoad(valueAlloca, "chan.received")
|
||||
var received llvm.Value
|
||||
if isZeroSize {
|
||||
received = llvm.ConstNull(valueType)
|
||||
} else {
|
||||
received = b.CreateLoad(valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||
@@ -79,8 +100,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
}
|
||||
|
||||
// createChanClose closes the given channel.
|
||||
func (b *builder) createChanClose(param ssa.Value) {
|
||||
ch := b.getValue(param)
|
||||
func (b *builder) createChanClose(ch llvm.Value) {
|
||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
@@ -117,7 +137,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
// determine the receive buffer size and alignment.
|
||||
recvbufSize := uint64(0)
|
||||
recvbufAlign := 0
|
||||
hasReceives := false
|
||||
var selectStates []llvm.Value
|
||||
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
@@ -134,7 +153,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
if align := b.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.
|
||||
@@ -151,7 +169,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
|
||||
// Create a receive buffer, where the received value will be stored.
|
||||
recvbuf := llvm.Undef(b.i8ptrType)
|
||||
if hasReceives {
|
||||
if recvbufSize != 0 {
|
||||
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||
|
||||
+653
-493
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,200 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Pass -update to go test to update the output of the test files.
|
||||
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
||||
|
||||
type testCase struct {
|
||||
file string
|
||||
target string
|
||||
}
|
||||
|
||||
// Basic tests for the compiler. Build some Go files and compare the output with
|
||||
// the expected LLVM IR for regression testing.
|
||||
func TestCompiler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Check LLVM version.
|
||||
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
|
||||
if err != nil {
|
||||
t.Fatal("could not parse LLVM version:", llvm.Version)
|
||||
}
|
||||
if llvmMajor < 11 {
|
||||
// It is likely this version needs to be bumped in the future.
|
||||
// The goal is to at least test the LLVM version that's used by default
|
||||
// in TinyGo and (if possible without too many workarounds) also some
|
||||
// previous versions.
|
||||
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{"basic.go", ""},
|
||||
{"pointer.go", ""},
|
||||
{"slice.go", ""},
|
||||
{"string.go", ""},
|
||||
{"float.go", ""},
|
||||
{"interface.go", ""},
|
||||
{"func.go", ""},
|
||||
{"pragma.go", ""},
|
||||
{"goroutine.go", "wasm"},
|
||||
{"goroutine.go", "cortex-m-qemu"},
|
||||
{"channel.go", ""},
|
||||
{"intrinsics.go", "cortex-m-qemu"},
|
||||
{"intrinsics.go", "wasm"},
|
||||
}
|
||||
|
||||
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read Go version:", err)
|
||||
}
|
||||
if minor >= 17 {
|
||||
tests = append(tests, testCase{"go1.17.go", ""})
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.file
|
||||
targetString := "wasm"
|
||||
if tc.target != "" {
|
||||
targetString = tc.target
|
||||
name = tc.file + "-" + tc.target
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
target, err := compileopts.LoadTarget(targetString)
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", tc.file, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Optimize IR a little.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
||||
defer funcPasses.Dispose()
|
||||
funcPasses.AddInstructionCombiningPass()
|
||||
funcPasses.InitializeFunc()
|
||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
||||
if tc.target != "" {
|
||||
outFilePrefix += "-" + tc.target
|
||||
}
|
||||
outPath := "./testdata/" + outFilePrefix + ".ll"
|
||||
|
||||
// Update test if needed. Do not check the result.
|
||||
if *flagUpdate {
|
||||
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
|
||||
if err != nil {
|
||||
t.Error("failed to write updated output file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
expected, err := ioutil.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
|
||||
if !fuzzyEqualIR(mod.String(), string(expected)) {
|
||||
t.Errorf("output does not match expected output:\n%s", mod.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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, line1 := range lines1 {
|
||||
line2 := lines2[i]
|
||||
if line1 != line2 {
|
||||
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.Split(line, ";")[0] // strip out comments/info
|
||||
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "source_filename = ") {
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return out
|
||||
}
|
||||
+40
-40
@@ -14,9 +14,9 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -26,9 +26,9 @@ import (
|
||||
// calls.
|
||||
func (b *builder) deferInitFunc() {
|
||||
// Some setup.
|
||||
b.deferFuncs = make(map[*ir.Function]int)
|
||||
b.deferFuncs = make(map[*ssa.Function]int)
|
||||
b.deferInvokeFuncs = make(map[string]int)
|
||||
b.deferClosureFuncs = make(map[*ir.Function]int)
|
||||
b.deferClosureFuncs = make(map[*ssa.Function]int)
|
||||
b.deferExprFuncs = make(map[ssa.Value]int)
|
||||
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
||||
|
||||
@@ -107,13 +107,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
|
||||
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
||||
// Regular function call.
|
||||
fn := b.ir.GetFunction(callee)
|
||||
|
||||
if _, ok := b.deferFuncs[fn]; !ok {
|
||||
b.deferFuncs[fn] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, fn)
|
||||
if _, ok := b.deferFuncs[callee]; !ok {
|
||||
b.deferFuncs[callee] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, callee)
|
||||
}
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
@@ -135,7 +133,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
context := b.CreateExtractValue(closure, 0, "")
|
||||
|
||||
// Get the callback number.
|
||||
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
|
||||
fn := makeClosure.Fn.(*ssa.Function)
|
||||
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
||||
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
||||
@@ -155,19 +153,19 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
valueTypes = append(valueTypes, context.Type())
|
||||
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
var funcName string
|
||||
switch builtin.Name() {
|
||||
case "close":
|
||||
funcName = "chanClose"
|
||||
default:
|
||||
b.addError(instr.Pos(), "todo: Implement defer for "+builtin.Name())
|
||||
return
|
||||
var argTypes []types.Type
|
||||
var argValues []llvm.Value
|
||||
for _, arg := range instr.Call.Args {
|
||||
argTypes = append(argTypes, arg.Type())
|
||||
argValues = append(argValues, b.getValue(arg))
|
||||
}
|
||||
|
||||
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
|
||||
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
|
||||
funcName,
|
||||
len(b.allDeferFuncs),
|
||||
callName: builtin.Name(),
|
||||
pos: builtin.Pos(),
|
||||
argTypes: argTypes,
|
||||
callback: len(b.allDeferFuncs),
|
||||
}
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
|
||||
}
|
||||
@@ -176,10 +174,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := b.getValue(param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
for _, param := range argValues {
|
||||
values = append(values, param)
|
||||
valueTypes = append(valueTypes, param.Type())
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -223,7 +220,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||
}
|
||||
if b.NeedsStackObjects() {
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
}
|
||||
b.CreateStore(deferFrame, alloca)
|
||||
@@ -251,10 +248,10 @@ func (b *builder) createRunDefers() {
|
||||
// }
|
||||
|
||||
// Create loop.
|
||||
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
|
||||
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
|
||||
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
|
||||
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
|
||||
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
|
||||
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
|
||||
b.CreateBr(loophead)
|
||||
|
||||
// Create loop head:
|
||||
@@ -286,7 +283,7 @@ func (b *builder) createRunDefers() {
|
||||
// Create switch case, for example:
|
||||
// case 0:
|
||||
// // run first deferred call
|
||||
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
|
||||
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
|
||||
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
||||
b.SetInsertPointAtEnd(block)
|
||||
switch callback := callback.(type) {
|
||||
@@ -350,12 +347,12 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
b.createCall(fnPtr, forwardParams, "")
|
||||
|
||||
case *ir.Function:
|
||||
case *ssa.Function:
|
||||
// Direct call.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
for _, param := range getParams(callback.Signature) {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||
}
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
@@ -364,7 +361,7 @@ func (b *builder) createRunDefers() {
|
||||
// Extract the params from the struct.
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := range callback.Params {
|
||||
for i := range getParams(callback.Signature) {
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
@@ -372,7 +369,7 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
|
||||
// These parameters should not be supplied when calling into an external C/ASM function.
|
||||
if !callback.IsExported() {
|
||||
if !b.getFunctionInfo(callback).exported {
|
||||
// Add the context parameter. We know it is ignored by the receiving
|
||||
// function, but we have to pass one anyway.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
@@ -382,11 +379,11 @@ func (b *builder) createRunDefers() {
|
||||
}
|
||||
|
||||
// Call real function.
|
||||
b.createCall(callback.LLVMFn, forwardParams, "")
|
||||
b.createCall(b.getFunction(callback), forwardParams, "")
|
||||
|
||||
case *ssa.MakeClosure:
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||
fn := callback.Fn.(*ssa.Function)
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
@@ -409,7 +406,7 @@ func (b *builder) createRunDefers() {
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
// Call deferred function.
|
||||
b.createCall(fn.LLVMFn, forwardParams, "")
|
||||
b.createCall(b.getFunction(fn), forwardParams, "")
|
||||
case *ssa.Builtin:
|
||||
db := b.deferBuiltinFuncs[callback]
|
||||
|
||||
@@ -426,15 +423,18 @@ func (b *builder) createRunDefers() {
|
||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
// Extract the params from the struct.
|
||||
var forwardParams []llvm.Value
|
||||
var argValues []llvm.Value
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
argValues = append(argValues, forwardParam)
|
||||
}
|
||||
|
||||
b.createRuntimeCall(db.funcName, forwardParams, "")
|
||||
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
||||
if err != nil {
|
||||
b.diagnostics = append(b.diagnostics, err)
|
||||
}
|
||||
default:
|
||||
panic("unknown deferred function type")
|
||||
}
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
// makeError makes it easy to create an error from a token.Pos with a message.
|
||||
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
|
||||
return types.Error{
|
||||
Fset: c.ir.Program.Fset,
|
||||
Fset: c.program.Fset,
|
||||
Pos: pos,
|
||||
Msg: msg,
|
||||
}
|
||||
|
||||
+29
-18
@@ -6,7 +6,6 @@ package compiler
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -21,24 +20,23 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
|
||||
// context.
|
||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.FuncImplementation() {
|
||||
case compileopts.FuncValueDoubleword:
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case compileopts.FuncValueSwitch:
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
case "switch":
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
c.getFuncSignatureID(sig),
|
||||
})
|
||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
}
|
||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||
default:
|
||||
@@ -51,6 +49,19 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// getFuncSignatureID returns a new external global for a given signature. This
|
||||
// global reference is not real, it is only used during func lowering to assign
|
||||
// signature types to functions and will then be removed.
|
||||
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
|
||||
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
|
||||
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||
if sigGlobal.IsNil() {
|
||||
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||
sigGlobal.SetGlobalConstant(true)
|
||||
}
|
||||
return sigGlobal
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
@@ -67,12 +78,12 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
// value. This may be an expensive operation.
|
||||
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = b.CreateExtractValue(funcValue, 0, "")
|
||||
switch b.FuncImplementation() {
|
||||
case compileopts.FuncValueDoubleword:
|
||||
switch b.FuncImplementation {
|
||||
case "doubleword":
|
||||
funcPtr = b.CreateExtractValue(funcValue, 1, "")
|
||||
case compileopts.FuncValueSwitch:
|
||||
case "switch":
|
||||
llvmSig := b.getRawFuncType(sig)
|
||||
sigGlobal := b.getTypeCode(sig)
|
||||
sigGlobal := b.getFuncSignatureID(sig)
|
||||
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
@@ -83,11 +94,11 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
||||
switch c.FuncImplementation() {
|
||||
case compileopts.FuncValueDoubleword:
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case compileopts.FuncValueSwitch:
|
||||
case "switch":
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
@@ -125,13 +136,13 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
for _, info := range expandFormalParamType(recv, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(recv, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
for _, info := range expandFormalParamType(subType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
@@ -149,7 +160,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
if len(expr.Bindings) == 0 {
|
||||
panic("unexpected: MakeClosure without bound variables")
|
||||
}
|
||||
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||
f := expr.Fn.(*ssa.Function)
|
||||
|
||||
// Collect all bound variables.
|
||||
boundVars := make([]llvm.Value, len(expr.Bindings))
|
||||
@@ -164,5 +175,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
context := b.emitPointerPack(boundVars)
|
||||
|
||||
// Create the closure.
|
||||
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
|
||||
}
|
||||
|
||||
+131
-26
@@ -5,34 +5,124 @@ package compiler
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createGoInstruction 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 (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||
// createGo emits code to start a new goroutine.
|
||||
func (b *builder) createGo(instr *ssa.Go) {
|
||||
// Get all function parameters to pass to the goroutine.
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Call.Args {
|
||||
params = append(params, b.getValue(param))
|
||||
}
|
||||
|
||||
var prefix string
|
||||
var funcPtr llvm.Value
|
||||
hasContext := false
|
||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
||||
// Static callee is known. This makes it easier to start a new
|
||||
// goroutine.
|
||||
var context llvm.Value
|
||||
switch value := instr.Call.Value.(type) {
|
||||
case *ssa.Function:
|
||||
// Goroutine call is regular function call. No context is necessary.
|
||||
if b.Scheduler == "coroutines" {
|
||||
// The context parameter is assumed to be always present in the
|
||||
// coroutines scheduler.
|
||||
context = llvm.Undef(b.i8ptrType)
|
||||
}
|
||||
case *ssa.MakeClosure:
|
||||
// A goroutine call on a func value, but the callee is trivial to find. For
|
||||
// example: immediately applied functions.
|
||||
funcValue := b.getValue(value)
|
||||
context = b.extractFuncContext(funcValue)
|
||||
default:
|
||||
panic("StaticCallee returned an unexpected value")
|
||||
}
|
||||
if !context.IsNil() {
|
||||
params = append(params, context) // context parameter
|
||||
hasContext = true
|
||||
}
|
||||
funcPtr = b.getFunction(callee)
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
// We cheat. None of the builtins do any long or blocking operation, so
|
||||
// we might as well run these builtins right away without the program
|
||||
// noticing the difference.
|
||||
// Possible exceptions:
|
||||
// - copy: this is a possibly long operation, but not a blocking
|
||||
// operation. Semantically it makes no difference to run it right
|
||||
// away (not in a goroutine). However, in practice it makes no sense
|
||||
// to run copy in a goroutine as there is no way to (safely) know
|
||||
// when it is finished.
|
||||
// - panic: the error message would appear in the parent goroutine.
|
||||
// But because `go panic("err")` would halt the program anyway
|
||||
// (there is no recover), panicking right away would give the same
|
||||
// behavior as creating a goroutine, switching the scheduler to that
|
||||
// goroutine, and panicking there. So this optimization seems
|
||||
// correct.
|
||||
// - recover: because it runs in a new goroutine, it is never a
|
||||
// deferred function. Thus this is a no-op.
|
||||
if builtin.Name() == "recover" {
|
||||
// This is a no-op, even in a deferred function:
|
||||
// go recover()
|
||||
return
|
||||
}
|
||||
var argTypes []types.Type
|
||||
var argValues []llvm.Value
|
||||
for _, arg := range instr.Call.Args {
|
||||
argTypes = append(argTypes, arg.Type())
|
||||
argValues = append(argValues, b.getValue(arg))
|
||||
}
|
||||
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
|
||||
return
|
||||
} else if !instr.Call.IsInvoke() {
|
||||
// This is a function pointer.
|
||||
// At the moment, two extra params are passed to the newly started
|
||||
// goroutine:
|
||||
// * The function context, for closures.
|
||||
// * The function pointer (for tasks).
|
||||
var context llvm.Value
|
||||
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
|
||||
params = append(params, context) // context parameter
|
||||
hasContext = true
|
||||
switch b.Scheduler {
|
||||
case "none", "coroutines":
|
||||
// There are no additional parameters needed for the goroutine start operation.
|
||||
case "tasks":
|
||||
// Add the function pointer as a parameter to start the goroutine.
|
||||
params = append(params, funcPtr)
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
prefix = b.fn.RelString(nil)
|
||||
} else {
|
||||
b.addError(instr.Pos(), "todo: go on interface call")
|
||||
return
|
||||
}
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var callee, stackSize llvm.Value
|
||||
switch b.Scheduler() {
|
||||
switch b.Scheduler {
|
||||
case "none", "tasks":
|
||||
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
|
||||
if b.AutomaticStackSize() {
|
||||
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
// The stack size is not known until after linking. Call a dummy
|
||||
// function that will be replaced with a load from a special ELF
|
||||
// section that contains the stack size (and is modified after
|
||||
// linking).
|
||||
stackSize = b.createCall(b.mod.NamedFunction("internal/task.getGoroutineStackSize"), []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
|
||||
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
|
||||
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
|
||||
} else {
|
||||
// The stack size is fixed at compile time. By emitting it here as a
|
||||
// constant, it can be optimized.
|
||||
stackSize = llvm.ConstInt(b.uintptrType, b.Target.DefaultStackSize, false)
|
||||
if b.Scheduler == "tasks" && b.DefaultStackSize == 0 {
|
||||
b.addError(instr.Pos(), "default stack size for goroutines is not set")
|
||||
}
|
||||
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
}
|
||||
case "coroutines":
|
||||
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
||||
@@ -42,8 +132,8 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
||||
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
|
||||
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
||||
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
||||
}
|
||||
|
||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
||||
@@ -64,7 +154,12 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
|
||||
// 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 *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||
//
|
||||
// The hasContext parameter indicates whether the context parameter (the second
|
||||
// to last parameter of the function) is used for this wrapper. If hasContext is
|
||||
// false, the parameter bundle is assumed to have no context parameter and undef
|
||||
// is passed instead.
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
builder := c.ctx.NewBuilder()
|
||||
@@ -81,14 +176,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
// 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.InternalLinkage)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(pos)
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(pos)
|
||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Parameters: nil, // do not show parameters in debugger
|
||||
@@ -111,8 +206,15 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
||||
params = append(params, llvm.Undef(c.i8ptrType))
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
|
||||
}
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
|
||||
|
||||
// Create the call.
|
||||
builder.CreateCall(fn, params, "")
|
||||
@@ -138,14 +240,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(pos)
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(pos)
|
||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Parameters: nil, // do not show parameters in debugger
|
||||
@@ -174,8 +276,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
// 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.
|
||||
// The last parameter in the packed object has somewhat of a dual role.
|
||||
// Inside the parameter bundle it's the function pointer, stored right
|
||||
// after the context pointer. But in the IR call instruction, it's the
|
||||
// parentHandle function that's always undef outside of the coroutines
|
||||
// scheduler. Thus, make the parameter undef here.
|
||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||
|
||||
// Create the call.
|
||||
|
||||
@@ -163,6 +163,44 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||
// be one of:
|
||||
//
|
||||
// func SVCall0(num uintptr) uintptr
|
||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
//
|
||||
// The num parameter must be a constant. All other parameters may be any scalar
|
||||
// value supported by LLVM inline assembly.
|
||||
// Same as emitSVCall but for AArch64
|
||||
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={x0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := b.getValue(arg)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
||||
// clobbered. x0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits CSR instructions. It can be one of:
|
||||
//
|
||||
// func (csr CSR) Get() uintptr
|
||||
|
||||
+123
-54
@@ -11,7 +11,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -25,16 +24,7 @@ import (
|
||||
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := b.emitPointerPack([]llvm.Value{val})
|
||||
itfTypeCodeGlobal := b.getTypeCode(typ)
|
||||
itfMethodSetGlobal := b.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
|
||||
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
|
||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
||||
@@ -55,6 +45,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
// reflect lowering simpler.
|
||||
var references llvm.Value
|
||||
var length int64
|
||||
var methodSet llvm.Value
|
||||
var ptrTo llvm.Value
|
||||
switch typ := typ.(type) {
|
||||
case *types.Named:
|
||||
references = c.getTypeCode(typ.Underlying())
|
||||
@@ -71,18 +63,32 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||
structGlobal := c.makeStructTypeFields(typ)
|
||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
||||
case *types.Interface:
|
||||
methodSetGlobal := c.getInterfaceMethodSet(typ)
|
||||
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
|
||||
}
|
||||
if _, ok := typ.Underlying().(*types.Interface); !ok {
|
||||
methodSet = c.getTypeMethodSet(typ)
|
||||
}
|
||||
if _, ok := typ.Underlying().(*types.Pointer); !ok {
|
||||
ptrTo = c.getTypeCode(types.NewPointer(typ))
|
||||
}
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
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)
|
||||
}
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
if !methodSet.IsNil() {
|
||||
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
|
||||
}
|
||||
if !ptrTo.IsNil() {
|
||||
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
@@ -104,8 +110,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
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),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
||||
if typ.Tag(i) != "" {
|
||||
@@ -113,8 +119,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
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),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
||||
}
|
||||
@@ -187,7 +193,7 @@ func getTypeCodeName(t types.Type) string {
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
@@ -236,7 +242,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
ms := c.program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
||||
@@ -247,15 +253,16 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
|
||||
if f.LLVMFn.IsNil() {
|
||||
fn := c.program.MethodValue(method)
|
||||
llvmFn := c.getFunction(fn)
|
||||
if llvmFn.IsNil() {
|
||||
// compiler error, so panic
|
||||
panic("cannot find function: " + f.LinkName())
|
||||
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
|
||||
}
|
||||
fn := c.getInterfaceInvokeWrapper(f)
|
||||
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
|
||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||
signatureGlobal,
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
llvm.ConstPtrToInt(wrapper, c.uintptrType),
|
||||
})
|
||||
methods[i] = methodInfo
|
||||
}
|
||||
@@ -264,7 +271,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
@@ -295,7 +302,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
||||
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
@@ -303,11 +310,22 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
||||
// external *i8 indicating the indicating the signature of this method. It is
|
||||
// used during the interface lowering pass.
|
||||
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := ir.MethodSignature(method)
|
||||
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||
signature := methodSignature(method)
|
||||
var globalName string
|
||||
if token.IsExported(method.Name()) {
|
||||
globalName = "reflect/methods." + signature
|
||||
} else {
|
||||
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
|
||||
}
|
||||
signatureGlobal := c.mod.NamedGlobal(globalName)
|
||||
if signatureGlobal.IsNil() {
|
||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||
// TODO: put something useful in these globals, such as the method
|
||||
// signature. Useful to one day implement reflect.Value.Method(n).
|
||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
|
||||
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
signatureGlobal.SetGlobalConstant(true)
|
||||
signatureGlobal.SetAlignment(1)
|
||||
}
|
||||
return signatureGlobal
|
||||
}
|
||||
@@ -338,10 +356,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||
|
||||
} else {
|
||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
// Create a new typecode global.
|
||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
||||
}
|
||||
// Type assert on concrete type.
|
||||
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
||||
// const false in the interface lowering pass.
|
||||
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
|
||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
}
|
||||
|
||||
@@ -357,8 +381,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
// value.
|
||||
|
||||
prevBlock := b.GetInsertBlock()
|
||||
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
|
||||
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
|
||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
@@ -436,8 +460,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
|
||||
// value, dereferences or unpacks it if necessary, and calls the real method.
|
||||
// If the method to wrap has a pointer receiver, no wrapping is necessary and
|
||||
// the function is returned directly.
|
||||
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
wrapperName := f.LinkName() + "$invoke"
|
||||
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
|
||||
wrapperName := llvmFn.Name() + "$invoke"
|
||||
wrapper := c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
// Wrapper already created. Return it directly.
|
||||
@@ -445,9 +469,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
}
|
||||
|
||||
// Get the expanded receiver type.
|
||||
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
|
||||
var expandedReceiverType []llvm.Type
|
||||
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
|
||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||
}
|
||||
|
||||
@@ -457,19 +481,17 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
// Casting a function signature to a different signature and calling it
|
||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||
// interface) is hopefully a safe (defined) operation.
|
||||
return f.LLVMFn
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
fnType := f.LLVMFn.Type().ElementType()
|
||||
fnType := llvmFn.Type().ElementType()
|
||||
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")
|
||||
}
|
||||
wrapper.LastParam().SetName("parentHandle")
|
||||
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
|
||||
// Create a new builder just to create this wrapper.
|
||||
@@ -480,9 +502,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
defer b.Builder.Dispose()
|
||||
|
||||
// add debug info if needed
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(f.Pos())
|
||||
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(fn.Pos())
|
||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
@@ -492,13 +514,60 @@ func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
|
||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateCall(f.LLVMFn, params, "")
|
||||
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
b.CreateRetVoid()
|
||||
} else {
|
||||
ret := b.CreateCall(f.LLVMFn, params, "ret")
|
||||
ret := b.CreateCall(llvmFn, params, "ret")
|
||||
b.CreateRet(ret)
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// methodSignature creates a readable version of a method signature (including
|
||||
// the function name, excluding the receiver name). This string is used
|
||||
// internally to match interfaces and to call the correct method on an
|
||||
// interface. Examples:
|
||||
//
|
||||
// String() string
|
||||
// Read([]byte) (int, error)
|
||||
func methodSignature(method *types.Func) string {
|
||||
return method.Name() + signature(method.Type().(*types.Signature))
|
||||
}
|
||||
|
||||
// Make a readable version of a function (pointer) signature.
|
||||
// Examples:
|
||||
//
|
||||
// () string
|
||||
// (string, int) (int, error)
|
||||
func signature(sig *types.Signature) string {
|
||||
s := ""
|
||||
if sig.Params().Len() == 0 {
|
||||
s += "()"
|
||||
} else {
|
||||
s += "("
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += sig.Params().At(i).Type().String()
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
if sig.Results().Len() == 0 {
|
||||
// keep as-is
|
||||
} else if sig.Results().Len() == 1 {
|
||||
s += " " + sig.Results().At(0).Type().String()
|
||||
} else {
|
||||
s += " ("
|
||||
for i := 0; i < sig.Results().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += sig.Results().At(i).Type().String()
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -39,14 +39,11 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
|
||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||
// type are lowered in the interrupt lowering pass.
|
||||
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := b.getLLVMType(globalType)
|
||||
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
|
||||
}
|
||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
@@ -55,8 +52,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
global.SetInitializer(initializer)
|
||||
|
||||
// Add debug info to the interrupt global.
|
||||
if b.Debug() {
|
||||
pos := b.ir.Program.Fset.Position(instr.Pos())
|
||||
if b.Debug {
|
||||
pos := b.program.Fset.Position(instr.Pos())
|
||||
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
||||
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
||||
LinkageName: globalName,
|
||||
@@ -79,7 +76,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
// 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(b.Triple(), "avr") {
|
||||
if strings.HasPrefix(b.Triple, "avr") {
|
||||
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
||||
if useFn.IsNil() {
|
||||
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
||||
|
||||
@@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
var mathToLLVMMapping = map[string]string{
|
||||
"math.Sqrt": "llvm.sqrt.f64",
|
||||
"math.Floor": "llvm.floor.f64",
|
||||
"math.Ceil": "llvm.ceil.f64",
|
||||
"math.Trunc": "llvm.trunc.f64",
|
||||
}
|
||||
|
||||
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
|
||||
// possible. It returns the call result if possible, and a boolean whether it
|
||||
// succeeded. If it doesn't succeed, the architecture doesn't support the given
|
||||
// intrinsic.
|
||||
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
// Check whether this intrinsic is supported on the given GOARCH.
|
||||
// If it is unsupported, this can have two reasons:
|
||||
//
|
||||
// 1. LLVM can expand the intrinsic inline (using float instructions), but
|
||||
// the result doesn't pass the tests of the math package.
|
||||
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
|
||||
// libm function call, but the libm function call also fails the math
|
||||
// package tests.
|
||||
//
|
||||
// Whatever the implementation, it must pass the tests in the math package
|
||||
// so unfortunately only the below intrinsic+architecture combinations are
|
||||
// supported.
|
||||
name := call.StaticCallee().RelString(nil)
|
||||
switch name {
|
||||
case "math.Ceil", "math.Floor", "math.Trunc":
|
||||
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
case "math.Sqrt":
|
||||
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, false // only the above functions are supported.
|
||||
}
|
||||
|
||||
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
|
||||
if llvmFn.IsNil() {
|
||||
// The intrinsic doesn't exist yet, so declare it.
|
||||
// At the moment, all supported intrinsics have the form "double
|
||||
// foo(double %x)" so we can hardcode the signature here.
|
||||
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
|
||||
}
|
||||
// Create a call to the intrinsic.
|
||||
args := make([]llvm.Value, len(call.Args))
|
||||
for i, arg := range call.Args {
|
||||
args[i] = b.getValue(arg)
|
||||
}
|
||||
return b.CreateCall(llvmFn, args, ""), true
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
// 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 (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
|
||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
|
||||
}
|
||||
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
|
||||
@@ -5,7 +5,6 @@ package llvmutil
|
||||
// itself if possible and legal.
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -13,7 +12,7 @@ import (
|
||||
// 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.
|
||||
// If the values are all constants, they are be stored in a constant global and deduplicated.
|
||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
|
||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
@@ -26,7 +25,6 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
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)
|
||||
@@ -39,9 +37,39 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
// 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, "")
|
||||
// it in a *i8 alloca first and load the *i8 value from there. This is
|
||||
// effectively a bitcast.
|
||||
packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "")
|
||||
|
||||
if size < targetData.TypeAllocSize(i8ptrType) {
|
||||
// The alloca is bigger than the value that will be stored in it.
|
||||
// To avoid having some bits undefined, zero the alloca first.
|
||||
// Hopefully this will get optimized away.
|
||||
builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc)
|
||||
}
|
||||
|
||||
// Store all values in the alloca.
|
||||
packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAllocCast, indices, "")
|
||||
builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
// Load value (the *i8) from the alloca.
|
||||
result := builder.CreateLoad(packedAlloc, "")
|
||||
|
||||
// End the lifetime of the alloca, to help the optimizer.
|
||||
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
||||
|
||||
return result
|
||||
} else {
|
||||
// Check if the values are all constants.
|
||||
constant := true
|
||||
@@ -67,12 +95,12 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
// 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{
|
||||
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
}, "")
|
||||
if config.NeedsStackObjects() {
|
||||
if needsStackObjects {
|
||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||
builder.CreateCall(trackPointer, []llvm.Value{
|
||||
packedHeapAlloc,
|
||||
@@ -80,28 +108,19 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
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)
|
||||
}
|
||||
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
|
||||
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.
|
||||
// Store all values in the 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)
|
||||
}
|
||||
|
||||
// Return the original heap allocation pointer, which already is an *i8.
|
||||
return packedHeapAlloc
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +150,9 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
return s.PtrSize
|
||||
case *types.Signature:
|
||||
// Func values in TinyGo are two words in size.
|
||||
return s.PtrSize * 2
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
|
||||
+358
-32
@@ -15,6 +15,314 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// functionInfo contains some information about a function or method. In
|
||||
// particular, it contains information obtained from pragmas.
|
||||
//
|
||||
// The linkName value contains a valid link name, even if //go:linkname is not
|
||||
// present.
|
||||
type functionInfo struct {
|
||||
module string // go:wasm-module
|
||||
importName string // go:linkname, go:export - The name the developer assigns
|
||||
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
}
|
||||
|
||||
type inlineType int
|
||||
|
||||
// How much to inline.
|
||||
const (
|
||||
// Default behavior. The compiler decides for itself whether any given
|
||||
// function will be inlined. Whether any function is inlined depends on the
|
||||
// optimization level.
|
||||
inlineDefault inlineType = iota
|
||||
|
||||
// Inline hint, just like the C inline keyword (signalled using
|
||||
// //go:inline). The compiler will be more likely to inline this function,
|
||||
// but it is not a guarantee.
|
||||
inlineHint
|
||||
|
||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
||||
// //go:noinline.
|
||||
inlineNone
|
||||
)
|
||||
|
||||
// getFunction returns the LLVM function for the given *ssa.Function, creating
|
||||
// it if needed. It can later be filled with compilerContext.createFunction().
|
||||
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
info := c.getFunctionInfo(fn)
|
||||
llvmFn := c.mod.NamedFunction(info.linkName)
|
||||
if !llvmFn.IsNil() {
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
var retType llvm.Type
|
||||
if fn.Signature.Results() == nil {
|
||||
retType = c.ctx.VoidType()
|
||||
} else if fn.Signature.Results().Len() == 1 {
|
||||
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
|
||||
} else {
|
||||
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
|
||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
||||
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
|
||||
}
|
||||
retType = c.ctx.StructType(results, false)
|
||||
}
|
||||
|
||||
var paramInfos []paramInfo
|
||||
for _, param := range getParams(fn.Signature) {
|
||||
paramType := c.getLLVMType(param.Type())
|
||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
}
|
||||
|
||||
// Add an extra parameter as the function context. This context is used in
|
||||
// closures and bound methods, but should be optimized away when not used.
|
||||
if !info.exported {
|
||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
|
||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
|
||||
}
|
||||
|
||||
var paramTypes []llvm.Type
|
||||
for _, info := range paramInfos {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
|
||||
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
|
||||
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
|
||||
if strings.HasPrefix(c.Triple, "wasm") {
|
||||
// C functions without prototypes like this:
|
||||
// void foo();
|
||||
// are actually variadic functions. However, it appears that it has been
|
||||
// decided in WebAssembly that such prototype-less functions are not
|
||||
// allowed in WebAssembly.
|
||||
// In C, this can only happen when there are zero parameters, hence this
|
||||
// check here. For more information:
|
||||
// https://reviews.llvm.org/D48443
|
||||
// https://github.com/WebAssembly/tool-conventions/issues/16
|
||||
if info.variadic && len(fn.Params) == 0 {
|
||||
attr := c.ctx.CreateStringAttribute("no-prototype", "")
|
||||
llvmFn.AddFunctionAttr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
|
||||
for i, info := range paramInfos {
|
||||
if info.flags¶mIsDeferenceableOrNull == 0 {
|
||||
continue
|
||||
}
|
||||
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
|
||||
el := info.llvmType.ElementType()
|
||||
size := c.targetData.TypeAllocSize(el)
|
||||
if size == 0 {
|
||||
// dereferenceable_or_null(0) appears to be illegal in LLVM.
|
||||
continue
|
||||
}
|
||||
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
|
||||
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a number of function or parameter attributes, depending on the
|
||||
// function. These functions are runtime functions that are known to have
|
||||
// certain attributes that might not be inferred by the compiler.
|
||||
switch info.linkName {
|
||||
case "abort":
|
||||
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
|
||||
// Mark it as noreturn so LLVM can optimize away code.
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
|
||||
case "runtime.alloc":
|
||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||
// returns values that are never null and never alias to an existing value.
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
|
||||
}
|
||||
case "runtime.sliceAppend":
|
||||
// Appending a slice will only read the to-be-appended slice, it won't
|
||||
// be modified.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.sliceCopy":
|
||||
// Copying a slice won't capture any of the parameters.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.trackPointer":
|
||||
// This function is necessary for tracking pointers on the stack in a
|
||||
// portable way (see gc_stack_portable.go). Indicate to the optimizer
|
||||
// that the only thing we'll do is read the pointer.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
}
|
||||
|
||||
// External/exported functions may not retain pointer values.
|
||||
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
||||
if info.exported {
|
||||
// Set the wasm-import-module attribute if the function's module is set.
|
||||
if info.module != "" {
|
||||
|
||||
// We need to add the wasm-import-module and the wasm-import-name
|
||||
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
|
||||
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
|
||||
|
||||
// Add the Wasm Import Name, if we are a named wasm import
|
||||
if info.importName != "" {
|
||||
wasmImportNameAttr := c.ctx.CreateStringAttribute("wasm-import-name", info.importName)
|
||||
llvmFn.AddFunctionAttr(wasmImportNameAttr)
|
||||
}
|
||||
}
|
||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||
for i, typ := range paramTypes {
|
||||
if typ.TypeKind() == llvm.PointerTypeKind {
|
||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Synthetic functions are functions that do not appear in the source code,
|
||||
// they are artificially constructed. Usually they are wrapper functions
|
||||
// that are not referenced anywhere except in a SSA call instruction so
|
||||
// should be created right away.
|
||||
// The exception is the package initializer, which does appear in the
|
||||
// *ssa.Package members and so shouldn't be created here.
|
||||
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
|
||||
irbuilder := c.ctx.NewBuilder()
|
||||
b := newBuilder(c, irbuilder, fn)
|
||||
b.createFunction()
|
||||
irbuilder.Dispose()
|
||||
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
llvmFn.SetUnnamedAddr(true)
|
||||
}
|
||||
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
// getFunctionInfo returns information about a function that is not directly
|
||||
// present in *ssa.Function, such as the link name and whether it should be
|
||||
// exported.
|
||||
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
info := functionInfo{}
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// Created by CGo: such a name cannot be created by regular C code.
|
||||
info.linkName = f.Name()[2:]
|
||||
info.exported = true
|
||||
} else {
|
||||
// Pick the default linkName.
|
||||
info.linkName = f.RelString(nil)
|
||||
}
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
info.parsePragmas(f)
|
||||
return info
|
||||
}
|
||||
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
// //export or //go:noinline.
|
||||
func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
if f.Syntax() == nil {
|
||||
return
|
||||
}
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
|
||||
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
|
||||
var importName string
|
||||
|
||||
for _, comment := range decl.Doc.List {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
importName = parts[1]
|
||||
info.exported = true
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
info.module = parts[1]
|
||||
case "//go:inline":
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
case "//go:linkname":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
// Only enable go:linkname when the package imports "unsafe".
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.section = parts[1]
|
||||
}
|
||||
case "//go:nobounds":
|
||||
// Skip bounds checking in this function. Useful for some
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:variadic":
|
||||
// The //go:variadic pragma is emitted by the CGo preprocessing
|
||||
// pass for C variadic functions. This includes both explicit
|
||||
// (with ...) and implicit (no parameters in signature)
|
||||
// functions.
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// This prefix cannot naturally be created, it must have
|
||||
// been created as a result of CGo preprocessing.
|
||||
info.variadic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the importName for our exported function if we have one
|
||||
if importName != "" {
|
||||
if info.module == "" {
|
||||
info.linkName = importName
|
||||
} else {
|
||||
// WebAssembly import
|
||||
info.importName = importName
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// getParams returns the function parameters, including the receiver at the
|
||||
// start. This is an alternative to the Params member of *ssa.Function, which is
|
||||
// not yet populated when the package has not yet been built.
|
||||
func getParams(sig *types.Signature) []*types.Var {
|
||||
params := []*types.Var{}
|
||||
if sig.Recv() != nil {
|
||||
params = append(params, sig.Recv())
|
||||
}
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
params = append(params, sig.Params().At(i))
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// 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).
|
||||
@@ -22,29 +330,27 @@ type globalInfo struct {
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
align int // go:align
|
||||
section string // go:section
|
||||
}
|
||||
|
||||
// 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 *compilerContext) 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
|
||||
}
|
||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||
for _, file := range pkg.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 := pkg.Pkg.Path() + "." + name.Name
|
||||
c.astComments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,30 +369,26 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
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 {
|
||||
alignment := c.targetData.ABITypeAlignment(llvmType)
|
||||
if info.align > alignment {
|
||||
alignment = info.align
|
||||
}
|
||||
if alignment <= 0 || alignment&(alignment-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)
|
||||
}
|
||||
alignInBits = uint32(alignment) ^ uint32(alignment-1)
|
||||
llvmGlobal.SetAlignment(alignment)
|
||||
}
|
||||
|
||||
if c.Debug() && !info.extern {
|
||||
if c.Debug && !info.extern {
|
||||
// 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())
|
||||
pos := c.program.Fset.Position(g.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: g.RelString(nil),
|
||||
LinkageName: info.linkName,
|
||||
@@ -142,6 +444,30 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
if err == nil {
|
||||
info.align = align
|
||||
}
|
||||
case "//go:section":
|
||||
if len(parts) == 2 {
|
||||
info.section = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
methods := make([]*types.Selection, ms.Len())
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
methods[i] = ms.At(i)
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// Return true if this package imports "unsafe", false otherwise.
|
||||
func hasUnsafeImport(pkg *types.Package) bool {
|
||||
for _, imp := range pkg.Imports() {
|
||||
if imp == types.Unsafe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+38
-16
@@ -10,14 +10,14 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createSyscall emits an inline system call instruction, depending on the
|
||||
// target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
// createRawSyscall creates a system call with the provided system call number
|
||||
// and returns the result as a single integer (the system call result). The
|
||||
// result is not further interpreted.
|
||||
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num := b.getValue(call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case b.GOARCH() == "amd64":
|
||||
if b.GOOS() == "darwin" {
|
||||
case b.GOARCH == "amd64":
|
||||
if b.GOOS == "darwin" {
|
||||
// Darwin adds this magic number to system call numbers:
|
||||
//
|
||||
// > Syscall classes for 64-bit system call entry.
|
||||
@@ -57,8 +57,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH() == "386" && b.GOOS() == "linux":
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
||||
// Sources:
|
||||
// syscall(2) man page
|
||||
// https://stackoverflow.com/a/2538212
|
||||
@@ -83,8 +83,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH() == "arm" && b.GOOS() == "linux":
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
@@ -115,8 +115,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
@@ -147,11 +147,20 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
switch b.GOOS() {
|
||||
}
|
||||
|
||||
// createSyscall emits instructions for the syscall.Syscall* family of
|
||||
// functions, depending on the target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
switch b.GOOS {
|
||||
case "linux", "freebsd":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
@@ -187,6 +196,19 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS()+"/"+b.GOARCH())
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// createRawSyscallNoError emits instructions for the Linux-specific
|
||||
// syscall.rawSyscallNoError function.
|
||||
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
// Basic tests that don't need to be split into a separate file.
|
||||
|
||||
func addInt(x, y int) int {
|
||||
return x + y
|
||||
}
|
||||
|
||||
func equalInt(x, y int) bool {
|
||||
return x == y
|
||||
}
|
||||
|
||||
func floatEQ(x, y float32) bool {
|
||||
return x == y
|
||||
}
|
||||
|
||||
func floatNE(x, y float32) bool {
|
||||
return x != y
|
||||
}
|
||||
|
||||
func floatLower(x, y float32) bool {
|
||||
return x < y
|
||||
}
|
||||
|
||||
func floatLowerEqual(x, y float32) bool {
|
||||
return x <= y
|
||||
}
|
||||
|
||||
func floatGreater(x, y float32) bool {
|
||||
return x > y
|
||||
}
|
||||
|
||||
func floatGreaterEqual(x, y float32) bool {
|
||||
return x >= y
|
||||
}
|
||||
|
||||
func complexReal(x complex64) float32 {
|
||||
return real(x)
|
||||
}
|
||||
|
||||
func complexImag(x complex64) float32 {
|
||||
return imag(x)
|
||||
}
|
||||
|
||||
func complexAdd(x, y complex64) complex64 {
|
||||
return x + y
|
||||
}
|
||||
|
||||
func complexSub(x, y complex64) complex64 {
|
||||
return x - y
|
||||
}
|
||||
|
||||
func complexMul(x, y complex64) complex64 {
|
||||
return x * y
|
||||
}
|
||||
|
||||
// TODO: complexDiv (requires runtime call)
|
||||
|
||||
// A type 'kv' also exists in function foo. Test that these two types don't
|
||||
// conflict with each other.
|
||||
type kv struct {
|
||||
v float32
|
||||
}
|
||||
|
||||
func foo(a *kv) {
|
||||
// Define a new 'kv' type.
|
||||
type kv struct {
|
||||
v byte
|
||||
}
|
||||
// Use this type.
|
||||
func(b *kv) {}(nil)
|
||||
}
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
; ModuleID = 'basic.go'
|
||||
source_filename = "basic.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%main.kv = type { float }
|
||||
%main.kv.0 = type { i8 }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = add i32 %x, %y
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp eq i32 %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp oeq float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp une float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp olt float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp ole float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp ogt float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fcmp oge float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret float %x.r
|
||||
}
|
||||
|
||||
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret float %x.i
|
||||
}
|
||||
|
||||
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fadd float %x.r, %y.r
|
||||
%1 = fadd float %x.i, %y.i
|
||||
%2 = insertvalue { float, float } undef, float %0, 0
|
||||
%3 = insertvalue { float, float } %2, float %1, 1
|
||||
ret { float, float } %3
|
||||
}
|
||||
|
||||
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fsub float %x.r, %y.r
|
||||
%1 = fsub float %x.i, %y.i
|
||||
%2 = insertvalue { float, float } undef, float %0, 0
|
||||
%3 = insertvalue { float, float } %2, float %1, 1
|
||||
ret { float, float } %3
|
||||
}
|
||||
|
||||
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = fmul float %x.r, %y.r
|
||||
%1 = fmul float %x.i, %y.i
|
||||
%2 = fsub float %0, %1
|
||||
%3 = fmul float %x.r, %y.i
|
||||
%4 = fmul float %x.i, %y.r
|
||||
%5 = fadd float %3, %4
|
||||
%6 = insertvalue { float, float } undef, float %2, 0
|
||||
%7 = insertvalue { float, float } %6, float %5, 1
|
||||
ret { float, float } %7
|
||||
}
|
||||
|
||||
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"main.foo$1"(%main.kv.0* null, i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
func chanIntSend(ch chan int) {
|
||||
ch <- 3
|
||||
}
|
||||
|
||||
func chanIntRecv(ch chan int) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func chanZeroSend(ch chan struct{}) {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
func chanZeroRecv(ch chan struct{}) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
|
||||
select {
|
||||
case ch1 <- 1:
|
||||
case <-ch2:
|
||||
default:
|
||||
}
|
||||
}
|
||||
Vendored
+114
@@ -0,0 +1,114 @@
|
||||
; ModuleID = 'channel.go'
|
||||
source_filename = "channel.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
|
||||
%"internal/task.state" = type { i8* }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
%chan.value.bitcast = bitcast i32* %chan.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
store i32 3, i32* %chan.value, align 4
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: argmemonly nounwind willreturn
|
||||
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #0
|
||||
|
||||
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
|
||||
|
||||
; Function Attrs: argmemonly nounwind willreturn
|
||||
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #0
|
||||
|
||||
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
%chan.value.bitcast = bitcast i32* %chan.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*, i8*)
|
||||
|
||||
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
|
||||
%select.send.value = alloca i32, align 4
|
||||
store i32 1, i32* %select.send.value, align 4
|
||||
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
|
||||
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
|
||||
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
|
||||
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
|
||||
%0 = bitcast i8** %.repack1 to i32**
|
||||
store i32* %select.send.value, i32** %0, align 4
|
||||
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
|
||||
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
|
||||
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
|
||||
store i8* null, i8** %.repack4, align 4
|
||||
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
|
||||
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
|
||||
%1 = extractvalue { i32, i1 } %select.result, 0
|
||||
%2 = icmp eq i32 %1, 0
|
||||
br i1 %2, label %select.done, label %select.next
|
||||
|
||||
select.done: ; preds = %select.body, %select.next, %entry
|
||||
ret void
|
||||
|
||||
select.next: ; preds = %entry
|
||||
%3 = icmp eq i32 %1, 1
|
||||
br i1 %3, label %select.body, label %select.done
|
||||
|
||||
select.body: ; preds = %select.next
|
||||
br label %select.done
|
||||
}
|
||||
|
||||
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*, i8*)
|
||||
|
||||
attributes #0 = { argmemonly nounwind willreturn }
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
// Test converting floats to ints.
|
||||
|
||||
func f32tou32(v float32) uint32 {
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func maxu32f() float32 {
|
||||
return float32(^uint32(0))
|
||||
}
|
||||
|
||||
func maxu32tof32() uint32 {
|
||||
f := float32(^uint32(0))
|
||||
return uint32(f)
|
||||
}
|
||||
|
||||
func inftoi32() (uint32, uint32, int32, int32) {
|
||||
inf := 1.0
|
||||
inf /= 0.0
|
||||
|
||||
return uint32(inf), uint32(-inf), int32(inf), int32(-inf)
|
||||
}
|
||||
|
||||
func u32tof32tou32(v uint32) uint32 {
|
||||
return uint32(float32(v))
|
||||
}
|
||||
|
||||
func f32tou32tof32(v float32) float32 {
|
||||
return float32(uint32(v))
|
||||
}
|
||||
|
||||
func f32tou8(v float32) uint8 {
|
||||
return uint8(v)
|
||||
}
|
||||
|
||||
func f32toi8(v float32) int8 {
|
||||
return int8(v)
|
||||
}
|
||||
Vendored
+82
@@ -0,0 +1,82 @@
|
||||
; ModuleID = 'float.go'
|
||||
source_filename = "float.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i32
|
||||
%normal = fptoui float %v to i32
|
||||
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define hidden float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret float 0x41F0000000000000
|
||||
}
|
||||
|
||||
define hidden i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
|
||||
}
|
||||
|
||||
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = uitofp i32 %v to float
|
||||
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
|
||||
%normal = fptoui float %0 to i32
|
||||
%1 = select i1 %withinmax, i32 %normal, i32 -1
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
define hidden float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i32
|
||||
%normal = fptoui float %v to i32
|
||||
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
|
||||
%1 = uitofp i32 %0 to float
|
||||
ret float %1
|
||||
}
|
||||
|
||||
define hidden i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 2.550000e+02
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i8
|
||||
%normal = fptoui float %v to i8
|
||||
%0 = select i1 %inbounds, i8 %normal, i8 %saturated
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
define hidden i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%abovemin = fcmp oge float %v, -1.280000e+02
|
||||
%belowmax = fcmp ole float %v, 1.270000e+02
|
||||
%inbounds = and i1 %abovemin, %belowmax
|
||||
%saturated = select i1 %abovemin, i8 127, i8 -128
|
||||
%isnan = fcmp uno float %v, 0.000000e+00
|
||||
%remapped = select i1 %isnan, i8 0, i8 %saturated
|
||||
%normal = fptosi float %v to i8
|
||||
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
|
||||
ret i8 %0
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
func foo(callback func(int)) {
|
||||
callback(3)
|
||||
}
|
||||
|
||||
func bar() {
|
||||
foo(someFunc)
|
||||
}
|
||||
|
||||
func someFunc(int) {
|
||||
}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
; ModuleID = 'func.go'
|
||||
source_filename = "func.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
|
||||
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
|
||||
@"main.someFunc$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @main.someFunc to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.foo(i8* %callback.context, i32 %callback.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i32 @runtime.getFuncPtr(i8* %callback.context, i32 %callback.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
|
||||
%1 = icmp eq i32 %0, 0
|
||||
br i1 %1, label %fpcall.throw, label %fpcall.next
|
||||
|
||||
fpcall.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
%2 = inttoptr i32 %0 to void (i32, i8*, i8*)*
|
||||
call void %2(i32 3, i8* %callback.context, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
define hidden void @main.bar(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @main.foo(i8* undef, i32 ptrtoint (%runtime.funcValueWithSignature* @"main.someFunc$withSignature" to i32), i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.someFunc(i32 %arg0, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
// Test changes to the language introduced in Go 1.17.
|
||||
// For details, see: https://tip.golang.org/doc/go1.17#language
|
||||
// These tests should be merged into the regular slice tests once Go 1.17 is the
|
||||
// minimun Go version for TinyGo.
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
|
||||
return unsafe.Add(p, len)
|
||||
}
|
||||
|
||||
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
|
||||
return unsafe.Add(p, len)
|
||||
}
|
||||
|
||||
func SliceToArray(s []int) *[4]int {
|
||||
return (*[4]int)(s)
|
||||
}
|
||||
|
||||
func SliceToArrayConst() *[4]int {
|
||||
s := make([]int, 6)
|
||||
return (*[4]int)(s)
|
||||
}
|
||||
|
||||
func SliceInt(ptr *int, len int) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceUint16(ptr *byte, len uint16) []byte {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceUint64(ptr *int, len uint64) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceInt64(ptr *int, len int64) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
Vendored
+136
@@ -0,0 +1,136 @@
|
||||
; ModuleID = 'go1.17.go'
|
||||
source_filename = "go1.17.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr i8, i8* %p, i32 %len
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = trunc i64 %len to i32
|
||||
%1 = getelementptr i8, i8* %p, i32 %0
|
||||
ret i8* %1
|
||||
}
|
||||
|
||||
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp ult i32 %s.len, 4
|
||||
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
|
||||
|
||||
slicetoarray.throw: ; preds = %entry
|
||||
call void @runtime.sliceToArrayPointerPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
slicetoarray.next: ; preds = %entry
|
||||
%1 = bitcast i32* %s.data to [4 x i32]*
|
||||
ret [4 x i32]* %1
|
||||
}
|
||||
|
||||
declare void @runtime.sliceToArrayPointerPanic(i8*, i8*)
|
||||
|
||||
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%makeslice = call i8* @runtime.alloc(i32 24, i8* undef, i8* null)
|
||||
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
|
||||
|
||||
slicetoarray.throw: ; preds = %entry
|
||||
unreachable
|
||||
|
||||
slicetoarray.next: ; preds = %entry
|
||||
%0 = bitcast i8* %makeslice to [4 x i32]*
|
||||
ret [4 x i32]* %0
|
||||
}
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp ugt i32 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i32 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
|
||||
ret { i32*, i32, i32 } %7
|
||||
}
|
||||
|
||||
declare void @runtime.unsafeSlicePanic(i8*, i8*)
|
||||
|
||||
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp eq i8* %ptr, null
|
||||
%1 = icmp ne i16 %len, 0
|
||||
%2 = and i1 %0, %1
|
||||
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%3 = zext i16 %len to i32
|
||||
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
|
||||
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
|
||||
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
|
||||
ret { i8*, i32, i32 } %6
|
||||
}
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp ugt i64 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i64 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = trunc i64 %len to i32
|
||||
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
|
||||
ret { i32*, i32, i32 } %8
|
||||
}
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp ugt i64 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i64 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = trunc i64 %len to i32
|
||||
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
|
||||
ret { i32*, i32, i32 } %8
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
; ModuleID = 'goroutine.go'
|
||||
source_filename = "goroutine.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv7m-none-eabi"
|
||||
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
|
||||
%"internal/task.state" = type { i32, i32* }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.regularFunction(i32, i8*, i8*)
|
||||
|
||||
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
|
||||
entry:
|
||||
%unpack.int = ptrtoint i8* %0 to i32
|
||||
call void @main.regularFunction(i32 %unpack.int, i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* nonnull inttoptr (i32 5 to i8*), i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
|
||||
entry:
|
||||
%unpack.int = ptrtoint i8* %0 to i32
|
||||
call void @"main.inlineFunctionGoroutine$1"(i32 %unpack.int, i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
|
||||
%0 = bitcast i8* %n to i32*
|
||||
store i32 3, i32* %0, align 4
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %n, i8** %4, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null)
|
||||
%5 = load i32, i32* %0, align 4
|
||||
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%unpack.ptr = bitcast i8* %context to i32*
|
||||
store i32 7, i32* %unpack.ptr, align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printint32(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.funcGoroutine(i8* %fn.context, void (i32, i8*, i8*)* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
store i32 5, i32* %1, align 4
|
||||
%2 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%3 = bitcast i8* %2 to i8**
|
||||
store i8* %fn.context, i8** %3, align 4
|
||||
%4 = getelementptr inbounds i8, i8* %0, i32 8
|
||||
%5 = bitcast i8* %4 to void (i32, i8*, i8*)**
|
||||
store void (i32, i8*, i8*)* %fn.funcptr, void (i32, i8*, i8*)** %5, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #3 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
%6 = getelementptr inbounds i8, i8* %0, i32 8
|
||||
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
|
||||
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
|
||||
call void %8(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
|
||||
|
||||
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
|
||||
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
|
||||
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
|
||||
attributes #3 = { "tinygo-gowrapper" }
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
; ModuleID = 'goroutine.go'
|
||||
source_filename = "goroutine.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.state" }
|
||||
%"internal/task.state" = type { i8* }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
|
||||
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.regularFunction(i32, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
|
||||
%0 = bitcast i8* %n to i32*
|
||||
store i32 3, i32* %0, align 4
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %n, i8** %4, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null)
|
||||
%5 = load i32, i32* %0, align 4
|
||||
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%unpack.ptr = bitcast i8* %context to i32*
|
||||
store i32 7, i32* %unpack.ptr, align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printint32(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %fn.context, i8** %4, align 4
|
||||
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
|
||||
define hidden void @main.recoverBuiltinGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.copyBuiltinGoroutine(i8* %dst.data, i32 %dst.len, i32 %dst.cap, i8* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%copy.n = call i32 @runtime.sliceCopy(i8* %dst.data, i8* %src.data, i32 %dst.len, i32 %src.len, i32 1, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.closeBuiltinGoroutine(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.chanClose(%runtime.channel* %ch, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(%runtime.channel* dereferenceable_or_null(32), i8*, i8*)
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
func regularFunctionGoroutine() {
|
||||
go regularFunction(5)
|
||||
}
|
||||
|
||||
func inlineFunctionGoroutine() {
|
||||
go func(x int) {
|
||||
}(5)
|
||||
}
|
||||
|
||||
func closureFunctionGoroutine() {
|
||||
n := 3
|
||||
go func(x int) {
|
||||
n = 7
|
||||
}(5)
|
||||
print(n) // note: this is racy (but good enough for this test)
|
||||
}
|
||||
|
||||
func funcGoroutine(fn func(x int)) {
|
||||
go fn(5)
|
||||
}
|
||||
|
||||
func recoverBuiltinGoroutine() {
|
||||
// This is a no-op.
|
||||
go recover()
|
||||
}
|
||||
|
||||
func copyBuiltinGoroutine(dst, src []byte) {
|
||||
// This is not run in a goroutine. While this copy operation can indeed take
|
||||
// some time (if there is a lot of data to copy), there is no race-free way
|
||||
// to make use of the result so it's unlikely applications will make use of
|
||||
// it. And doing it this way should be just within the Go specification.
|
||||
go copy(dst, src)
|
||||
}
|
||||
|
||||
func closeBuiltinGoroutine(ch chan int) {
|
||||
// This builtin is executed directly, not in a goroutine.
|
||||
// The observed behavior is the same.
|
||||
go close(ch)
|
||||
}
|
||||
|
||||
func regularFunction(x int)
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
// This file tests interface types and interface builtins.
|
||||
|
||||
package main
|
||||
|
||||
// Test interface construction.
|
||||
|
||||
func simpleType() interface{} {
|
||||
return 0
|
||||
}
|
||||
|
||||
func pointerType() interface{} {
|
||||
// Pointers have an element type, in this case int.
|
||||
var v *int
|
||||
return v
|
||||
}
|
||||
|
||||
func interfaceType() interface{} {
|
||||
// Interfaces can exist in interfaces, but only indirectly (through
|
||||
// pointers).
|
||||
var v *error
|
||||
return v
|
||||
}
|
||||
|
||||
func anonymousInterfaceType() interface{} {
|
||||
var v *interface {
|
||||
String() string
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// Test interface builtins.
|
||||
|
||||
func isInt(itf interface{}) bool {
|
||||
_, ok := itf.(int)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isError(itf interface{}) bool {
|
||||
// Interface assert on (builtin) named interface type.
|
||||
_, ok := itf.(error)
|
||||
return ok
|
||||
}
|
||||
|
||||
func isStringer(itf interface{}) bool {
|
||||
// Interface assert on anonymous interface type.
|
||||
_, ok := itf.(interface {
|
||||
String() string
|
||||
})
|
||||
return ok
|
||||
}
|
||||
|
||||
func callErrorMethod(itf error) string {
|
||||
return itf.Error()
|
||||
}
|
||||
Vendored
+101
@@ -0,0 +1,101 @@
|
||||
; ModuleID = 'interface.go'
|
||||
source_filename = "interface.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||
%runtime._interface = type { i32, i8* }
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" }
|
||||
@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
|
||||
@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
|
||||
@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" }
|
||||
@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }
|
||||
@"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1
|
||||
@"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
|
||||
@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
|
||||
@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null }
|
||||
@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }
|
||||
@"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1
|
||||
@"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"]
|
||||
@"reflect/types.typeid:basic:int" = external constant i8
|
||||
@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"]
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden %runtime._interface @main.simpleType(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* null }
|
||||
}
|
||||
|
||||
define hidden %runtime._interface @main.pointerType(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:basic:int" to i32), i8* null }
|
||||
}
|
||||
|
||||
define hidden %runtime._interface @main.interfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null }
|
||||
}
|
||||
|
||||
define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null }
|
||||
}
|
||||
|
||||
define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, i8* nonnull @"reflect/types.typeid:basic:int", i8* undef, i8* null)
|
||||
br i1 %typecode, label %typeassert.ok, label %typeassert.next
|
||||
|
||||
typeassert.ok: ; preds = %entry
|
||||
br label %typeassert.next
|
||||
|
||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||
ret i1 %typecode
|
||||
}
|
||||
|
||||
declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
|
||||
define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null)
|
||||
br i1 %0, label %typeassert.ok, label %typeassert.next
|
||||
|
||||
typeassert.ok: ; preds = %entry
|
||||
br label %typeassert.next
|
||||
|
||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*)
|
||||
|
||||
define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null)
|
||||
br i1 %0, label %typeassert.ok, label %typeassert.next
|
||||
|
||||
typeassert.ok: ; preds = %entry
|
||||
br label %typeassert.next
|
||||
|
||||
typeassert.next: ; preds = %typeassert.ok, %entry
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null)
|
||||
%invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)*
|
||||
%0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef)
|
||||
ret %runtime._string %0
|
||||
}
|
||||
|
||||
declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*)
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
; ModuleID = 'intrinsics.go'
|
||||
source_filename = "intrinsics.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv7m-none-eabi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call double @math.Sqrt(double %x, i8* undef, i8* undef)
|
||||
ret double %0
|
||||
}
|
||||
|
||||
declare double @math.Sqrt(double, i8*, i8*)
|
||||
|
||||
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call double @math.Trunc(double %x, i8* undef, i8* undef)
|
||||
ret double %0
|
||||
}
|
||||
|
||||
declare double @math.Trunc(double, i8*, i8*)
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
; ModuleID = 'intrinsics.go'
|
||||
source_filename = "intrinsics.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden double @main.mySqrt(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call double @llvm.sqrt.f64(double %x)
|
||||
ret double %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind readnone speculatable willreturn
|
||||
declare double @llvm.sqrt.f64(double) #0
|
||||
|
||||
define hidden double @main.myTrunc(double %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call double @llvm.trunc.f64(double %x)
|
||||
ret double %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind readnone speculatable willreturn
|
||||
declare double @llvm.trunc.f64(double) #0
|
||||
|
||||
attributes #0 = { nounwind readnone speculatable willreturn }
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
// Test how intrinsics are lowered: either as regular calls to the math
|
||||
// functions or as LLVM builtins (such as llvm.sqrt.f64).
|
||||
|
||||
import "math"
|
||||
|
||||
func mySqrt(x float64) float64 {
|
||||
return math.Sqrt(x)
|
||||
}
|
||||
|
||||
func myTrunc(x float64) float64 {
|
||||
return math.Trunc(x)
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
// This file tests various operations on pointers, such as pointer arithmetic
|
||||
// and dereferencing pointers.
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Dereference pointers.
|
||||
|
||||
func pointerDerefZero(x *[0]int) [0]int {
|
||||
return *x // This is a no-op, there is nothing to load.
|
||||
}
|
||||
|
||||
// Unsafe pointer casts, they are sometimes a no-op.
|
||||
|
||||
func pointerCastFromUnsafe(x unsafe.Pointer) *int {
|
||||
return (*int)(x)
|
||||
}
|
||||
|
||||
func pointerCastToUnsafe(x *int) unsafe.Pointer {
|
||||
return unsafe.Pointer(x)
|
||||
}
|
||||
|
||||
func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer {
|
||||
return unsafe.Pointer(x)
|
||||
}
|
||||
|
||||
// The compiler has support for a few special cast+add patterns that are
|
||||
// transformed into a single GEP.
|
||||
|
||||
func pointerUnsafeGEPFixedOffset(ptr *byte) *byte {
|
||||
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 10))
|
||||
}
|
||||
|
||||
func pointerUnsafeGEPByteOffset(ptr *byte, offset uintptr) *byte {
|
||||
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset))
|
||||
}
|
||||
|
||||
func pointerUnsafeGEPIntOffset(ptr *int32, offset uintptr) *int32 {
|
||||
return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset*4))
|
||||
}
|
||||
Vendored
+51
@@ -0,0 +1,51 @@
|
||||
; ModuleID = 'pointer.go'
|
||||
source_filename = "pointer.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret [0 x i32] zeroinitializer
|
||||
}
|
||||
|
||||
define hidden i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = bitcast i8* %x to i32*
|
||||
ret i32* %0
|
||||
}
|
||||
|
||||
define hidden i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = bitcast i32* %x to i8*
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define hidden i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i8* %x
|
||||
}
|
||||
|
||||
define hidden i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define hidden i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define hidden i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr i32, i32* %ptr, i32 %offset
|
||||
ret i32* %0
|
||||
}
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
// Creates an external global with name extern_global.
|
||||
//go:extern extern_global
|
||||
var externGlobal [0]byte
|
||||
|
||||
// Creates a
|
||||
//go:align 32
|
||||
var alignedGlobal [4]uint32
|
||||
|
||||
// Test conflicting pragmas (the last one counts).
|
||||
//go:align 64
|
||||
//go:align 16
|
||||
var alignedGlobal16 [4]uint32
|
||||
|
||||
// Test exported functions.
|
||||
//export extern_func
|
||||
func externFunc() {
|
||||
}
|
||||
|
||||
// Define a function in a different package using go:linkname.
|
||||
//go:linkname withLinkageName1 somepkg.someFunction1
|
||||
func withLinkageName1() {
|
||||
}
|
||||
|
||||
// Import a function from a different package using go:linkname.
|
||||
//go:linkname withLinkageName2 somepkg.someFunction2
|
||||
func withLinkageName2()
|
||||
|
||||
// Function has an 'inline hint', similar to the inline keyword in C.
|
||||
//go:inline
|
||||
func inlineFunc() {
|
||||
}
|
||||
|
||||
// Function should never be inlined, equivalent to GCC
|
||||
// __attribute__((noinline)).
|
||||
//go:noinline
|
||||
func noinlineFunc() {
|
||||
}
|
||||
|
||||
// This function should have the specified section.
|
||||
//go:section .special_function_section
|
||||
func functionInSection() {
|
||||
}
|
||||
|
||||
//export exportedFunctionInSection
|
||||
//go:section .special_function_section
|
||||
func exportedFunctionInSection() {
|
||||
}
|
||||
|
||||
// This function should not: it's only a declaration and not a definition.
|
||||
//go:section .special_function_section
|
||||
func undefinedFunctionNotInSection()
|
||||
|
||||
//go:section .special_global_section
|
||||
var globalInSection uint32
|
||||
|
||||
//go:section .special_global_section
|
||||
//go:extern undefinedGlobalNotInSection
|
||||
var undefinedGlobalNotInSection uint32
|
||||
|
||||
//go:align 1024
|
||||
//go:section .global_section
|
||||
var multipleGlobalPragmas uint32
|
||||
Vendored
+59
@@ -0,0 +1,59 @@
|
||||
; ModuleID = 'pragma.go'
|
||||
source_filename = "pragma.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
@extern_global = external global [0 x i8], align 1
|
||||
@main.alignedGlobal = hidden global [4 x i32] zeroinitializer, align 32
|
||||
@main.alignedGlobal16 = hidden global [4 x i32] zeroinitializer, align 16
|
||||
@main.globalInSection = hidden global i32 0, section ".special_global_section", align 4
|
||||
@undefinedGlobalNotInSection = external global i32, align 4
|
||||
@main.multipleGlobalPragmas = hidden global i32 0, section ".global_section", align 1024
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @extern_func() #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @somepkg.someFunction1(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @somepkg.someFunction2(i8*, i8*)
|
||||
|
||||
; Function Attrs: inlinehint
|
||||
define hidden void @main.inlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #1 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: noinline
|
||||
define hidden void @main.noinlineFunc(i8* %context, i8* %parentHandle) unnamed_addr #2 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.functionInSection(i8* %context, i8* %parentHandle) unnamed_addr section ".special_function_section" {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @exportedFunctionInSection() #3 section ".special_function_section" {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.undefinedFunctionNotInSection(i8*, i8*)
|
||||
|
||||
attributes #0 = { "wasm-export-name"="extern_func" }
|
||||
attributes #1 = { inlinehint }
|
||||
attributes #2 = { noinline }
|
||||
attributes #3 = { "wasm-export-name"="exportedFunctionInSection" }
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
package main
|
||||
|
||||
func sliceLen(ints []int) int {
|
||||
return len(ints)
|
||||
}
|
||||
|
||||
func sliceCap(ints []int) int {
|
||||
return cap(ints)
|
||||
}
|
||||
|
||||
func sliceElement(ints []int, index int) int {
|
||||
return ints[index]
|
||||
}
|
||||
|
||||
func sliceAppendValues(ints []int) []int {
|
||||
return append(ints, 1, 2, 3)
|
||||
}
|
||||
|
||||
func sliceAppendSlice(ints, added []int) []int {
|
||||
return append(ints, added...)
|
||||
}
|
||||
|
||||
func sliceCopy(dst, src []int) int {
|
||||
return copy(dst, src)
|
||||
}
|
||||
|
||||
// Test bounds checking in *ssa.MakeSlice instruction.
|
||||
|
||||
func makeByteSlice(len int) []byte {
|
||||
return make([]byte, len)
|
||||
}
|
||||
|
||||
func makeInt16Slice(len int) []int16 {
|
||||
return make([]int16, len)
|
||||
}
|
||||
|
||||
func makeArraySlice(len int) [][3]byte {
|
||||
return make([][3]byte, len) // slice with element size of 3
|
||||
}
|
||||
|
||||
func makeInt32Slice(len int) []int32 {
|
||||
return make([]int32, len)
|
||||
}
|
||||
Vendored
+164
@@ -0,0 +1,164 @@
|
||||
; ModuleID = 'slice.go'
|
||||
source_filename = "slice.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 %ints.len
|
||||
}
|
||||
|
||||
define hidden i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 %ints.cap
|
||||
}
|
||||
|
||||
define hidden i32 @main.sliceElement(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%.not = icmp ult i32 %index, %ints.len
|
||||
br i1 %.not, label %lookup.next, label %lookup.throw
|
||||
|
||||
lookup.throw: ; preds = %entry
|
||||
call void @runtime.lookupPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
lookup.next: ; preds = %entry
|
||||
%0 = getelementptr inbounds i32, i32* %ints.data, i32 %index
|
||||
%1 = load i32, i32* %0, align 4
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
declare void @runtime.lookupPanic(i8*, i8*)
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.sliceAppendValues(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%varargs = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
|
||||
%0 = bitcast i8* %varargs to i32*
|
||||
store i32 1, i32* %0, align 4
|
||||
%1 = getelementptr inbounds i8, i8* %varargs, i32 4
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 2, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %varargs, i32 8
|
||||
%4 = bitcast i8* %3 to i32*
|
||||
store i32 3, i32* %4, align 4
|
||||
%append.srcPtr = bitcast i32* %ints.data to i8*
|
||||
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* nonnull %varargs, i32 %ints.len, i32 %ints.cap, i32 3, i32 4, i8* undef, i8* null)
|
||||
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
|
||||
%append.newBuf = bitcast i8* %append.newPtr to i32*
|
||||
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
|
||||
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
|
||||
%5 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
|
||||
%6 = insertvalue { i32*, i32, i32 } %5, i32 %append.newLen, 1
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %append.newCap, 2
|
||||
ret { i32*, i32, i32 } %7
|
||||
}
|
||||
|
||||
declare { i8*, i32, i32 } @runtime.sliceAppend(i8*, i8* nocapture readonly, i32, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.sliceAppendSlice(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i32* %added.data, i32 %added.len, i32 %added.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%append.srcPtr = bitcast i32* %ints.data to i8*
|
||||
%append.srcPtr1 = bitcast i32* %added.data to i8*
|
||||
%append.new = call { i8*, i32, i32 } @runtime.sliceAppend(i8* %append.srcPtr, i8* %append.srcPtr1, i32 %ints.len, i32 %ints.cap, i32 %added.len, i32 4, i8* undef, i8* null)
|
||||
%append.newPtr = extractvalue { i8*, i32, i32 } %append.new, 0
|
||||
%append.newBuf = bitcast i8* %append.newPtr to i32*
|
||||
%append.newLen = extractvalue { i8*, i32, i32 } %append.new, 1
|
||||
%append.newCap = extractvalue { i8*, i32, i32 } %append.new, 2
|
||||
%0 = insertvalue { i32*, i32, i32 } undef, i32* %append.newBuf, 0
|
||||
%1 = insertvalue { i32*, i32, i32 } %0, i32 %append.newLen, 1
|
||||
%2 = insertvalue { i32*, i32, i32 } %1, i32 %append.newCap, 2
|
||||
ret { i32*, i32, i32 } %2
|
||||
}
|
||||
|
||||
define hidden i32 @main.sliceCopy(i32* %dst.data, i32 %dst.len, i32 %dst.cap, i32* %src.data, i32 %src.len, i32 %src.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%copy.dstPtr = bitcast i32* %dst.data to i8*
|
||||
%copy.srcPtr = bitcast i32* %src.data to i8*
|
||||
%copy.n = call i32 @runtime.sliceCopy(i8* %copy.dstPtr, i8* %copy.srcPtr, i32 %dst.len, i32 %src.len, i32 4, i8* undef, i8* null)
|
||||
ret i32 %copy.n
|
||||
}
|
||||
|
||||
declare i32 @runtime.sliceCopy(i8* nocapture writeonly, i8* nocapture readonly, i32, i32, i32, i8*, i8*)
|
||||
|
||||
define hidden { i8*, i32, i32 } @main.makeByteSlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%slice.maxcap = icmp slt i32 %len, 0
|
||||
br i1 %slice.maxcap, label %slice.throw, label %slice.next
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.buf = call i8* @runtime.alloc(i32 %len, i8* undef, i8* null)
|
||||
%0 = insertvalue { i8*, i32, i32 } undef, i8* %makeslice.buf, 0
|
||||
%1 = insertvalue { i8*, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { i8*, i32, i32 } %1, i32 %len, 2
|
||||
ret { i8*, i32, i32 } %2
|
||||
}
|
||||
|
||||
declare void @runtime.slicePanic(i8*, i8*)
|
||||
|
||||
define hidden { i16*, i32, i32 } @main.makeInt16Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%slice.maxcap = icmp slt i32 %len, 0
|
||||
br i1 %slice.maxcap, label %slice.throw, label %slice.next
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = shl i32 %len, 1
|
||||
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
|
||||
%makeslice.array = bitcast i8* %makeslice.buf to i16*
|
||||
%0 = insertvalue { i16*, i32, i32 } undef, i16* %makeslice.array, 0
|
||||
%1 = insertvalue { i16*, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { i16*, i32, i32 } %1, i32 %len, 2
|
||||
ret { i16*, i32, i32 } %2
|
||||
}
|
||||
|
||||
define hidden { [3 x i8]*, i32, i32 } @main.makeArraySlice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%slice.maxcap = icmp ugt i32 %len, 1431655765
|
||||
br i1 %slice.maxcap, label %slice.throw, label %slice.next
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = mul i32 %len, 3
|
||||
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
|
||||
%makeslice.array = bitcast i8* %makeslice.buf to [3 x i8]*
|
||||
%0 = insertvalue { [3 x i8]*, i32, i32 } undef, [3 x i8]* %makeslice.array, 0
|
||||
%1 = insertvalue { [3 x i8]*, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { [3 x i8]*, i32, i32 } %1, i32 %len, 2
|
||||
ret { [3 x i8]*, i32, i32 } %2
|
||||
}
|
||||
|
||||
define hidden { i32*, i32, i32 } @main.makeInt32Slice(i32 %len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%slice.maxcap = icmp ugt i32 %len, 1073741823
|
||||
br i1 %slice.maxcap, label %slice.throw, label %slice.next
|
||||
|
||||
slice.throw: ; preds = %entry
|
||||
call void @runtime.slicePanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
slice.next: ; preds = %entry
|
||||
%makeslice.cap = shl i32 %len, 2
|
||||
%makeslice.buf = call i8* @runtime.alloc(i32 %makeslice.cap, i8* undef, i8* null)
|
||||
%makeslice.array = bitcast i8* %makeslice.buf to i32*
|
||||
%0 = insertvalue { i32*, i32, i32 } undef, i32* %makeslice.array, 0
|
||||
%1 = insertvalue { i32*, i32, i32 } %0, i32 %len, 1
|
||||
%2 = insertvalue { i32*, i32, i32 } %1, i32 %len, 2
|
||||
ret { i32*, i32, i32 } %2
|
||||
}
|
||||
Vendored
+29
@@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
func someString() string {
|
||||
return "foo"
|
||||
}
|
||||
|
||||
func zeroLengthString() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func stringLen(s string) int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func stringIndex(s string, index int) byte {
|
||||
return s[index]
|
||||
}
|
||||
|
||||
func stringCompareEqual(s1, s2 string) bool {
|
||||
return s1 == s2
|
||||
}
|
||||
|
||||
func stringCompareUnequal(s1, s2 string) bool {
|
||||
return s1 != s2
|
||||
}
|
||||
|
||||
func stringCompareLarger(s1, s2 string) bool {
|
||||
return s1 > s2
|
||||
}
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
; ModuleID = 'string.go'
|
||||
source_filename = "string.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
@"main.someString$string" = internal unnamed_addr constant [3 x i8] c"foo", align 1
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden %runtime._string @main.someString(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._string { i8* getelementptr inbounds ([3 x i8], [3 x i8]* @"main.someString$string", i32 0, i32 0), i32 3 }
|
||||
}
|
||||
|
||||
define hidden %runtime._string @main.zeroLengthString(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret %runtime._string zeroinitializer
|
||||
}
|
||||
|
||||
define hidden i32 @main.stringLen(i8* %s.data, i32 %s.len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 %s.len
|
||||
}
|
||||
|
||||
define hidden i8 @main.stringIndex(i8* %s.data, i32 %s.len, i32 %index, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%.not = icmp ult i32 %index, %s.len
|
||||
br i1 %.not, label %lookup.next, label %lookup.throw
|
||||
|
||||
lookup.throw: ; preds = %entry
|
||||
call void @runtime.lookupPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
lookup.next: ; preds = %entry
|
||||
%0 = getelementptr inbounds i8, i8* %s.data, i32 %index
|
||||
%1 = load i8, i8* %0, align 1
|
||||
ret i8 %1
|
||||
}
|
||||
|
||||
declare void @runtime.lookupPanic(i8*, i8*)
|
||||
|
||||
define hidden i1 @main.stringCompareEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
declare i1 @runtime.stringEqual(i8*, i32, i8*, i32, i8*, i8*)
|
||||
|
||||
define hidden i1 @main.stringCompareUnequal(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i1 @runtime.stringEqual(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
|
||||
%1 = xor i1 %0, true
|
||||
ret i1 %1
|
||||
}
|
||||
|
||||
define hidden i1 @main.stringCompareLarger(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i1 @runtime.stringLess(i8* %s1.data, i32 %s1.len, i8* %s2.data, i32 %s2.len, i8* undef, i8* null)
|
||||
%1 = xor i1 %0, true
|
||||
ret i1 %1
|
||||
}
|
||||
|
||||
declare i1 @runtime.stringLess(i8*, i32, i8*, i32, i8*, i8*)
|
||||
@@ -1,14 +1,16 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.11
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac
|
||||
github.com/chromedp/chromedp v0.5.3
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693
|
||||
github.com/chromedp/chromedp v0.6.4
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
||||
go.bug.st/serial v1.0.0
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
|
||||
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
go.bug.st/serial v1.1.2
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007
|
||||
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9
|
||||
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
|
||||
)
|
||||
|
||||
@@ -1,50 +1,74 @@
|
||||
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/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac h1:T7V5BXqnYd55Hj/g5uhDYumg9Fp3rMTS6bykYtTIFX4=
|
||||
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
||||
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
|
||||
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y=
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0=
|
||||
github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVDSY=
|
||||
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
|
||||
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
|
||||
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
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/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
|
||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
|
||||
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
|
||||
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
|
||||
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9 h1:nvvuMxmx1q0gfRki3T0hjG8EwAcVCs91oWAXvyt4zhI=
|
||||
golang.org/x/tools v0.1.6-0.20210813165731-45389f592fe9/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d h1:hcX7vpB067GWM/EH4sGGOti0PMgIx+0bbZwUXctOIvE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200503225853-345b2947b59d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4 h1:CMUHxVTb+UuUePuMf8vkWjZ3gTp9BBK91KrgOCwoNHs=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210308112806-9ef958b6bed4/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c h1:vn9IPshzYmzZis10UEVrsIBRv9FpykADw6M3/tHHROg=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
|
||||
+24
-4
@@ -142,10 +142,11 @@ func getHomeDir() string {
|
||||
// getGoroot returns an appropriate GOROOT from various sources. If it can't be
|
||||
// found, it returns an empty string.
|
||||
func getGoroot() string {
|
||||
// An explicitly set GOROOT always has preference.
|
||||
goroot := os.Getenv("GOROOT")
|
||||
if goroot != "" {
|
||||
// An explicitly set GOROOT always has preference.
|
||||
return goroot
|
||||
// Convert to the standard GOROOT being referenced, if it's a TinyGo cache.
|
||||
return getStandardGoroot(goroot)
|
||||
}
|
||||
|
||||
// Check for the location of the 'go' binary and base GOROOT on that.
|
||||
@@ -170,8 +171,9 @@ func getGoroot() string {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
candidates = []string{
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/lib/go", // from the distribution
|
||||
"/usr/local/go", // manually installed
|
||||
"/usr/lib/go", // from the distribution
|
||||
"/snap/go/current/", // installed using snap
|
||||
}
|
||||
case "darwin":
|
||||
candidates = []string{
|
||||
@@ -195,3 +197,21 @@ func isGoroot(goroot string) bool {
|
||||
_, err := os.Stat(filepath.Join(goroot, "src", "runtime", "internal", "sys", "zversion.go"))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// getStandardGoroot returns the physical path to a real, standard Go GOROOT
|
||||
// implied by the given path.
|
||||
// If the given path appears to be a TinyGo cached GOROOT, it returns the path
|
||||
// referenced by symlinks contained in the cache. Otherwise, it returns the
|
||||
// given path as-is.
|
||||
func getStandardGoroot(path string) string {
|
||||
// Check if the "bin" subdirectory of our given GOROOT is a symlink, and then
|
||||
// return the _parent_ directory of its destination.
|
||||
if dest, err := os.Readlink(filepath.Join(path, "bin")); nil == err {
|
||||
// Clean the destination to remove any trailing slashes, so that
|
||||
// filepath.Dir will always return the parent.
|
||||
// (because both "/foo" and "/foo/" are valid symlink destinations,
|
||||
// but filepath.Dir would return "/" and "/foo", respectively)
|
||||
return filepath.Dir(filepath.Clean(dest))
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
+5
-5
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// Version of TinyGo.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.15.0"
|
||||
const Version = "0.20.0"
|
||||
|
||||
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
@@ -48,7 +48,10 @@ func GetGorootVersion(goroot string) (major, minor int, err error) {
|
||||
// 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(
|
||||
if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||
return string(data), nil
|
||||
|
||||
} else if data, err := ioutil.ReadFile(filepath.Join(
|
||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||
|
||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||
@@ -59,9 +62,6 @@ func GorootVersionString(goroot string) (string, error) {
|
||||
|
||||
return string(matches[1]), nil
|
||||
|
||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||
return string(data), nil
|
||||
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
|
||||
+92
-37
@@ -6,50 +6,81 @@ possible and only run unknown expressions (e.g. external calls) at runtime. This
|
||||
is in practice a partial evaluator of the `runtime.initAll` function, which
|
||||
calls each package initializer.
|
||||
|
||||
It works by directly interpreting LLVM IR:
|
||||
This package is a rewrite of a previous partial evaluator that worked
|
||||
directly on LLVM IR and used the module and LLVM constants as intermediate
|
||||
values. This newer version instead uses a mostly Go intermediate form. It
|
||||
compiles functions and extracts relevant data first (compiler.go), then
|
||||
executes those functions (interpreter.go) in a memory space that can be
|
||||
rolled back per function (memory.go). This means that it is not necessary to
|
||||
scan functions to see whether they can be run at compile time, which was very
|
||||
error prone. Instead it just tries to execute everything and if it hits
|
||||
something it cannot interpret (such as a store to memory-mapped I/O) it rolls
|
||||
back the execution of that function and runs the function at runtime instead.
|
||||
All in all, this design provides several benefits:
|
||||
|
||||
* Almost all operations work directly on constants, and are implemented using
|
||||
the llvm.Const* set of functions that are evaluated directly.
|
||||
* External function calls and some other operations (inline assembly, volatile
|
||||
load, volatile store) are seen as having limited side effects. Limited in
|
||||
the sense that it is known at compile time which globals it affects, which
|
||||
then are marked 'dirty' (meaning, further operations on it must be done at
|
||||
runtime). These operations are emitted directly in the `runtime.initAll`
|
||||
function. Return values are also considered 'dirty'.
|
||||
* Such 'dirty' objects and local values must be executed at runtime instead of
|
||||
at compile time. This dirtyness propagates further through the IR, for
|
||||
example storing a dirty local value to a global also makes the global dirty,
|
||||
meaning that the global may not be read or written at compile time as it's
|
||||
contents at that point during interpretation is unknown.
|
||||
* There are some heuristics in place to avoid doing too much with dirty
|
||||
values. For example, a branch based on a dirty local marks the whole
|
||||
function itself as having side effect (as if it is an external function).
|
||||
However, all globals it touches are still taken into account and when a call
|
||||
is inserted in `runtime.initAll`, all globals it references are also marked
|
||||
dirty.
|
||||
* Heap allocation (`runtime.alloc`) is emulated by creating new objects. The
|
||||
value in the allocation is the initializer of the global, the zero value is
|
||||
the zero initializer.
|
||||
* Stack allocation (`alloca`) is often emulated using a fake alloca object,
|
||||
until the address of the alloca is taken in which case it is also created as
|
||||
a real `alloca` in `runtime.initAll` and marked dirty. This may be necessary
|
||||
when calling an external function with the given alloca as paramter.
|
||||
* Much better error handling. By being able to revert to runtime execution
|
||||
without the need for scanning functions, this version is able to
|
||||
automatically work around many bugs in the previous implementation.
|
||||
* More correct memory model. This is not inherent to the new design, but the
|
||||
new design also made the memory model easier to reason about.
|
||||
* Faster execution of initialization code. While it is not much faster for
|
||||
normal interpretation (maybe 25% or so) due to the compilation overhead,
|
||||
it should be a whole lot faster for loops as it doesn't have to call into
|
||||
LLVM (via CGo) for every operation.
|
||||
|
||||
As mentioned, this partial evaulator comes in three parts: a compiler, an
|
||||
interpreter, and a memory manager.
|
||||
|
||||
## Compiler
|
||||
|
||||
The main task of the compiler is that it extracts all necessary data from
|
||||
every instruction in a function so that when this instruction is interpreted,
|
||||
no additional CGo calls are necessary. This is not currently done for all
|
||||
instructions (`runtime.alloc` is a notable exception), but at least it does
|
||||
so for the vast majority of instructions.
|
||||
|
||||
## Interpreter
|
||||
|
||||
The interpreter runs an instruction just like it would if it were executed
|
||||
'for real'. The vast majority of instructions can be executed at compile
|
||||
time. As indicated above, some instructions need to be executed at runtime
|
||||
instead.
|
||||
|
||||
## Memory
|
||||
|
||||
Memory is represented as objects (the `object` type) that contains data that
|
||||
will eventually be stored in a global and values (the `value` interface) that
|
||||
can be worked with while running the interpreter. Values therefore are only
|
||||
used locally and are always passed by value (just like most LLVM constants)
|
||||
while objects represent the backing storage (like LLVM globals). Some values
|
||||
are pointer values, and point to an object.
|
||||
|
||||
Importantly, this partial evaluator can roll back the execution of a
|
||||
function. This is implemented by creating a new memory view per function
|
||||
activation, which makes sure that any change to a global (such as a store
|
||||
instruction) is stored in the memory view. It creates a copy of the object
|
||||
and stores that in the memory view to be modified. Once the function has
|
||||
executed successfully, all these modified objects are then copied into the
|
||||
parent function, up to the root function invocation which (on successful
|
||||
execution) writes the values back into the LLVM module. This way, function
|
||||
invocations can be rolled back without leaving a trace.
|
||||
|
||||
Pointer values point to memory objects, but not to a particular memory
|
||||
object. Every memory object is given an index, and pointers use that index to
|
||||
look up the current active object for the pointer to load from or to copy
|
||||
when storing to it.
|
||||
|
||||
Rolling back a function should roll back everyting, including the few
|
||||
instructions emitted at runtime. This is done by treating instructions much
|
||||
like memory objects and removing the created instructions when necessary.
|
||||
|
||||
## Why is this necessary?
|
||||
|
||||
A partial evaluator is hard to get right, so why go through all the trouble of
|
||||
writing one?
|
||||
|
||||
The main reason is that the previous attempt wasn't complete and wasn't sound.
|
||||
It simply tried to evaluate Go SSA directly, which was good but more difficult
|
||||
than necessary. An IR based interpreter needs to understand fewer instructions
|
||||
as the LLVM IR simply has less (complex) instructions than Go SSA. Also, LLVM
|
||||
provides some useful tools like easily getting all uses of a function or global,
|
||||
which Go SSA does not provide.
|
||||
|
||||
But why is it necessary at all? The answer is that globals with initializers are
|
||||
much easier to optimize by LLVM than initialization code. Also, there are a few
|
||||
other benefits:
|
||||
The answer is that globals with initializers are much easier to optimize by
|
||||
LLVM than initialization code. Also, there are a few other benefits:
|
||||
|
||||
* Dead globals are trivial to optimize away.
|
||||
* Constant globals are easier to detect. Remember that Go does not have global
|
||||
@@ -60,5 +91,29 @@ other benefits:
|
||||
* Constants are much more efficent on microcontrollers, as they can be
|
||||
allocated in flash instead of RAM.
|
||||
|
||||
The Go SSA package does not create constant initializers for globals.
|
||||
Instead, it emits initialization functions, so if you write the following:
|
||||
|
||||
```go
|
||||
var foo = []byte{1, 2, 3, 4}
|
||||
```
|
||||
|
||||
It would generate something like this:
|
||||
|
||||
```go
|
||||
var foo []byte
|
||||
|
||||
func init() {
|
||||
foo = make([]byte, 4)
|
||||
foo[0] = 1
|
||||
foo[1] = 2
|
||||
foo[2] = 3
|
||||
foo[3] = 4
|
||||
}
|
||||
```
|
||||
|
||||
This is of course hugely wasteful, it's much better to create `foo` as a
|
||||
global array instead of initializing it at runtime.
|
||||
|
||||
For more details, see [this section of the
|
||||
documentation](https://tinygo.org/compiler-internals/differences-from-go/).
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package interp
|
||||
|
||||
// This file compiles the LLVM IR to a form that's easy to efficiently
|
||||
// interpret.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A function is a compiled LLVM function, which means that interpreting it
|
||||
// avoids most CGo calls necessary. This is done in a separate step so the
|
||||
// result can be cached.
|
||||
// Functions are in SSA form, just like the LLVM version if it. The first block
|
||||
// (blocks[0]) is the entry block.
|
||||
type function struct {
|
||||
llvmFn llvm.Value
|
||||
name string // precalculated llvmFn.Name()
|
||||
params []llvm.Value // precalculated llvmFn.Params()
|
||||
blocks []*basicBlock
|
||||
locals map[llvm.Value]int
|
||||
}
|
||||
|
||||
// basicBlock represents a LLVM basic block and contains a slice of
|
||||
// instructions. The last instruction must be a terminator instruction.
|
||||
type basicBlock struct {
|
||||
phiNodes []instruction
|
||||
instructions []instruction
|
||||
}
|
||||
|
||||
// instruction is a precompiled LLVM IR instruction. The operands can be either
|
||||
// an already known value (such as literalValue or pointerValue) but can also be
|
||||
// the special localValue, which means that the value is a function parameter or
|
||||
// is produced by another instruction in the function. In that case, the
|
||||
// interpreter will replace the operand with that local value.
|
||||
type instruction struct {
|
||||
opcode llvm.Opcode
|
||||
localIndex int
|
||||
operands []value
|
||||
llvmInst llvm.Value
|
||||
name string
|
||||
}
|
||||
|
||||
// String returns a nice human-readable version of this instruction.
|
||||
func (inst *instruction) String() string {
|
||||
operands := make([]string, len(inst.operands))
|
||||
for i, op := range inst.operands {
|
||||
operands[i] = op.String()
|
||||
}
|
||||
|
||||
name := instructionNameMap[inst.opcode]
|
||||
if name == "" {
|
||||
name = "<unknown op>"
|
||||
}
|
||||
return name + " " + strings.Join(operands, " ")
|
||||
}
|
||||
|
||||
// compileFunction compiles a given LLVM function to an easier to interpret
|
||||
// version of the function. As far as possible, all operands are preprocessed so
|
||||
// that the interpreter doesn't have to call into LLVM.
|
||||
func (r *runner) compileFunction(llvmFn llvm.Value) *function {
|
||||
fn := &function{
|
||||
llvmFn: llvmFn,
|
||||
name: llvmFn.Name(),
|
||||
params: llvmFn.Params(),
|
||||
locals: make(map[llvm.Value]int),
|
||||
}
|
||||
if llvmFn.IsDeclaration() {
|
||||
// Nothing to do.
|
||||
return fn
|
||||
}
|
||||
|
||||
for i, param := range fn.params {
|
||||
fn.locals[param] = i
|
||||
}
|
||||
|
||||
// Make a map of all the blocks, to quickly find the block number for a
|
||||
// given branch instruction.
|
||||
blockIndices := make(map[llvm.Value]int)
|
||||
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
|
||||
index := len(blockIndices)
|
||||
blockIndices[llvmBB.AsValue()] = index
|
||||
}
|
||||
|
||||
// Compile every block.
|
||||
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
|
||||
bb := &basicBlock{}
|
||||
fn.blocks = append(fn.blocks, bb)
|
||||
|
||||
// Compile every instruction in the block.
|
||||
for llvmInst := llvmBB.FirstInstruction(); !llvmInst.IsNil(); llvmInst = llvm.NextInstruction(llvmInst) {
|
||||
// Create instruction skeleton.
|
||||
opcode := llvmInst.InstructionOpcode()
|
||||
inst := instruction{
|
||||
opcode: opcode,
|
||||
localIndex: len(fn.locals),
|
||||
llvmInst: llvmInst,
|
||||
}
|
||||
fn.locals[llvmInst] = len(fn.locals)
|
||||
|
||||
// Add operands specific for this instruction.
|
||||
switch opcode {
|
||||
case llvm.Ret:
|
||||
// Return instruction, which can either be a `ret void` (no
|
||||
// return value) or return a value.
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
if numOperands != 0 {
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
}
|
||||
}
|
||||
case llvm.Br:
|
||||
// Branch instruction. Can be either a conditional branch (with
|
||||
// 3 operands) or unconditional branch (with just one basic
|
||||
// block operand).
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
switch numOperands {
|
||||
case 3:
|
||||
// Conditional jump to one of two blocks. Comparable to an
|
||||
// if/else in procedural languages.
|
||||
thenBB := llvmInst.Operand(2)
|
||||
elseBB := llvmInst.Operand(1)
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint32(blockIndices[thenBB])},
|
||||
literalValue{uint32(blockIndices[elseBB])},
|
||||
}
|
||||
case 1:
|
||||
// Unconditional jump to a target basic block. Comparable to
|
||||
// a jump in C and Go.
|
||||
jumpBB := llvmInst.Operand(0)
|
||||
inst.operands = []value{
|
||||
literalValue{uint32(blockIndices[jumpBB])},
|
||||
}
|
||||
default:
|
||||
panic("unknown number of operands")
|
||||
}
|
||||
case llvm.Switch:
|
||||
// A switch is an array of (value, label) pairs, of which the
|
||||
// first one indicates the to-switch value and the default
|
||||
// label.
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
for i := 0; i < numOperands; i += 2 {
|
||||
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
|
||||
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
|
||||
}
|
||||
case llvm.PHI:
|
||||
inst.name = llvmInst.Name()
|
||||
incomingCount := inst.llvmInst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
incomingBB := inst.llvmInst.IncomingBlock(i)
|
||||
incomingValue := inst.llvmInst.IncomingValue(i)
|
||||
inst.operands = append(inst.operands,
|
||||
literalValue{uint32(blockIndices[incomingBB.AsValue()])},
|
||||
r.getValue(incomingValue),
|
||||
)
|
||||
}
|
||||
case llvm.Select:
|
||||
// Select is a special instruction that is much like a ternary
|
||||
// operator. It produces operand 1 or 2 based on the boolean
|
||||
// that is operand 0.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
r.getValue(llvmInst.Operand(2)),
|
||||
}
|
||||
case llvm.Call:
|
||||
// Call is a regular function call but could also be a runtime
|
||||
// intrinsic. Some runtime intrinsics are treated specially by
|
||||
// the interpreter, such as runtime.alloc. We don't
|
||||
// differentiate between them here because these calls may also
|
||||
// need to be run at runtime, in which case they should all be
|
||||
// created in the same way.
|
||||
llvmCalledValue := llvmInst.CalledValue()
|
||||
if !llvmCalledValue.IsAFunction().IsNil() {
|
||||
name := llvmCalledValue.Name()
|
||||
if name == "llvm.dbg.value" || strings.HasPrefix(name, "llvm.lifetime.") {
|
||||
// These intrinsics should not be interpreted, they are not
|
||||
// relevant to the execution of this function.
|
||||
continue
|
||||
}
|
||||
}
|
||||
inst.name = llvmInst.Name()
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
inst.operands = append(inst.operands, r.getValue(llvmCalledValue))
|
||||
for i := 0; i < numOperands-1; i++ {
|
||||
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
|
||||
}
|
||||
case llvm.Load:
|
||||
// Load instruction. The interpreter will load from the
|
||||
// appropriate memory view.
|
||||
// Also provide the memory size to be loaded, which is necessary
|
||||
// with a lack of type information.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{r.targetData.TypeAllocSize(llvmInst.Type())},
|
||||
}
|
||||
case llvm.Store:
|
||||
// Store instruction. The interpreter will create a new object
|
||||
// in the memory view of the function invocation and store to
|
||||
// that, to make it possible to roll back this store.
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
}
|
||||
case llvm.Alloca:
|
||||
// Alloca allocates stack space for local variables.
|
||||
numElements := r.getValue(inst.llvmInst.Operand(0)).(literalValue).value.(uint32)
|
||||
elementSize := r.targetData.TypeAllocSize(inst.llvmInst.Type().ElementType())
|
||||
inst.operands = []value{
|
||||
literalValue{elementSize * uint64(numElements)},
|
||||
}
|
||||
case llvm.GetElementPtr:
|
||||
// GetElementPtr does pointer arithmetic.
|
||||
inst.name = llvmInst.Name()
|
||||
ptr := llvmInst.Operand(0)
|
||||
n := llvmInst.OperandsCount()
|
||||
elementType := ptr.Type().ElementType()
|
||||
// gep: [source ptr, dest value size, pairs of indices...]
|
||||
inst.operands = []value{
|
||||
r.getValue(ptr),
|
||||
literalValue{r.targetData.TypeAllocSize(llvmInst.Type().ElementType())},
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{r.targetData.TypeAllocSize(elementType)},
|
||||
}
|
||||
for i := 2; i < n; i++ {
|
||||
operand := r.getValue(llvmInst.Operand(i))
|
||||
if elementType.TypeKind() == llvm.StructTypeKind {
|
||||
index := operand.(literalValue).value.(uint32)
|
||||
elementOffset := r.targetData.ElementOffset(elementType, int(index))
|
||||
// Encode operands in a special way. The elementOffset
|
||||
// is just the offset in bytes. The elementSize is a
|
||||
// negative number (when cast to a int64) by flipping
|
||||
// all the bits. This allows the interpreter to detect
|
||||
// this is a struct field and that it should not
|
||||
// multiply it with the elementOffset to get the offset.
|
||||
// It is important for the interpreter to know the
|
||||
// struct field index for when the GEP must be done at
|
||||
// runtime.
|
||||
inst.operands = append(inst.operands, literalValue{elementOffset}, literalValue{^uint64(index)})
|
||||
elementType = elementType.StructElementTypes()[index]
|
||||
} else {
|
||||
elementType = elementType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(elementType)
|
||||
elementSizeOperand := literalValue{elementSize}
|
||||
// Add operand * elementSizeOperand bytes to the pointer.
|
||||
inst.operands = append(inst.operands, operand, elementSizeOperand)
|
||||
}
|
||||
}
|
||||
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
|
||||
// Bitcasts are ususally used to cast a pointer from one type to
|
||||
// another leaving the pointer itself intact.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
}
|
||||
case llvm.ExtractValue:
|
||||
inst.name = llvmInst.Name()
|
||||
agg := llvmInst.Operand(0)
|
||||
var offset uint64
|
||||
indexingType := agg.Type()
|
||||
for _, index := range inst.llvmInst.Indices() {
|
||||
switch indexingType.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
offset += r.targetData.ElementOffset(indexingType, int(index))
|
||||
indexingType = indexingType.StructElementTypes()[index]
|
||||
default: // ArrayTypeKind
|
||||
indexingType = indexingType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(indexingType)
|
||||
offset += elementSize * uint64(index)
|
||||
}
|
||||
}
|
||||
size := r.targetData.TypeAllocSize(inst.llvmInst.Type())
|
||||
// extractvalue [agg, byteOffset, byteSize]
|
||||
inst.operands = []value{
|
||||
r.getValue(agg),
|
||||
literalValue{offset},
|
||||
literalValue{size},
|
||||
}
|
||||
case llvm.InsertValue:
|
||||
inst.name = llvmInst.Name()
|
||||
agg := llvmInst.Operand(0)
|
||||
var offset uint64
|
||||
indexingType := agg.Type()
|
||||
for _, index := range inst.llvmInst.Indices() {
|
||||
switch indexingType.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
offset += r.targetData.ElementOffset(indexingType, int(index))
|
||||
indexingType = indexingType.StructElementTypes()[index]
|
||||
default: // ArrayTypeKind
|
||||
indexingType = indexingType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(indexingType)
|
||||
offset += elementSize * uint64(index)
|
||||
}
|
||||
}
|
||||
// insertvalue [agg, elt, byteOffset]
|
||||
inst.operands = []value{
|
||||
r.getValue(agg),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{offset},
|
||||
}
|
||||
case llvm.ICmp:
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{uint8(llvmInst.IntPredicate())},
|
||||
}
|
||||
case llvm.FCmp:
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{uint8(llvmInst.FloatPredicate())},
|
||||
}
|
||||
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
|
||||
// Integer binary operations.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
}
|
||||
case llvm.SExt, llvm.ZExt, llvm.Trunc:
|
||||
// Extend or shrink an integer size.
|
||||
// No sign extension going on so easy to do.
|
||||
// zext: [value, bitwidth]
|
||||
// trunc: [value, bitwidth]
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint64(llvmInst.Type().IntTypeWidth())},
|
||||
}
|
||||
case llvm.SIToFP, llvm.UIToFP:
|
||||
// Convert an integer to a floating point instruction.
|
||||
// opcode: [value, bitwidth]
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint64(r.targetData.TypeAllocSize(llvmInst.Type()) * 8)},
|
||||
}
|
||||
default:
|
||||
// Unknown instruction, which is already set in inst.opcode so
|
||||
// is detectable.
|
||||
// This error is handled when actually trying to interpret this
|
||||
// instruction (to not trigger on code that won't be executed).
|
||||
}
|
||||
if inst.opcode == llvm.PHI {
|
||||
// PHI nodes need to be treated specially, see the comment in
|
||||
// interpreter.go for an explanation.
|
||||
bb.phiNodes = append(bb.phiNodes, inst)
|
||||
} else {
|
||||
bb.instructions = append(bb.instructions, inst)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// instructionNameMap maps from instruction opcodes to instruction names. This
|
||||
// can be useful for debug logging.
|
||||
var instructionNameMap = [...]string{
|
||||
llvm.Ret: "ret",
|
||||
llvm.Br: "br",
|
||||
llvm.Switch: "switch",
|
||||
llvm.IndirectBr: "indirectbr",
|
||||
llvm.Invoke: "invoke",
|
||||
llvm.Unreachable: "unreachable",
|
||||
|
||||
// Standard Binary Operators
|
||||
llvm.Add: "add",
|
||||
llvm.FAdd: "fadd",
|
||||
llvm.Sub: "sub",
|
||||
llvm.FSub: "fsub",
|
||||
llvm.Mul: "mul",
|
||||
llvm.FMul: "fmul",
|
||||
llvm.UDiv: "udiv",
|
||||
llvm.SDiv: "sdiv",
|
||||
llvm.FDiv: "fdiv",
|
||||
llvm.URem: "urem",
|
||||
llvm.SRem: "srem",
|
||||
llvm.FRem: "frem",
|
||||
|
||||
// Logical Operators
|
||||
llvm.Shl: "shl",
|
||||
llvm.LShr: "lshr",
|
||||
llvm.AShr: "ashr",
|
||||
llvm.And: "and",
|
||||
llvm.Or: "or",
|
||||
llvm.Xor: "xor",
|
||||
|
||||
// Memory Operators
|
||||
llvm.Alloca: "alloca",
|
||||
llvm.Load: "load",
|
||||
llvm.Store: "store",
|
||||
llvm.GetElementPtr: "getelementptr",
|
||||
|
||||
// Cast Operators
|
||||
llvm.Trunc: "trunc",
|
||||
llvm.ZExt: "zext",
|
||||
llvm.SExt: "sext",
|
||||
llvm.FPToUI: "fptoui",
|
||||
llvm.FPToSI: "fptosi",
|
||||
llvm.UIToFP: "uitofp",
|
||||
llvm.SIToFP: "sitofp",
|
||||
llvm.FPTrunc: "fptrunc",
|
||||
llvm.FPExt: "fpext",
|
||||
llvm.PtrToInt: "ptrtoint",
|
||||
llvm.IntToPtr: "inttoptr",
|
||||
llvm.BitCast: "bitcast",
|
||||
|
||||
// Other Operators
|
||||
llvm.ICmp: "icmp",
|
||||
llvm.FCmp: "fcmp",
|
||||
llvm.PHI: "phi",
|
||||
llvm.Call: "call",
|
||||
llvm.Select: "select",
|
||||
llvm.VAArg: "vaarg",
|
||||
llvm.ExtractElement: "extractelement",
|
||||
llvm.InsertElement: "insertelement",
|
||||
llvm.ShuffleVector: "shufflevector",
|
||||
llvm.ExtractValue: "extractvalue",
|
||||
llvm.InsertValue: "insertvalue",
|
||||
}
|
||||
+20
-12
@@ -11,15 +11,22 @@ import (
|
||||
"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 = &Error{Err: errors.New("interp: unreachable executed")}
|
||||
// These errors are expected during normal execution and can be recovered from
|
||||
// by running the affected function at runtime instead of compile time.
|
||||
var (
|
||||
errIntegerAsPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)")
|
||||
errUnsupportedInst = errors.New("interp: unsupported instruction")
|
||||
errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)")
|
||||
errMapAlreadyCreated = errors.New("interp: map already created")
|
||||
)
|
||||
|
||||
// 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) *Error {
|
||||
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
|
||||
// This is one of the errors that can be returned from toLLVMValue when the
|
||||
// passed type does not fit the data to serialize. It is recoverable by
|
||||
// serializing without a type (using rawValue.rawLLVMValue).
|
||||
var errInvalidPtrToIntSize = errors.New("interp: ptrtoint integer size does not equal pointer size")
|
||||
|
||||
func isRecoverableError(err error) bool {
|
||||
return err == errIntegerAsPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
|
||||
}
|
||||
|
||||
// ErrorLine is one line in a traceback. The position may be missing.
|
||||
@@ -46,13 +53,14 @@ func (e *Error) Error() string {
|
||||
// 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, err error) *Error {
|
||||
pos := getPosition(inst)
|
||||
func (r *runner) errorAt(inst instruction, err error) *Error {
|
||||
pos := getPosition(inst.llvmInst)
|
||||
return &Error{
|
||||
ImportPath: e.packagePath,
|
||||
ImportPath: r.pkgName,
|
||||
Inst: inst.llvmInst,
|
||||
Pos: pos,
|
||||
Err: err,
|
||||
Traceback: []ErrorLine{{pos, inst}},
|
||||
Traceback: []ErrorLine{{pos, inst.llvmInst}},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-708
@@ -1,708 +0,0 @@
|
||||
package interp
|
||||
|
||||
// This file implements the core interpretation routines, interpreting single
|
||||
// functions.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
*evalPackage
|
||||
fn llvm.Value
|
||||
locals map[llvm.Value]Value
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Most of it works at compile time. Some calls get translated into calls to be
|
||||
// executed at runtime: calls to functions with side effects, external calls,
|
||||
// and operations on the result of such instructions.
|
||||
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err *Error) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if fr.Debug {
|
||||
print(indent)
|
||||
inst.Dump()
|
||||
println()
|
||||
}
|
||||
switch {
|
||||
case !inst.IsABinaryOperator().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
|
||||
switch inst.InstructionOpcode() {
|
||||
// Standard binary operators
|
||||
case llvm.Add:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAdd(lhs, rhs, "")}
|
||||
case llvm.FAdd:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFAdd(lhs, rhs, "")}
|
||||
case llvm.Sub:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSub(lhs, rhs, "")}
|
||||
case llvm.FSub:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFSub(lhs, rhs, "")}
|
||||
case llvm.Mul:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateMul(lhs, rhs, "")}
|
||||
case llvm.FMul:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFMul(lhs, rhs, "")}
|
||||
case llvm.UDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUDiv(lhs, rhs, "")}
|
||||
case llvm.SDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSDiv(lhs, rhs, "")}
|
||||
case llvm.FDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFDiv(lhs, rhs, "")}
|
||||
case llvm.URem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateURem(lhs, rhs, "")}
|
||||
case llvm.SRem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSRem(lhs, rhs, "")}
|
||||
case llvm.FRem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFRem(lhs, rhs, "")}
|
||||
|
||||
// Logical operators
|
||||
case llvm.Shl:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateShl(lhs, rhs, "")}
|
||||
case llvm.LShr:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateLShr(lhs, rhs, "")}
|
||||
case llvm.AShr:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAShr(lhs, rhs, "")}
|
||||
case llvm.And:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAnd(lhs, rhs, "")}
|
||||
case llvm.Or:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateOr(lhs, rhs, "")}
|
||||
case llvm.Xor:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
|
||||
|
||||
default:
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
|
||||
// Memory operators
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
|
||||
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
||||
} else {
|
||||
var err error
|
||||
value, err = operand.Load()
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
}
|
||||
if value.Type() != inst.Type() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: load: type does not match"))
|
||||
}
|
||||
fr.locals[inst] = fr.getValue(value)
|
||||
case !inst.IsAStoreInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
ptr := fr.getLocal(inst.Operand(1))
|
||||
if inst.IsVolatile() {
|
||||
fr.builder.CreateStore(value.Value(), ptr.Value())
|
||||
} else {
|
||||
err := ptr.Store(value.Value())
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
}
|
||||
case !inst.IsAGetElementPtrInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
llvmIndices := make([]llvm.Value, inst.OperandsCount()-1)
|
||||
for i := range llvmIndices {
|
||||
llvmIndices[i] = inst.Operand(i + 1)
|
||||
}
|
||||
indices := make([]uint32, len(llvmIndices))
|
||||
for i, llvmIndex := range llvmIndices {
|
||||
operand := fr.getLocal(llvmIndex)
|
||||
if !operand.IsConstant() {
|
||||
// Not a constant operation.
|
||||
// This should be detected by the scanner, but isn't at the
|
||||
// moment.
|
||||
return nil, nil, fr.errorAt(inst, errors.New("todo: non-const gep"))
|
||||
}
|
||||
indices[i] = uint32(operand.Value().ZExtValue())
|
||||
}
|
||||
result, err := value.GetElementPtr(indices)
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
if result.Type() != inst.Type() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: gep: type does not match"))
|
||||
}
|
||||
fr.locals[inst] = result
|
||||
|
||||
// Cast operators
|
||||
case !inst.IsATruncInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAZExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateZExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsASExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPToUIInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToUI(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPToSIInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToSI(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAUIToFPInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsASIToFPInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPTruncInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAPtrToIntInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreatePtrToInt(value.Value(), inst.Type(), "")}
|
||||
case !inst.IsABitCastInst().IsNil() && inst.Type().TypeKind() == llvm.PointerTypeKind:
|
||||
operand := inst.Operand(0)
|
||||
if !operand.IsACallInst().IsNil() {
|
||||
fn := operand.CalledValue()
|
||||
if !fn.IsAFunction().IsNil() && fn.Name() == "runtime.alloc" {
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
||||
// Special case for runtime.trackPointer calls.
|
||||
// Note: this might not be entirely sound in some rare cases
|
||||
// where the map is stored in a dirty global.
|
||||
uses := getUses(inst)
|
||||
if len(uses) == 1 {
|
||||
use := uses[0]
|
||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It is not possible in Go to bitcast a map value to a pointer.
|
||||
return nil, nil, fr.errorAt(inst, errors.New("unimplemented: bitcast of map"))
|
||||
}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
// Other operators
|
||||
case !inst.IsAICmpInst().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.IntPredicate()
|
||||
if predicate == llvm.IntEQ {
|
||||
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
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.FloatPredicate()
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFCmp(predicate, lhs, rhs, "")}
|
||||
case !inst.IsAPHINode().IsNil():
|
||||
for i := 0; i < inst.IncomingCount(); i++ {
|
||||
if inst.IncomingBlock(i) == incoming {
|
||||
fr.locals[inst] = fr.getLocal(inst.IncomingValue(i))
|
||||
}
|
||||
}
|
||||
case !inst.IsACallInst().IsNil():
|
||||
callee := inst.CalledValue()
|
||||
switch {
|
||||
case callee.Name() == "runtime.alloc":
|
||||
// heap allocation
|
||||
users := getUses(inst)
|
||||
var resultInst = inst
|
||||
if len(users) == 1 && !users[0].IsABitCastInst().IsNil() {
|
||||
// happens when allocating something other than i8*
|
||||
resultInst = users[0]
|
||||
}
|
||||
size := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying.ZExtValue()
|
||||
allocType := resultInst.Type().ElementType()
|
||||
typeSize := fr.TargetData.TypeAllocSize(allocType)
|
||||
elementCount := 1
|
||||
if size != typeSize {
|
||||
// allocate an array
|
||||
if size%typeSize != 0 {
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
elementCount = int(size / typeSize)
|
||||
allocType = llvm.ArrayType(allocType, elementCount)
|
||||
}
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
|
||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
if elementCount == 1 {
|
||||
fr.locals[resultInst] = result
|
||||
} else {
|
||||
result, err := result.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
fr.locals[resultInst] = result
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapMake":
|
||||
// create a map
|
||||
keySize := inst.Operand(0).ZExtValue()
|
||||
valueSize := inst.Operand(1).ZExtValue()
|
||||
fr.locals[inst] = &MapValue{
|
||||
Eval: fr.Eval,
|
||||
PkgName: fr.packagePath,
|
||||
KeySize: int(keySize),
|
||||
ValueSize: int(valueSize),
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
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.
|
||||
err := m.PutString(keyBuf, keyLen, valPtr)
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
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
|
||||
}
|
||||
err := m.PutBinary(keyBuf, valPtr)
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
buf1Ptr := fr.getLocal(inst.Operand(0))
|
||||
buf1Len := fr.getLocal(inst.Operand(1))
|
||||
buf2Ptr := fr.getLocal(inst.Operand(2))
|
||||
buf2Len := fr.getLocal(inst.Operand(3))
|
||||
buf1, err := getStringBytes(buf1Ptr, buf1Len.Value())
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
buf2, err := getStringBytes(buf2Ptr, buf2Len.Value())
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
result := []byte(string(buf1) + string(buf2))
|
||||
vals := make([]llvm.Value, len(result))
|
||||
for i := range vals {
|
||||
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
stringType := fr.Mod.GetTypeByName("runtime._string")
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := llvm.ConstNull(stringType)
|
||||
ret = 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...
|
||||
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
dstArray = dstArrayValue.(*LocalValue)
|
||||
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
srcArray = srcArrayValue.(*LocalValue)
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("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, errors.New("interp: slice src element size does not match pointer type"))
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: slice element types don't match"))
|
||||
}
|
||||
length := dstLen.Value().SExtValue()
|
||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||
length = srcLength
|
||||
}
|
||||
if length < 0 {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: trying to copy a slice with negative length?"))
|
||||
}
|
||||
for i := int64(0); i < length; i++ {
|
||||
// *dst = *src
|
||||
val, err := srcArray.Load()
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
err = dstArray.Store(val)
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
// dst++
|
||||
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
dstArray = dstArrayValue.(*LocalValue)
|
||||
// src++
|
||||
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
srcArray = srcArrayValue.(*LocalValue)
|
||||
}
|
||||
case callee.Name() == "runtime.stringToBytes":
|
||||
// convert a string to a []byte
|
||||
bufPtr := fr.getLocal(inst.Operand(0))
|
||||
bufLen := fr.getLocal(inst.Operand(1))
|
||||
result, err := getStringBytes(bufPtr, bufLen.Value())
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err)
|
||||
}
|
||||
vals := make([]llvm.Value, len(result))
|
||||
for i := range vals {
|
||||
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
sliceType := inst.Type()
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := llvm.ConstNull(sliceType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.typeAssert":
|
||||
actualTypeInt := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
assertedType := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
if actualTypeInt.IsAConstantExpr().IsNil() || actualTypeInt.Opcode() != llvm.PtrToInt {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode in runtime.typeAssert to be a ptrtoint"))
|
||||
}
|
||||
actualType := actualTypeInt.Operand(0)
|
||||
if actualType.IsAConstant().IsNil() || assertedType.IsAConstant().IsNil() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: unimplemented: type assert with non-constant interface value"))
|
||||
}
|
||||
assertOk := uint64(0)
|
||||
if llvm.ConstExtractValue(actualType.Initializer(), []uint32{0}) == assertedType {
|
||||
assertOk = 1
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), assertOk, false)}
|
||||
case callee.Name() == "runtime.interfaceImplements":
|
||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: expected typecode to be a ptrtoint"))
|
||||
}
|
||||
typecode = typecode.Operand(0)
|
||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("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 {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: expected method set to be a constant gep"))
|
||||
}
|
||||
methodSet = methodSet.Operand(0).Initializer()
|
||||
|
||||
// Make a set of all the methods on the concrete type, for
|
||||
// easier checking in the next step.
|
||||
definedMethods := map[string]struct{}{}
|
||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
||||
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
||||
definedMethods[name] = struct{}{}
|
||||
}
|
||||
// Check whether all interface methods are also in the list
|
||||
// of defined methods calculated above.
|
||||
implements := uint64(1) // i1 true
|
||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
||||
if _, ok := definedMethods[name]; !ok {
|
||||
// There is a method on the interface that is not
|
||||
// implemented by the type.
|
||||
implements = 0 // i1 false
|
||||
break
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
||||
case callee.Name() == "runtime.nanotime":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case 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.
|
||||
// TODO: print an error when executing runtime._panic (with the
|
||||
// exact error message it would print at runtime).
|
||||
var params []llvm.Value
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
operand := fr.getLocal(inst.Operand(i)).Value()
|
||||
fr.markDirty(operand)
|
||||
params = append(params, operand)
|
||||
}
|
||||
// TODO: accurate debug info, including call chain
|
||||
fr.builder.CreateCall(callee, params, inst.Name())
|
||||
case !callee.IsAFunction().IsNil() && callee.IsDeclaration():
|
||||
// external functions
|
||||
var params []llvm.Value
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
operand := fr.getLocal(inst.Operand(i)).Value()
|
||||
fr.markDirty(operand)
|
||||
params = append(params, operand)
|
||||
}
|
||||
// TODO: accurate debug info, including call chain
|
||||
result := fr.builder.CreateCall(callee, params, inst.Name())
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
fr.markDirty(result)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, result}
|
||||
}
|
||||
case !callee.IsAFunction().IsNil():
|
||||
// regular function
|
||||
var params []Value
|
||||
dirtyParams := false
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
local := fr.getLocal(inst.Operand(i))
|
||||
if !local.IsConstant() {
|
||||
dirtyParams = true
|
||||
}
|
||||
params = append(params, local)
|
||||
}
|
||||
var ret Value
|
||||
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
|
||||
// is known at compile time which side effects it invokes.
|
||||
// This means the function can be called at runtime and the
|
||||
// affected globals can be marked dirty at compile time.
|
||||
llvmParams := make([]llvm.Value, len(params))
|
||||
for i, param := range params {
|
||||
llvmParams[i] = param.Value()
|
||||
}
|
||||
result := fr.builder.CreateCall(callee, llvmParams, inst.Name())
|
||||
ret = &LocalValue{fr.Eval, result}
|
||||
// mark all mentioned globals as dirty
|
||||
for global := range scanResult.mentionsGlobals {
|
||||
fr.markDirty(global)
|
||||
}
|
||||
} else {
|
||||
// Side effect is one of:
|
||||
// * None: no side effects, can be fully interpreted at
|
||||
// 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, indent+" ")
|
||||
if err != nil {
|
||||
// Record this function call in the backtrace.
|
||||
err.Traceback = append(err.Traceback, ErrorLine{
|
||||
Pos: getPosition(inst),
|
||||
Inst: inst,
|
||||
})
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
fr.locals[inst] = ret
|
||||
}
|
||||
default:
|
||||
// function pointers, etc.
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
case !inst.IsAExtractValueInst().IsNil():
|
||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||
indices := inst.Indices()
|
||||
if agg.Underlying.IsConstant() {
|
||||
newValue := llvm.ConstExtractValue(agg.Underlying, indices)
|
||||
fr.locals[inst] = fr.getValue(newValue)
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("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())}
|
||||
}
|
||||
case !inst.IsAInsertValueInst().IsNil():
|
||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||
val := fr.getLocal(inst.Operand(1))
|
||||
indices := inst.Indices()
|
||||
if agg.IsConstant() && val.IsConstant() {
|
||||
newValue := llvm.ConstInsertValue(agg.Underlying, val.Value(), indices)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("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())}
|
||||
}
|
||||
case !inst.IsASelectInst().IsNil():
|
||||
// var result T
|
||||
// if cond {
|
||||
// result = x
|
||||
// } else {
|
||||
// result = y
|
||||
// }
|
||||
// return result
|
||||
cond := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
x := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
y := fr.getLocal(inst.Operand(2)).(*LocalValue).Underlying
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSelect(cond, x, y, "")}
|
||||
|
||||
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 0:
|
||||
return nil, nil, nil // ret void
|
||||
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 1:
|
||||
return fr.getLocal(inst.Operand(0)), nil, nil
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 3:
|
||||
// conditional branch (if/then/else)
|
||||
cond := fr.getLocal(inst.Operand(0)).Value()
|
||||
if cond.Type() != fr.Mod.Context().Int1Type() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("expected an i1 in a branch instruction"))
|
||||
}
|
||||
thenBB := inst.Operand(1)
|
||||
elseBB := inst.Operand(2)
|
||||
if !cond.IsAInstruction().IsNil() {
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-constant"))
|
||||
}
|
||||
if !cond.IsAConstantExpr().IsNil() {
|
||||
// This may happen when the instruction builder could not
|
||||
// const-fold some instructions.
|
||||
return nil, nil, fr.errorAt(inst, errors.New("interp: branch on a non-const-propagated constant expression"))
|
||||
}
|
||||
switch cond {
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
||||
return nil, []llvm.Value{thenBB}, nil // then
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
||||
return nil, []llvm.Value{elseBB}, nil // else
|
||||
default:
|
||||
return nil, nil, fr.errorAt(inst, errors.New("branch was not true or false"))
|
||||
}
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||
// unconditional branch (goto)
|
||||
return nil, []llvm.Value{inst.Operand(0)}, nil
|
||||
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.
|
||||
// This is a sentinel error value.
|
||||
return nil, nil, errUnreachable
|
||||
|
||||
default:
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
}
|
||||
|
||||
panic("interp: reached end of basic block without terminator")
|
||||
}
|
||||
|
||||
// Get the Value for an operand, which is a constant value of some sort.
|
||||
func (fr *frame) getLocal(v llvm.Value) Value {
|
||||
if ret, ok := fr.locals[v]; ok {
|
||||
return ret
|
||||
} else if value := fr.getValue(v); value != nil {
|
||||
return value
|
||||
} else {
|
||||
// This should not happen under normal circumstances.
|
||||
panic("cannot find value")
|
||||
}
|
||||
}
|
||||
+239
-103
@@ -1,59 +1,88 @@
|
||||
// Package interp interprets Go package initializers as much as possible. This
|
||||
// avoid running them at runtime, improving code size and making other
|
||||
// optimizations possible.
|
||||
// Package interp is a partial evaluator of code run at package init time. See
|
||||
// the README in this package for details.
|
||||
package interp
|
||||
|
||||
// This file provides the overarching Eval object with associated (utility)
|
||||
// methods.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Eval struct {
|
||||
Mod llvm.Module
|
||||
TargetData llvm.TargetData
|
||||
Debug bool
|
||||
builder llvm.Builder
|
||||
dirtyGlobals map[llvm.Value]struct{}
|
||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||
// Version of the interp package. It must be incremented whenever the interp
|
||||
// package is changed in a way that affects the output so that cached package
|
||||
// builds will be invalidated.
|
||||
// This version is independent of the TinyGo version number.
|
||||
const Version = 2 // last change: fix GEP on untyped pointers
|
||||
|
||||
// Enable extra checks, which should be disabled by default.
|
||||
// This may help track down bugs by adding a few more sanity checks.
|
||||
const checks = true
|
||||
|
||||
// runner contains all state related to one interp run.
|
||||
type runner struct {
|
||||
mod llvm.Module
|
||||
targetData llvm.TargetData
|
||||
builder llvm.Builder
|
||||
pointerSize uint32 // cached pointer size from the TargetData
|
||||
i8ptrType llvm.Type // often used type so created in advance
|
||||
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
|
||||
debug bool // log debug messages
|
||||
pkgName string // package name of the currently executing package
|
||||
functionCache map[llvm.Value]*function // cache of compiled functions
|
||||
objects []object // slice of objects in memory
|
||||
globals map[llvm.Value]int // map from global to index in objects slice
|
||||
start time.Time
|
||||
callsExecuted uint64
|
||||
}
|
||||
|
||||
// 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
|
||||
func newRunner(mod llvm.Module, debug bool) *runner {
|
||||
r := runner{
|
||||
mod: mod,
|
||||
targetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
debug: debug,
|
||||
functionCache: make(map[llvm.Value]*function),
|
||||
objects: []object{{}},
|
||||
globals: make(map[llvm.Value]int),
|
||||
start: time.Now(),
|
||||
}
|
||||
r.pointerSize = uint32(r.targetData.PointerSize())
|
||||
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
|
||||
return &r
|
||||
}
|
||||
|
||||
// Run evaluates the function with the given name and then eliminates all
|
||||
// callers.
|
||||
// Run evaluates runtime.initAll function as much as possible at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
if debug {
|
||||
println("\ncompile-time evaluation:")
|
||||
}
|
||||
r := newRunner(mod, debug)
|
||||
|
||||
name := "runtime.initAll"
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
Debug: debug,
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
initAll := mod.NamedFunction("runtime.initAll")
|
||||
bb := initAll.EntryBasicBlock()
|
||||
|
||||
// Create a builder, to insert instructions that could not be evaluated at
|
||||
// compile time.
|
||||
r.builder = mod.Context().NewBuilder()
|
||||
defer r.builder.Dispose()
|
||||
|
||||
// Create a dummy alloca in the entry block that we can set the insert point
|
||||
// to. This is necessary because otherwise we might be removing the
|
||||
// instruction (init call) that we are removing after successful
|
||||
// interpretation.
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
r.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
|
||||
r.builder.SetInsertPointBefore(dummy)
|
||||
defer dummy.EraseFromParentAsInstruction()
|
||||
|
||||
// Get a list if init calls. A runtime.initAll might look something like this:
|
||||
// func initAll() {
|
||||
// unsafe.init()
|
||||
// machine.init()
|
||||
// runtime.init()
|
||||
// }
|
||||
// This function gets a list of these call instructions.
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
@@ -63,99 +92,206 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
break // ret void
|
||||
}
|
||||
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
||||
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
|
||||
return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
|
||||
}
|
||||
initCalls = append(initCalls, inst)
|
||||
}
|
||||
|
||||
// Do this in a separate step to avoid corrupting the iterator above.
|
||||
undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
|
||||
// Run initializers for each package. Once the package initializer is
|
||||
// finished, the call to the package initializer can be removed.
|
||||
for _, call := range initCalls {
|
||||
initName := call.CalledValue().Name()
|
||||
if !strings.HasSuffix(initName, ".init") {
|
||||
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
|
||||
return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
r.pkgName = initName[:len(initName)-len(".init")]
|
||||
fn := call.CalledValue()
|
||||
call.EraseFromParentAsInstruction()
|
||||
evalPkg := evalPackage{
|
||||
Eval: e,
|
||||
packagePath: pkgName,
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "call:", fn.Name())
|
||||
}
|
||||
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
|
||||
if err == errUnreachable {
|
||||
break
|
||||
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
||||
call.EraseFromParentAsInstruction()
|
||||
if callErr != nil {
|
||||
if isRecoverableError(callErr.Err) {
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
|
||||
}
|
||||
// Remove instructions that were created as part of interpreting
|
||||
// the package.
|
||||
mem.revert()
|
||||
// Create a call to the package initializer (which was
|
||||
// previously deleted).
|
||||
i8undef := llvm.Undef(r.i8ptrType)
|
||||
r.builder.CreateCall(fn, []llvm.Value{i8undef, i8undef}, "")
|
||||
// Make sure that any globals touched by the package
|
||||
// initializer, won't be accessed by later package initializers.
|
||||
r.markExternalLoad(fn)
|
||||
continue
|
||||
}
|
||||
return callErr
|
||||
}
|
||||
for index, obj := range mem.objects {
|
||||
r.objects[index] = obj
|
||||
}
|
||||
}
|
||||
r.pkgName = ""
|
||||
|
||||
// Update all global variables in the LLVM module.
|
||||
mem := memoryView{r: r}
|
||||
for i, obj := range r.objects {
|
||||
if obj.llvmGlobal.IsNil() {
|
||||
continue
|
||||
}
|
||||
if obj.buffer == nil {
|
||||
continue
|
||||
}
|
||||
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
if err == errInvalidPtrToIntSize {
|
||||
// This can happen when a previous interp run did not have the
|
||||
// correct LLVM type for a global and made something up. In that
|
||||
// case, some fields could be written out as a series of (null)
|
||||
// bytes even though they actually contain a pointer value.
|
||||
// As a fallback, use asRawValue to get something of the correct
|
||||
// memory layout.
|
||||
initializer, err := obj.buffer.asRawValue(r).rawLLVMValue(&mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
initializerType := initializer.Type()
|
||||
newGlobal := llvm.AddGlobal(mod, initializerType, obj.llvmGlobal.Name()+".tmp")
|
||||
newGlobal.SetInitializer(initializer)
|
||||
newGlobal.SetLinkage(obj.llvmGlobal.Linkage())
|
||||
newGlobal.SetAlignment(obj.llvmGlobal.Alignment())
|
||||
// TODO: copy debug info, unnamed_addr, ...
|
||||
bitcast := llvm.ConstBitCast(newGlobal, obj.llvmGlobal.Type())
|
||||
obj.llvmGlobal.ReplaceAllUsesWith(bitcast)
|
||||
name := obj.llvmGlobal.Name()
|
||||
obj.llvmGlobal.EraseFromParentAsGlobal()
|
||||
newGlobal.SetName(name)
|
||||
|
||||
// Update interp-internal references.
|
||||
delete(r.globals, obj.llvmGlobal)
|
||||
obj.llvmGlobal = newGlobal
|
||||
r.globals[newGlobal] = i
|
||||
r.objects[i] = obj
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
||||
panic("initializer type mismatch")
|
||||
}
|
||||
obj.llvmGlobal.SetInitializer(initializer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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{
|
||||
evalPackage: e,
|
||||
fn: fn,
|
||||
locals: make(map[llvm.Value]Value),
|
||||
// RunFunc evaluates a single package initializer at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func RunFunc(fn llvm.Value, debug bool) error {
|
||||
// Create and initialize *runner object.
|
||||
mod := fn.GlobalParent()
|
||||
r := newRunner(mod, debug)
|
||||
initName := fn.Name()
|
||||
if !strings.HasSuffix(initName, ".init") {
|
||||
return errorAt(fn, "interp: unexpected function name (expected *.init)")
|
||||
}
|
||||
for i, param := range fn.Params() {
|
||||
fr.locals[param] = params[i]
|
||||
r.pkgName = initName[:len(initName)-len(".init")]
|
||||
|
||||
// Create new function with the interp result.
|
||||
newFn := llvm.AddFunction(mod, fn.Name()+".tmp", fn.Type().ElementType())
|
||||
newFn.SetLinkage(fn.Linkage())
|
||||
newFn.SetVisibility(fn.Visibility())
|
||||
entry := mod.Context().AddBasicBlock(newFn, "entry")
|
||||
|
||||
// Create a builder, to insert instructions that could not be evaluated at
|
||||
// compile time.
|
||||
r.builder = mod.Context().NewBuilder()
|
||||
defer r.builder.Dispose()
|
||||
r.builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
// Copy debug information.
|
||||
subprogram := fn.Subprogram()
|
||||
if !subprogram.IsNil() {
|
||||
newFn.SetSubprogram(subprogram)
|
||||
r.builder.SetCurrentDebugLocation(subprogram.SubprogramLine(), 0, subprogram, llvm.Metadata{})
|
||||
}
|
||||
|
||||
bb := fn.EntryBasicBlock()
|
||||
var lastBB llvm.BasicBlock
|
||||
for {
|
||||
retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent)
|
||||
if outgoing == nil {
|
||||
// returned something (a value or void, or an error)
|
||||
return retval, err
|
||||
}
|
||||
if len(outgoing) > 1 {
|
||||
panic("unimplemented: multiple outgoing blocks")
|
||||
}
|
||||
next := outgoing[0]
|
||||
if next.IsABasicBlock().IsNil() {
|
||||
panic("did not switch to a basic block")
|
||||
}
|
||||
lastBB = bb
|
||||
bb = next.AsBasicBlock()
|
||||
// Run the initializer, filling the .init.tmp function.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "interp:", fn.Name())
|
||||
}
|
||||
_, pkgMem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
||||
if callErr != nil {
|
||||
if isRecoverableError(callErr.Err) {
|
||||
// Could not finish, but could recover from it.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "not interpreting", r.pkgName, "because of error:", callErr.Error())
|
||||
}
|
||||
newFn.EraseFromParentAsFunction()
|
||||
return nil
|
||||
}
|
||||
return callErr
|
||||
}
|
||||
for index, obj := range pkgMem.objects {
|
||||
r.objects[index] = obj
|
||||
}
|
||||
|
||||
// Update globals with values determined while running the initializer above.
|
||||
mem := memoryView{r: r}
|
||||
for _, obj := range r.objects {
|
||||
if obj.llvmGlobal.IsNil() {
|
||||
continue
|
||||
}
|
||||
if obj.buffer == nil {
|
||||
continue
|
||||
}
|
||||
initializer, err := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
||||
panic("initializer type mismatch")
|
||||
}
|
||||
obj.llvmGlobal.SetInitializer(initializer)
|
||||
}
|
||||
|
||||
// Finalize: remove the old init function and replace it with the new
|
||||
// (.init.tmp) function.
|
||||
r.builder.CreateRetVoid()
|
||||
fnName := fn.Name()
|
||||
fn.ReplaceAllUsesWith(newFn)
|
||||
fn.EraseFromParentAsFunction()
|
||||
newFn.SetName(fnName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getValue determines what kind of LLVM value it gets and returns the
|
||||
// appropriate Value type.
|
||||
func (e *Eval) getValue(v llvm.Value) Value {
|
||||
return &LocalValue{e, v}
|
||||
// getFunction returns the compiled version of the given LLVM function. It
|
||||
// compiles the function if necessary and caches the result.
|
||||
func (r *runner) getFunction(llvmFn llvm.Value) *function {
|
||||
if fn, ok := r.functionCache[llvmFn]; ok {
|
||||
return fn
|
||||
}
|
||||
fn := r.compileFunction(llvmFn)
|
||||
r.functionCache[llvmFn] = fn
|
||||
return fn
|
||||
}
|
||||
|
||||
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
|
||||
// when it encounters a constant GEP on a global, it marks the global dirty.
|
||||
func (e *Eval) markDirty(v llvm.Value) {
|
||||
if !v.IsAGlobalVariable().IsNil() {
|
||||
if v.IsGlobalConstant() {
|
||||
return
|
||||
// markExternalLoad marks the given llvmValue as being loaded externally. This
|
||||
// is primarily used to mark package initializers that could not be run at
|
||||
// compile time. As an example, a package initialize might store to a global
|
||||
// variable. Another package initializer might read from the same global
|
||||
// variable. By marking this function as being run at runtime, that load
|
||||
// instruction will need to be run at runtime instead of at compile time.
|
||||
func (r *runner) markExternalLoad(llvmValue llvm.Value) {
|
||||
mem := memoryView{r: r}
|
||||
mem.markExternalLoad(llvmValue)
|
||||
for index, obj := range mem.objects {
|
||||
if obj.marked > r.objects[index].marked {
|
||||
r.objects[index].marked = obj.marked
|
||||
}
|
||||
if _, ok := e.dirtyGlobals[v]; !ok {
|
||||
e.dirtyGlobals[v] = struct{}{}
|
||||
e.sideEffectFuncs = nil // re-calculate all side effects
|
||||
}
|
||||
} else if v.IsConstant() {
|
||||
if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
|
||||
// looks like a constant getelementptr of a global.
|
||||
// TODO: find a way to make sure it really is: v.Opcode() returns 0.
|
||||
e.markDirty(v.Operand(0))
|
||||
return
|
||||
}
|
||||
return // nothing to mark
|
||||
} else if !v.IsAGetElementPtrInst().IsNil() {
|
||||
panic("interp: todo: GEP")
|
||||
} else {
|
||||
// Not constant and not a global or GEP so doesn't have to be marked
|
||||
// non-constant.
|
||||
}
|
||||
}
|
||||
|
||||
+37
-3
@@ -3,6 +3,7 @@ package interp
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -12,10 +13,11 @@ import (
|
||||
func TestInterp(t *testing.T) {
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"phi",
|
||||
"slice-copy",
|
||||
"consteval",
|
||||
"map",
|
||||
"interface",
|
||||
"revert",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@@ -41,9 +43,29 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||
// Perform the transform.
|
||||
err = Run(mod, false)
|
||||
if err != nil {
|
||||
if err, match := err.(*Error); match {
|
||||
println(err.Error())
|
||||
if !err.Inst.IsNil() {
|
||||
err.Inst.Dump()
|
||||
println()
|
||||
}
|
||||
if len(err.Traceback) > 0 {
|
||||
println("\ntraceback:")
|
||||
for _, line := range err.Traceback {
|
||||
println(line.Pos.String() + ":")
|
||||
line.Inst.Dump()
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// To be sure, verify that the module is still valid.
|
||||
if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Run some cleanup passes to get easy-to-read outputs.
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
@@ -66,6 +88,8 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||
}
|
||||
}
|
||||
|
||||
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
|
||||
|
||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
@@ -75,8 +99,18 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
for i, line := range lines1 {
|
||||
if line != lines2[i] {
|
||||
for i, line1 := range lines1 {
|
||||
line2 := lines2[i]
|
||||
match1 := alignRegexp.MatchString(line1)
|
||||
match2 := alignRegexp.MatchString(line2)
|
||||
if match1 != match2 {
|
||||
// Only one of the lines has the align keyword. Remove it.
|
||||
// This is a change to make the test work in both LLVM 10 and LLVM
|
||||
// 11 (LLVM 11 appears to automatically add alignment everywhere).
|
||||
line1 = alignRegexp.ReplaceAllString(line1, "")
|
||||
line2 = alignRegexp.ReplaceAllString(line2, "")
|
||||
}
|
||||
if line1 != line2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1211
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user