mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
357 Commits
v0.16.0
...
file-embed
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b6b2bfc59 | |||
| 778c79cc85 | |||
| 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 |
+59
-116
@@ -68,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:
|
||||
@@ -108,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
|
||||
@@ -132,6 +135,7 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
@@ -153,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
|
||||
@@ -179,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:
|
||||
@@ -193,6 +203,7 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
@@ -214,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
|
||||
@@ -274,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.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.16.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:
|
||||
@@ -340,78 +359,12 @@ 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
|
||||
@@ -430,6 +383,12 @@ jobs:
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "11"
|
||||
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
|
||||
@@ -442,35 +401,19 @@ jobs:
|
||||
- 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-llvm11-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,45 @@
|
||||
name: CI for tinygo-dev docker container
|
||||
on:
|
||||
push:
|
||||
branches: [ dev ]
|
||||
|
||||
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.13+)
|
||||
* 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
|
||||
|
||||
+278
@@ -1,3 +1,281 @@
|
||||
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
|
||||
---
|
||||
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 10 and the TinyGo compiler itself.
|
||||
FROM golang:1.15 AS tinygo-base
|
||||
# TinyGo base stage installs the most recent Go 1.16.x, LLVM 11 and the TinyGo compiler itself.
|
||||
FROM golang:1.16 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/buster/ llvm-toolchain-buster-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.
|
||||
|
||||
@@ -36,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
|
||||
|
||||
@@ -48,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) -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/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
|
||||
@@ -140,23 +147,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
|
||||
@@ -173,25 +183,30 @@ 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 \
|
||||
encoding \
|
||||
encoding/ascii85 \
|
||||
encoding/base32 \
|
||||
encoding/hex \
|
||||
hash/adler32 \
|
||||
hash/fnv \
|
||||
hash/crc64 \
|
||||
math \
|
||||
math/cmplx \
|
||||
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/heap
|
||||
$(TINYGO) test container/list
|
||||
$(TINYGO) test container/ring
|
||||
$(TINYGO) test crypto/des
|
||||
$(TINYGO) test encoding/ascii85
|
||||
$(TINYGO) test encoding/base32
|
||||
$(TINYGO) test encoding/hex
|
||||
$(TINYGO) test hash/fnv
|
||||
$(TINYGO) test hash/crc64
|
||||
$(TINYGO) test math
|
||||
$(TINYGO) test text/scanner
|
||||
$(TINYGO) test unicode/utf8
|
||||
$(TINYGO) test $(TEST_PACKAGES)
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
@@ -215,6 +230,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
|
||||
@@ -245,12 +262,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
|
||||
@@ -259,6 +278,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
|
||||
@@ -267,14 +290,6 @@ 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
|
||||
@@ -283,6 +298,8 @@ smoketest:
|
||||
@$(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
|
||||
@@ -299,8 +316,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
|
||||
@@ -321,14 +336,36 @@ 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/blinky1
|
||||
$(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=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
|
||||
@@ -336,8 +373,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
|
||||
@@ -347,6 +408,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
|
||||
@@ -373,8 +438,13 @@ 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
|
||||
|
||||
|
||||
wasmtest:
|
||||
$(GO) test ./tests/wasm
|
||||
|
||||
@@ -43,50 +43,74 @@ 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 68 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 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/)
|
||||
* [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 +172,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.16'
|
||||
- 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
|
||||
|
||||
+764
-245
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
|
||||
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
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)
|
||||
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 < 13 || minor > 16 {
|
||||
return nil, fmt.Errorf("requires go version 1.13 through 1.16, 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
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
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 all the jobs indicated in the jobs slice and returns the error
|
||||
// of the first job that fails to run.
|
||||
// It runs all jobs in the order of the slice, as long as all dependencies have
|
||||
// already run. Therefore, if some jobs are preferred to run before others, they
|
||||
// should be ordered as such in this slice.
|
||||
func runJobs(jobs []*compileJob) error {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
+73
-29
@@ -1,7 +1,6 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -40,34 +39,62 @@ 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
|
||||
}
|
||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||
err = runJobs(jobs)
|
||||
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 if the job and
|
||||
// job.dependencies have 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 +105,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
|
||||
}
|
||||
+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
-4
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
@@ -23,7 +24,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)
|
||||
actual := strings.ReplaceAll(result, "\r\n", "\n")
|
||||
|
||||
// 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
|
||||
@@ -41,6 +42,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 +65,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 +113,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 +154,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,!llvm11
|
||||
// +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 llvm11
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#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"
|
||||
@@ -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
+5
@@ -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
|
||||
|
||||
+77
-48
@@ -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", "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,7 +89,7 @@ func (c *Config) NeedsStackObjects() bool {
|
||||
switch c.GC() {
|
||||
case "conservative", "extalloc":
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "wasm" {
|
||||
if tag == "tinygo.wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -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,12 +188,21 @@ 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")
|
||||
@@ -198,17 +221,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)
|
||||
}
|
||||
@@ -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"
|
||||
@@ -303,6 +325,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)
|
||||
}
|
||||
@@ -338,6 +363,10 @@ func (c *Config) WasmAbi() string {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
+39
-12
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
@@ -30,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"`
|
||||
@@ -40,10 +41,12 @@ type TargetSpec struct {
|
||||
LDFlags []string `json:"ldflags"`
|
||||
LinkerScript string `json:"linkerscript"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
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"`
|
||||
@@ -52,6 +55,7 @@ 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"`
|
||||
@@ -228,31 +232,40 @@ 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",
|
||||
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 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 = "gdb-multiarch"
|
||||
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"
|
||||
@@ -270,3 +283,17 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
}
|
||||
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, ", ") + ")")
|
||||
}
|
||||
|
||||
+13
-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
|
||||
@@ -104,7 +104,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
||||
// 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 +159,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 +196,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 +219,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, "")
|
||||
|
||||
+1
-2
@@ -79,8 +79,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}, "")
|
||||
}
|
||||
|
||||
|
||||
+545
-471
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"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")
|
||||
|
||||
// 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 := []struct {
|
||||
file string
|
||||
target string
|
||||
}{
|
||||
{"basic.go", ""},
|
||||
{"pointer.go", ""},
|
||||
{"slice.go", ""},
|
||||
{"string.go", ""},
|
||||
{"float.go", ""},
|
||||
{"interface.go", ""},
|
||||
{"func.go", ""},
|
||||
{"pragma.go", ""},
|
||||
{"goroutine.go", "wasm"},
|
||||
{"goroutine.go", "cortex-m-qemu"},
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// parseGoEmbed parses the text following "//go:embed" to extract the glob patterns.
|
||||
// It accepts unquoted space-separated patterns as well as double-quoted and back-quoted Go strings.
|
||||
// Copied from https://github.com/golang/go/blob/219fe9d547d09d3de1b024c6c8b8314dd0bf12e4/src/cmd/compile/internal/noder/noder.go#L1717-L1776
|
||||
func parseGoEmbed(args string) ([]string, error) {
|
||||
var list []string
|
||||
for args = strings.TrimSpace(args); args != ""; args = strings.TrimSpace(args) {
|
||||
var path string
|
||||
Switch:
|
||||
switch args[0] {
|
||||
default:
|
||||
i := len(args)
|
||||
for j, c := range args {
|
||||
if unicode.IsSpace(c) {
|
||||
i = j
|
||||
break
|
||||
}
|
||||
}
|
||||
path = args[:i]
|
||||
args = args[i:]
|
||||
|
||||
case '`':
|
||||
i := strings.Index(args[1:], "`")
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
|
||||
}
|
||||
path = args[1 : 1+i]
|
||||
args = args[1+i+1:]
|
||||
|
||||
case '"':
|
||||
i := 1
|
||||
for ; i < len(args); i++ {
|
||||
if args[i] == '\\' {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
if args[i] == '"' {
|
||||
q, err := strconv.Unquote(args[:i+1])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args[:i+1])
|
||||
}
|
||||
path = q
|
||||
args = args[i+1:]
|
||||
break Switch
|
||||
}
|
||||
}
|
||||
if i >= len(args) {
|
||||
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
|
||||
}
|
||||
}
|
||||
|
||||
if args != "" {
|
||||
r, _ := utf8.DecodeRuneInString(args)
|
||||
if !unicode.IsSpace(r) {
|
||||
return nil, fmt.Errorf("invalid quoted string in //go:embed: %s", args)
|
||||
}
|
||||
}
|
||||
list = append(list, path)
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
+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.
|
||||
|
||||
+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)
|
||||
|
||||
+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())
|
||||
}
|
||||
|
||||
+362
-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,28 @@ type globalInfo struct {
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
align int // go:align
|
||||
section string // go:section
|
||||
embeds string // go:embed
|
||||
}
|
||||
|
||||
// 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 +370,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 +445,33 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
if err == nil {
|
||||
info.align = align
|
||||
}
|
||||
case "//go:section":
|
||||
if len(parts) == 2 {
|
||||
info.section = parts[1]
|
||||
}
|
||||
case "//go:embed":
|
||||
raw := strings.TrimPrefix(comment.Text, "//go:embed")
|
||||
info.embeds += " " + raw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
+57
@@ -0,0 +1,57 @@
|
||||
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)
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
; ModuleID = 'basic.go'
|
||||
source_filename = "basic.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.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
|
||||
}
|
||||
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
|
||||
}
|
||||
+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*)
|
||||
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
+25
@@ -0,0 +1,25 @@
|
||||
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)
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
; 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*)
|
||||
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.16
|
||||
|
||||
require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b
|
||||
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e
|
||||
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
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
go.bug.st/serial v1.1.2
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
|
||||
tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9
|
||||
tinygo.org/x/go-llvm v0.0.0-20210325115028-e7b85195e81c
|
||||
)
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
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/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
||||
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b h1:LF+GRwyzxrO3MUzPvejv+yBup0lNG+/QdIRrkxOPseA=
|
||||
github.com/chromedp/cdproto v0.0.0-20200709115526-d1f6fc58448b/go.mod h1:E6LPWRdIJc11h/di5p0rwvRmUYbhGpBEH7ZbPfzDIOE=
|
||||
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e h1:Hv0JVyHhbIXb9NiYQe4NsrfgrSofAp0q2FnhhJOXgi8=
|
||||
github.com/chromedp/chromedp v0.5.4-0.20200303084119-2bb39134ab9e/go.mod h1:vmQMRHFZrY3T+Jv51T0n87OK/i6bK+5P9a+Fg5jPwgQ=
|
||||
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.3 h1:ZOigqf7iBxkA4jdQ3am7ATzdlOFp9YzA6NmuvEEZc9g=
|
||||
github.com/gobwas/ws v1.0.3/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/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
|
||||
github.com/mailru/easyjson v0.7.1/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=
|
||||
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=
|
||||
@@ -39,8 +45,11 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
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-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
@@ -48,5 +57,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbO
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/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-20201104183921-570e7a6841d9 h1:l2kTQOhqEoeDTK3ckUnwReOQwMPUmURMIdjJbeAuDT4=
|
||||
tinygo.org/x/go-llvm v0.0.0-20201104183921-570e7a6841d9/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
|
||||
}
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
// Version of TinyGo.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.16.0"
|
||||
const Version = "0.20.0-dev"
|
||||
|
||||
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
|
||||
+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.
|
||||
}
|
||||
}
|
||||
|
||||
+22
-1
@@ -13,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) {
|
||||
@@ -42,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()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1211
File diff suppressed because it is too large
Load Diff
-259
@@ -1,259 +0,0 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type sideEffectSeverity int
|
||||
|
||||
func (severity sideEffectSeverity) String() string {
|
||||
switch severity {
|
||||
case sideEffectInProgress:
|
||||
return "in progress"
|
||||
case sideEffectNone:
|
||||
return "none"
|
||||
case sideEffectLimited:
|
||||
return "limited"
|
||||
case sideEffectAll:
|
||||
return "all"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
sideEffectInProgress sideEffectSeverity = iota // computing side effects is in progress (for recursive functions)
|
||||
sideEffectNone // no side effects at all (pure)
|
||||
sideEffectLimited // has side effects, but the effects are known
|
||||
sideEffectAll // has unknown side effects
|
||||
)
|
||||
|
||||
// sideEffectResult contains the scan results after scanning a function for side
|
||||
// effects (recursively).
|
||||
type sideEffectResult struct {
|
||||
severity sideEffectSeverity
|
||||
mentionsGlobals map[llvm.Value]struct{}
|
||||
}
|
||||
|
||||
// hasSideEffects scans this function and all descendants, recursively. It
|
||||
// returns whether this function has side effects and if it does, which globals
|
||||
// it mentions anywhere in this function or any called functions.
|
||||
func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error) {
|
||||
name := fn.Name()
|
||||
switch {
|
||||
case name == "runtime.alloc":
|
||||
// Cannot be scanned but can be interpreted.
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.nanotime":
|
||||
// Fixed value at compile time.
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime._panic":
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
case name == "runtime.typeAssert":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.interfaceImplements":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.sliceCopy":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "runtime.trackPointer":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "llvm.dbg.value":
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
case name == "(*sync/atomic.Value).Load" || name == "(*sync/atomic.Value).Store":
|
||||
// These functions do some unsafe pointer loading/storing but are
|
||||
// otherwise safe.
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
case strings.HasPrefix(name, "llvm.lifetime."):
|
||||
return &sideEffectResult{severity: sideEffectNone}, nil
|
||||
}
|
||||
if fn.IsDeclaration() {
|
||||
return &sideEffectResult{severity: sideEffectLimited}, nil
|
||||
}
|
||||
if e.sideEffectFuncs == nil {
|
||||
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
|
||||
}
|
||||
if se, ok := e.sideEffectFuncs[fn]; ok {
|
||||
return se, nil
|
||||
}
|
||||
result := &sideEffectResult{
|
||||
severity: sideEffectInProgress,
|
||||
mentionsGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.sideEffectFuncs[fn] = result
|
||||
dirtyLocals := map[llvm.Value]struct{}{}
|
||||
for bb := fn.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsAInstruction().IsNil() {
|
||||
// Should not happen in valid IR.
|
||||
panic("not an instruction")
|
||||
}
|
||||
|
||||
// Check for any globals mentioned anywhere in the function. Assume
|
||||
// any mentioned globals may be read from or written to when
|
||||
// executed, thus must be marked dirty with a call.
|
||||
for i := 0; i < inst.OperandsCount(); i++ {
|
||||
operand := inst.Operand(i)
|
||||
if !operand.IsAGlobalVariable().IsNil() {
|
||||
result.mentionsGlobals[operand] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
switch inst.InstructionOpcode() {
|
||||
case llvm.IndirectBr, llvm.Invoke:
|
||||
// Not emitted by the compiler.
|
||||
return nil, e.errorAt(inst, errors.New("unknown instructions"))
|
||||
case llvm.Call:
|
||||
child := inst.CalledValue()
|
||||
if !child.IsAInlineAsm().IsNil() {
|
||||
// Inline assembly. This most likely has side effects.
|
||||
// Assume they're only limited side effects, similar to
|
||||
// external function calls.
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
continue
|
||||
}
|
||||
if child.IsAFunction().IsNil() {
|
||||
// Indirect call?
|
||||
// In any case, we can't know anything here about what it
|
||||
// affects exactly so mark this function as invoking all
|
||||
// possible side effects.
|
||||
result.updateSeverity(sideEffectAll)
|
||||
continue
|
||||
}
|
||||
if child.IsDeclaration() {
|
||||
// External function call. Assume only limited side effects
|
||||
// (no affected globals, etc.).
|
||||
switch child.Name() {
|
||||
case "runtime.alloc":
|
||||
continue
|
||||
case "runtime.typeAssert":
|
||||
continue // implemented in interp
|
||||
case "runtime.interfaceImplements":
|
||||
continue // implemented in interp
|
||||
}
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
continue
|
||||
}
|
||||
childSideEffects, err := e.hasSideEffects(child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch childSideEffects.severity {
|
||||
case sideEffectInProgress, sideEffectNone:
|
||||
// no side effects or recursive function - continue scanning
|
||||
case sideEffectLimited:
|
||||
// The return value may be problematic.
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
case sideEffectAll:
|
||||
result.updateSeverity(sideEffectAll)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
case llvm.Load:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
if _, ok := e.dirtyGlobals[inst.Operand(0)]; ok {
|
||||
if e.hasLocalSideEffects(dirtyLocals, inst) {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
}
|
||||
case llvm.Store:
|
||||
if inst.IsVolatile() {
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
}
|
||||
case llvm.IntToPtr:
|
||||
// Pointer casts are not yet supported.
|
||||
result.updateSeverity(sideEffectLimited)
|
||||
default:
|
||||
// Ignore most instructions.
|
||||
// Check this list for completeness:
|
||||
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if result.severity == sideEffectInProgress {
|
||||
// No side effect was reported for this function.
|
||||
result.severity = sideEffectNone
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// hasLocalSideEffects checks whether the given instruction flows into a branch
|
||||
// or return instruction, in which case the whole function must be marked as
|
||||
// having side effects and be called at runtime.
|
||||
func (e *Eval) hasLocalSideEffects(dirtyLocals map[llvm.Value]struct{}, inst llvm.Value) bool {
|
||||
if _, ok := dirtyLocals[inst]; ok {
|
||||
// It is already known that this local is dirty.
|
||||
return true
|
||||
}
|
||||
|
||||
for use := inst.FirstUse(); !use.IsNil(); use = use.NextUse() {
|
||||
user := use.User()
|
||||
if user.IsAInstruction().IsNil() {
|
||||
// Should not happen in valid IR.
|
||||
panic("user not an instruction")
|
||||
}
|
||||
switch user.InstructionOpcode() {
|
||||
case llvm.Br, llvm.Switch:
|
||||
// A branch on a dirty value makes this function dirty: it cannot be
|
||||
// interpreted at compile time so has to be run at runtime. It is
|
||||
// marked as having side effects for this reason.
|
||||
return true
|
||||
case llvm.Ret:
|
||||
// This function returns a dirty value so it is itself marked as
|
||||
// dirty to make sure it is called at runtime.
|
||||
return true
|
||||
case llvm.Store:
|
||||
ptr := user.Operand(1)
|
||||
if !ptr.IsAGlobalVariable().IsNil() {
|
||||
// Store to a global variable.
|
||||
// Already handled in (*Eval).hasSideEffects.
|
||||
continue
|
||||
}
|
||||
// This store might affect all kinds of values. While it is
|
||||
// certainly possible to traverse through all of them, the easiest
|
||||
// option right now is to just assume the worst and say that this
|
||||
// function has side effects.
|
||||
// TODO: traverse through all stores and mark all relevant allocas /
|
||||
// globals dirty.
|
||||
return true
|
||||
default:
|
||||
// All instructions that take 0 or more operands (1 or more if it
|
||||
// was a use) and produce a result.
|
||||
// For a list:
|
||||
// https://godoc.org/github.com/llvm-mirror/llvm/bindings/go/llvm#Opcode
|
||||
dirtyLocals[user] = struct{}{}
|
||||
if e.hasLocalSideEffects(dirtyLocals, user) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No side effects found.
|
||||
return false
|
||||
}
|
||||
|
||||
// updateSeverity sets r.severity to the max of r.severity and severity,
|
||||
// conservatively assuming the worst severity.
|
||||
func (r *sideEffectResult) updateSeverity(severity sideEffectSeverity) {
|
||||
if severity > r.severity {
|
||||
r.severity = severity
|
||||
}
|
||||
}
|
||||
|
||||
// updateSeverity updates the severity with the severity of the child severity,
|
||||
// like in a function call. This means it also copies the mentioned globals.
|
||||
func (r *sideEffectResult) update(child *sideEffectResult) {
|
||||
r.updateSeverity(child.severity)
|
||||
for global := range child.mentionsGlobals {
|
||||
r.mentionsGlobals[global] = struct{}{}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
var scanTestTable = []struct {
|
||||
name string
|
||||
severity sideEffectSeverity
|
||||
mentionsGlobals []string
|
||||
}{
|
||||
{"returnsConst", sideEffectNone, nil},
|
||||
{"returnsArg", sideEffectNone, nil},
|
||||
{"externalCallOnly", sideEffectNone, nil},
|
||||
{"externalCallAndReturn", sideEffectLimited, nil},
|
||||
{"externalCallBranch", sideEffectLimited, nil},
|
||||
{"readCleanGlobal", sideEffectNone, []string{"cleanGlobalInt"}},
|
||||
{"readDirtyGlobal", sideEffectLimited, []string{"dirtyGlobalInt"}},
|
||||
{"callFunctionPointer", sideEffectAll, []string{"functionPointer"}},
|
||||
{"getDirtyPointer", sideEffectLimited, nil},
|
||||
{"storeToPointer", sideEffectLimited, nil},
|
||||
{"callTypeAssert", sideEffectNone, nil},
|
||||
{"callInterfaceImplements", sideEffectNone, nil},
|
||||
}
|
||||
|
||||
func TestScan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Read the input IR.
|
||||
path := "testdata/scan.ll"
|
||||
ctx := llvm.NewContext()
|
||||
buf, err := llvm.NewMemoryBufferFromFile(path)
|
||||
os.Stat(path) // make sure this file is tracked by `go test` caching
|
||||
if err != nil {
|
||||
t.Fatalf("could not read file %s: %v", path, err)
|
||||
}
|
||||
mod, err := ctx.ParseIR(buf)
|
||||
if err != nil {
|
||||
t.Fatalf("could not load module:\n%v", err)
|
||||
}
|
||||
|
||||
// Check all to-be-tested functions.
|
||||
for _, tc := range scanTestTable {
|
||||
// Create an eval object, for testing.
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
|
||||
// Mark some globals dirty, for testing.
|
||||
e.markDirty(mod.NamedGlobal("dirtyGlobalInt"))
|
||||
|
||||
// Scan for side effects.
|
||||
fn := mod.NamedFunction(tc.name)
|
||||
if fn.IsNil() {
|
||||
t.Errorf("scan test: could not find tested function %s in the IR", tc.name)
|
||||
continue
|
||||
}
|
||||
evalPkg := &evalPackage{e, "testdata"}
|
||||
result, err := evalPkg.hasSideEffects(fn)
|
||||
if err != nil {
|
||||
t.Errorf("scan test: failed to scan %s for side effects: %v", fn.Name(), err)
|
||||
}
|
||||
|
||||
// Check whether the result is what we expect.
|
||||
if result.severity != tc.severity {
|
||||
t.Errorf("scan test: function %s should have severity %s but it has %s", tc.name, tc.severity, result.severity)
|
||||
}
|
||||
|
||||
// Check whether the mentioned globals match with what we'd expect.
|
||||
mentionsGlobalNames := make([]string, 0, len(result.mentionsGlobals))
|
||||
for global := range result.mentionsGlobals {
|
||||
mentionsGlobalNames = append(mentionsGlobalNames, global.Name())
|
||||
}
|
||||
sort.Strings(mentionsGlobalNames)
|
||||
globalsMismatch := false
|
||||
if len(result.mentionsGlobals) != len(tc.mentionsGlobals) {
|
||||
globalsMismatch = true
|
||||
} else {
|
||||
for i, globalName := range mentionsGlobalNames {
|
||||
if tc.mentionsGlobals[i] != globalName {
|
||||
globalsMismatch = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if globalsMismatch {
|
||||
t.Errorf("scan test: expected %s to mention globals %v, but it mentions globals %v", tc.name, tc.mentionsGlobals, mentionsGlobalNames)
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+76
@@ -4,6 +4,11 @@ target triple = "x86_64--linux"
|
||||
@main.v1 = internal global i64 0
|
||||
@main.nonConst1 = global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = global i64 0
|
||||
@main.someArray = global [8 x {i16, i32}] zeroinitializer
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = global i16 0
|
||||
@main.insertedValue = global {i8, i32, {float, {i64, i16}}} zeroinitializer
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
@@ -47,6 +52,36 @@ entry:
|
||||
%value2 = load i64, i64* %gep2
|
||||
store i64 %value2, i64* @main.nonConst2
|
||||
|
||||
; Test that the following GEP works:
|
||||
; var someArray
|
||||
; modifyExternal(&someArray[3].field1)
|
||||
%gep3 = getelementptr [8 x {i16, i32}], [8 x {i16, i32}]* @main.someArray, i32 0, i32 3, i32 1
|
||||
call void @modifyExternal(i32* %gep3)
|
||||
|
||||
; Test that marking a value as external also marks all referenced values.
|
||||
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
|
||||
store i16 5, i16* @main.exposedValue1
|
||||
|
||||
; Test that this even propagates through functions.
|
||||
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
|
||||
store i16 7, i16* @main.exposedValue2
|
||||
|
||||
; Test that inline assembly is ignored.
|
||||
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
|
||||
|
||||
; Test switch statement.
|
||||
%switch1 = call i64 @testSwitch(i64 1) ; 1 returns 6
|
||||
%switch2 = call i64 @testSwitch(i64 9) ; 9 returns the default value -1
|
||||
call void @runtime.printint64(i64 %switch1)
|
||||
call void @runtime.printint64(i64 %switch2)
|
||||
|
||||
; Test extractvalue/insertvalue with multiple operands.
|
||||
%agg = call {i8, i32, {float, {i64, i16}}} @nestedStruct()
|
||||
%elt = extractvalue {i8, i32, {float, {i64, i16}}} %agg, 2, 1, 0
|
||||
call void @runtime.printint64(i64 %elt)
|
||||
%agg2 = insertvalue {i8, i32, {float, {i64, i16}}} %agg, i64 5, 2, 1, 0
|
||||
store {i8, i32, {float, {i64, i16}}} %agg2, {i8, i32, {float, {i64, i16}}}* @main.insertedValue
|
||||
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -58,3 +93,44 @@ entry:
|
||||
}
|
||||
|
||||
declare i64 @someValue()
|
||||
|
||||
declare void @modifyExternal(i32*)
|
||||
|
||||
; This function will modify an external value. By passing this function as a
|
||||
; function pointer to an external function, @main.exposedValue2 should be
|
||||
; marked as external.
|
||||
define void @willModifyGlobal() {
|
||||
entry:
|
||||
store i16 8, i16* @main.exposedValue2
|
||||
ret void
|
||||
}
|
||||
|
||||
; Inline assembly should be ignored in the interp package. While it is possible
|
||||
; to modify other globals that way, usually that's not the case and there is no
|
||||
; real way to check.
|
||||
define void @hasInlineAsm() {
|
||||
entry:
|
||||
call void asm sideeffect "", ""()
|
||||
ret void
|
||||
}
|
||||
|
||||
define i64 @testSwitch(i64 %val) {
|
||||
entry:
|
||||
; Test switch statement.
|
||||
switch i64 %val, label %otherwise [ i64 0, label %zero
|
||||
i64 1, label %one
|
||||
i64 2, label %two ]
|
||||
zero:
|
||||
ret i64 5
|
||||
|
||||
one:
|
||||
ret i64 6
|
||||
|
||||
two:
|
||||
ret i64 7
|
||||
|
||||
otherwise:
|
||||
ret i64 -1
|
||||
}
|
||||
|
||||
declare {i8, i32, {float, {i64, i16}}} @nestedStruct()
|
||||
|
||||
Vendored
+61
@@ -3,6 +3,11 @@ target triple = "x86_64--linux"
|
||||
|
||||
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
||||
@main.nonConst2 = local_unnamed_addr global i64 0
|
||||
@main.someArray = global [8 x { i16, i32 }] zeroinitializer
|
||||
@main.exportedValue = global [1 x i16*] [i16* @main.exposedValue1]
|
||||
@main.exposedValue1 = global i16 0
|
||||
@main.exposedValue2 = local_unnamed_addr global i16 0
|
||||
@main.insertedValue = local_unnamed_addr global { i8, i32, { float, { i64, i16 } } } zeroinitializer
|
||||
|
||||
declare void @runtime.printint64(i64) unnamed_addr
|
||||
|
||||
@@ -16,6 +21,25 @@ entry:
|
||||
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||
store i64 %value2, i64* @main.nonConst2
|
||||
call void @modifyExternal(i32* getelementptr inbounds ([8 x { i16, i32 }], [8 x { i16, i32 }]* @main.someArray, i32 0, i32 3, i32 1))
|
||||
call void @modifyExternal(i32* bitcast ([1 x i16*]* @main.exportedValue to i32*))
|
||||
store i16 5, i16* @main.exposedValue1
|
||||
call void @modifyExternal(i32* bitcast (void ()* @willModifyGlobal to i32*))
|
||||
store i16 7, i16* @main.exposedValue2
|
||||
call void @modifyExternal(i32* bitcast (void ()* @hasInlineAsm to i32*))
|
||||
call void @runtime.printint64(i64 6)
|
||||
call void @runtime.printint64(i64 -1)
|
||||
%agg = call { i8, i32, { float, { i64, i16 } } } @nestedStruct()
|
||||
%elt.agg = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
|
||||
%elt.agg1 = extractvalue { float, { i64, i16 } } %elt.agg, 1
|
||||
%elt = extractvalue { i64, i16 } %elt.agg1, 0
|
||||
call void @runtime.printint64(i64 %elt)
|
||||
%agg2.agg0 = extractvalue { i8, i32, { float, { i64, i16 } } } %agg, 2
|
||||
%agg2.agg1 = extractvalue { float, { i64, i16 } } %agg2.agg0, 1
|
||||
%agg2.insertvalue2 = insertvalue { i64, i16 } %agg2.agg1, i64 5, 0
|
||||
%agg2.insertvalue1 = insertvalue { float, { i64, i16 } } %agg2.agg0, { i64, i16 } %agg2.insertvalue2, 1
|
||||
%agg2.insertvalue0 = insertvalue { i8, i32, { float, { i64, i16 } } } %agg, { float, { i64, i16 } } %agg2.insertvalue1, 2
|
||||
store { i8, i32, { float, { i64, i16 } } } %agg2.insertvalue0, { i8, i32, { float, { i64, i16 } } }* @main.insertedValue
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -27,3 +51,40 @@ entry:
|
||||
}
|
||||
|
||||
declare i64 @someValue() local_unnamed_addr
|
||||
|
||||
declare void @modifyExternal(i32*) local_unnamed_addr
|
||||
|
||||
define void @willModifyGlobal() {
|
||||
entry:
|
||||
store i16 8, i16* @main.exposedValue2
|
||||
ret void
|
||||
}
|
||||
|
||||
define void @hasInlineAsm() {
|
||||
entry:
|
||||
call void asm sideeffect "", ""()
|
||||
ret void
|
||||
}
|
||||
|
||||
define i64 @testSwitch(i64 %val) local_unnamed_addr {
|
||||
entry:
|
||||
switch i64 %val, label %otherwise [
|
||||
i64 0, label %zero
|
||||
i64 1, label %one
|
||||
i64 2, label %two
|
||||
]
|
||||
|
||||
zero: ; preds = %entry
|
||||
ret i64 5
|
||||
|
||||
one: ; preds = %entry
|
||||
ret i64 6
|
||||
|
||||
two: ; preds = %entry
|
||||
ret i64 7
|
||||
|
||||
otherwise: ; preds = %entry
|
||||
ret i64 -1
|
||||
}
|
||||
|
||||
declare { i8, i32, { float, { i64, i16 } } } @nestedStruct() local_unnamed_addr
|
||||
|
||||
Vendored
+5
-6
@@ -1,17 +1,16 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i64 }
|
||||
%runtime.typeInInterface = type { %runtime.typecodeID*, %runtime.interfaceMethodInfo* }
|
||||
|
||||
@main.v1 = global i1 0
|
||||
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0 }
|
||||
@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null }
|
||||
@"reflect/types.typeid:named:main.foo" = external constant i8
|
||||
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
|
||||
@"typeInInterface:reflect/types.type:named:main.foo" = private constant %runtime.typeInInterface { %runtime.typecodeID* @"reflect/types.type:named:main.foo", %runtime.interfaceMethodInfo* null }
|
||||
|
||||
|
||||
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
|
||||
declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
@@ -22,7 +21,7 @@ entry:
|
||||
define internal void @main.init() unnamed_addr {
|
||||
entry:
|
||||
; Test type asserts.
|
||||
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typeInInterface* @"typeInInterface:reflect/types.type:named:main.foo" to i64), %runtime.typecodeID* @"reflect/types.type:named:main.foo", i8* undef, i8* null)
|
||||
%typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null)
|
||||
store i1 %typecode, i1* @main.v1
|
||||
ret void
|
||||
}
|
||||
|
||||
Vendored
-76
@@ -1,76 +0,0 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime._string = type { i8*, i32 }
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
|
||||
@main.m = global %runtime.hashmap* null
|
||||
@main.binaryMap = global %runtime.hashmap* null
|
||||
@main.stringMap = global %runtime.hashmap* null
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
|
||||
declare %runtime.hashmap* @runtime.hashmapMake(i8, i8, i32, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8* %context, i8* %parentHandle)
|
||||
declare void @llvm.lifetime.end.p0i8(i64, i8*)
|
||||
declare void @llvm.lifetime.start.p0i8(i64, i8*)
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @main.init(i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
; Test that hashmap optimizations generally work (even with lifetimes).
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca %runtime._string
|
||||
%0 = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 8, i32 1, i8* undef, i8* null)
|
||||
%hashmap.value.bitcast = bitcast %runtime._string* %hashmap.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string* %hashmap.value
|
||||
call void @llvm.lifetime.start.p0i8(i64 1, i8* %hashmap.key)
|
||||
store i8 1, i8* %hashmap.key
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %0, i8* %hashmap.key, i8* %hashmap.value.bitcast, i8* undef, i8* null)
|
||||
call void @llvm.lifetime.end.p0i8(i64 1, i8* %hashmap.key)
|
||||
call void @llvm.lifetime.end.p0i8(i64 8, i8* %hashmap.value.bitcast)
|
||||
store %runtime.hashmap* %0, %runtime.hashmap** @main.m
|
||||
|
||||
; Other tests, that can be done in a separate function.
|
||||
call void @main.testNonConstantBinarySet()
|
||||
call void @main.testNonConstantStringSet()
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with binary keys).
|
||||
define internal void @main.testNonConstantBinarySet() {
|
||||
%hashmap.key = alloca i8
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapBinarySet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 1, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
|
||||
; Do the binary set to the newly loaded map.
|
||||
store i8 1, i8* %hashmap.key
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* %map, i8* %hashmap.key, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Test that a map loaded from a global can still be used for mapassign
|
||||
; operations (with string keys).
|
||||
define internal void @main.testNonConstantStringSet() {
|
||||
%hashmap.value = alloca i8
|
||||
; Create hashmap from global. This breaks the normal hashmapStringSet
|
||||
; optimization, to test the fallback.
|
||||
%map.new = call %runtime.hashmap* @runtime.hashmapMake(i8 8, i8 1, i32 1, i8* undef, i8* null)
|
||||
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
|
||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
|
||||
; Do the string set to the newly loaded map.
|
||||
store i8 2, i8* %hashmap.value
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* %map, i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* %hashmap.value, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
Vendored
-28
@@ -1,28 +0,0 @@
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv6m-none-eabi"
|
||||
|
||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
||||
%runtime._string = type { i8*, i32 }
|
||||
|
||||
@main.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
|
||||
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
|
||||
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
|
||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||
@"main$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, [8 x i8] c"\01\00\00\00\00\00\00\00", [8 x %runtime._string] [%runtime._string { i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7 }, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer, %runtime._string zeroinitializer] }
|
||||
@"main$map" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }, { [8 x i8], i8*, [8 x i8], [8 x %runtime._string] }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
|
||||
@"main$alloca.2" = internal global i8 1
|
||||
@"main$alloca.3" = internal global i8 2
|
||||
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
|
||||
@"main$alloca.5" = internal global i8 2
|
||||
@"main$map.6" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 8, i8 1, i8 0 }
|
||||
|
||||
declare void @runtime.hashmapBinarySet(%runtime.hashmap*, i8*, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
declare void @runtime.hashmapStringSet(%runtime.hashmap*, i8*, i32, i8*, i8*, i8*) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @runtime.hashmapBinarySet(%runtime.hashmap* @"main$map.4", i8* @"main$alloca.2", i8* @"main$alloca.3", i8* undef, i8* null)
|
||||
call void @runtime.hashmapStringSet(%runtime.hashmap* @"main$map.6", i8* getelementptr inbounds ([7 x i8], [7 x i8]* @main.init.string, i32 0, i32 0), i32 7, i8* @"main$alloca.5", i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
Vendored
+31
@@ -0,0 +1,31 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.phiNodesResultA = global i8 0
|
||||
@main.phiNodesResultB = global i8 0
|
||||
|
||||
define void @runtime.initAll() {
|
||||
call void @main.init()
|
||||
ret void
|
||||
}
|
||||
|
||||
; PHI nodes always use the value from the previous block, even in a loop. This
|
||||
; means that the loop below should swap the values %a and %b on each iteration.
|
||||
; Previously there was a bug which resulted in %b getting the value 3 on the
|
||||
; second iteration while it should have gotten 1 (from the first iteration of
|
||||
; %for.loop).
|
||||
define internal void @main.init() {
|
||||
entry:
|
||||
br label %for.loop
|
||||
|
||||
for.loop:
|
||||
%a = phi i8 [ 1, %entry ], [ %b, %for.loop ]
|
||||
%b = phi i8 [ 3, %entry ], [ %a, %for.loop ]
|
||||
%icmp = icmp eq i8 %a, 3
|
||||
br i1 %icmp, label %for.done, label %for.loop
|
||||
|
||||
for.done:
|
||||
store i8 %a, i8* @main.phiNodesResultA
|
||||
store i8 %b, i8* @main.phiNodesResultB
|
||||
ret void
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@main.phiNodesResultA = local_unnamed_addr global i8 3
|
||||
@main.phiNodesResultB = local_unnamed_addr global i8 1
|
||||
|
||||
define void @runtime.initAll() local_unnamed_addr {
|
||||
ret void
|
||||
}
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
declare void @externalCall(i64)
|
||||
|
||||
@foo.knownAtRuntime = global i64 0
|
||||
@bar.knownAtRuntime = global i64 0
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call void @foo.init(i8* undef, i8* undef)
|
||||
call void @bar.init(i8* undef, i8* undef)
|
||||
call void @main.init(i8* undef, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
store i64 5, i64* @foo.knownAtRuntime
|
||||
unreachable ; this triggers a revert of @foo.init.
|
||||
}
|
||||
|
||||
define internal void @bar.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
%val = load i64, i64* @foo.knownAtRuntime
|
||||
store i64 %val, i64* @bar.knownAtRuntime
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @externalCall(i64 3)
|
||||
ret void
|
||||
}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@foo.knownAtRuntime = local_unnamed_addr global i64 0
|
||||
@bar.knownAtRuntime = local_unnamed_addr global i64 0
|
||||
|
||||
declare void @externalCall(i64) local_unnamed_addr
|
||||
|
||||
define void @runtime.initAll() unnamed_addr {
|
||||
entry:
|
||||
call fastcc void @foo.init(i8* undef, i8* undef)
|
||||
%val = load i64, i64* @foo.knownAtRuntime, align 8
|
||||
store i64 %val, i64* @bar.knownAtRuntime, align 8
|
||||
call void @externalCall(i64 3)
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal fastcc void @foo.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
store i64 5, i64* @foo.knownAtRuntime, align 8
|
||||
unreachable
|
||||
}
|
||||
Vendored
-78
@@ -1,78 +0,0 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i64 }
|
||||
|
||||
declare i1 @runtime.typeAssert(i64, %runtime.typecodeID*, i8*, i8*)
|
||||
declare i1 @runtime.interfaceImplements(i64, i8**)
|
||||
|
||||
define i64 @returnsConst() {
|
||||
ret i64 0
|
||||
}
|
||||
|
||||
define i64 @returnsArg(i64 %arg) {
|
||||
ret i64 %arg
|
||||
}
|
||||
|
||||
declare i64 @externalCall()
|
||||
|
||||
define i64 @externalCallOnly() {
|
||||
%result = call i64 @externalCall()
|
||||
ret i64 0
|
||||
}
|
||||
|
||||
define i64 @externalCallAndReturn() {
|
||||
%result = call i64 @externalCall()
|
||||
ret i64 %result
|
||||
}
|
||||
|
||||
define i64 @externalCallBranch() {
|
||||
%result = call i64 @externalCall()
|
||||
%zero = icmp eq i64 %result, 0
|
||||
br i1 %zero, label %if.then, label %if.done
|
||||
|
||||
if.then:
|
||||
ret i64 2
|
||||
|
||||
if.done:
|
||||
ret i64 4
|
||||
}
|
||||
|
||||
@cleanGlobalInt = global i64 5
|
||||
define i64 @readCleanGlobal() {
|
||||
%global = load i64, i64* @cleanGlobalInt
|
||||
ret i64 %global
|
||||
}
|
||||
|
||||
@dirtyGlobalInt = global i64 5
|
||||
define i64 @readDirtyGlobal() {
|
||||
%global = load i64, i64* @dirtyGlobalInt
|
||||
ret i64 %global
|
||||
}
|
||||
|
||||
declare i64* @getDirtyPointer()
|
||||
|
||||
define void @storeToPointer() {
|
||||
%ptr = call i64* @getDirtyPointer()
|
||||
store i64 3, i64* %ptr
|
||||
ret void
|
||||
}
|
||||
|
||||
@functionPointer = global i64()* null
|
||||
define i64 @callFunctionPointer() {
|
||||
%fp = load i64()*, i64()** @functionPointer
|
||||
%result = call i64 %fp()
|
||||
ret i64 %result
|
||||
}
|
||||
|
||||
define i1 @callTypeAssert() {
|
||||
; Note: parameters are not realistic.
|
||||
%ok = call i1 @runtime.typeAssert(i64 0, %runtime.typecodeID* null, i8* undef, i8* null)
|
||||
ret i1 %ok
|
||||
}
|
||||
|
||||
define i1 @callInterfaceImplements() {
|
||||
; Note: parameters are not realistic.
|
||||
%ok = call i1 @runtime.interfaceImplements(i64 0, i8** null)
|
||||
ret i1 %ok
|
||||
}
|
||||
Vendored
+4
-1
@@ -1,6 +1,8 @@
|
||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||
target triple = "x86_64--linux"
|
||||
|
||||
@"main$alloc.1" = internal unnamed_addr constant [6 x i8] c"\05\00{\00\00\04"
|
||||
|
||||
declare void @runtime.printuint8(i8) local_unnamed_addr
|
||||
|
||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||
@@ -15,6 +17,7 @@ entry:
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printuint8(i8 3)
|
||||
call void @runtime.printint16(i16 5)
|
||||
call void @runtime.printint16(i16 5)
|
||||
%int16SliceDst.val = load i16, i16* bitcast ([6 x i8]* @"main$alloc.1" to i16*)
|
||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
||||
ret void
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user