mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 11:07:46 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4e07fc569c | |||
| 25e13d887f | |||
| 51daa2a044 | |||
| f70dc70247 | |||
| f24bb1ceda |
+45
-91
@@ -28,7 +28,6 @@ commands:
|
|||||||
qemu-user \
|
qemu-user \
|
||||||
gcc-avr \
|
gcc-avr \
|
||||||
avr-libc
|
avr-libc
|
||||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
|
|
||||||
install-node:
|
install-node:
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
@@ -38,54 +37,28 @@ commands:
|
|||||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||||
rm node-v10.15.1-linux-x64.tar.xz
|
rm node-v10.15.1-linux-x64.tar.xz
|
||||||
install-chrome:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: "Install Chrome"
|
|
||||||
command: |
|
|
||||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
|
||||||
sudo apt install ./google-chrome-stable_current_amd64.deb
|
|
||||||
install-wasmtime:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: "Install wasmtime"
|
|
||||||
command: |
|
|
||||||
curl https://wasmtime.dev/install.sh -sSf | bash
|
|
||||||
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
|
|
||||||
install-xtensa-toolchain:
|
|
||||||
parameters:
|
|
||||||
variant:
|
|
||||||
type: string
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: "Install Xtensa toolchain"
|
|
||||||
command: |
|
|
||||||
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
|
|
||||||
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
|
|
||||||
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
|
|
||||||
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
|
|
||||||
llvm-source-linux:
|
llvm-source-linux:
|
||||||
steps:
|
steps:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-11-v1
|
- llvm-source-10-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-11-v1
|
key: llvm-source-10-v0
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm-project
|
||||||
build-wasi-libc:
|
build-wasi-libc:
|
||||||
steps:
|
steps:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- wasi-libc-sysroot-v4
|
- wasi-libc-sysroot-v2
|
||||||
- run:
|
- run:
|
||||||
name: "Build wasi-libc"
|
name: "Build wasi-libc"
|
||||||
command: make wasi-libc
|
command: make wasi-libc
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: wasi-libc-sysroot-v4
|
key: wasi-libc-sysroot-v2
|
||||||
paths:
|
paths:
|
||||||
- lib/wasi-libc/sysroot
|
- lib/wasi-libc/sysroot
|
||||||
test-linux:
|
test-linux:
|
||||||
@@ -98,8 +71,6 @@ commands:
|
|||||||
- apt-dependencies:
|
- apt-dependencies:
|
||||||
llvm: "<<parameters.llvm>>"
|
llvm: "<<parameters.llvm>>"
|
||||||
- install-node
|
- install-node
|
||||||
- install-chrome
|
|
||||||
- install-wasmtime
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
@@ -108,21 +79,20 @@ commands:
|
|||||||
- run: go install -tags=llvm<<parameters.llvm>> .
|
- run: go install -tags=llvm<<parameters.llvm>> .
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- wasi-libc-sysroot-systemclang-v3
|
- wasi-libc-sysroot-systemclang-v1
|
||||||
- run: make wasi-libc
|
- run: make wasi-libc
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: wasi-libc-sysroot-systemclang-v3
|
key: wasi-libc-sysroot-systemclang-v1
|
||||||
paths:
|
paths:
|
||||||
- lib/wasi-libc/sysroot
|
- lib/wasi-libc/sysroot
|
||||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./interp ./transform .
|
||||||
- run: make gen-device -j4
|
- run: make gen-device -j4
|
||||||
- run: make smoketest XTENSA=0
|
- run: make smoketest
|
||||||
- run: make tinygo-test
|
|
||||||
- run: make wasmtest
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
- /go/pkg/mod
|
||||||
- run: make fmt-check
|
- run: make fmt-check
|
||||||
assert-test-linux:
|
assert-test-linux:
|
||||||
@@ -132,9 +102,9 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Install apt dependencies"
|
name: "Install apt dependencies"
|
||||||
command: |
|
command: |
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install \
|
sudo apt-get install \
|
||||||
gcc-arm-linux-gnueabihf \
|
gcc-arm-linux-gnueabihf \
|
||||||
|
binutils-arm-none-eabi \
|
||||||
libc6-dev-armel-cross \
|
libc6-dev-armel-cross \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
libc6-dev-arm64-cross \
|
libc6-dev-arm64-cross \
|
||||||
@@ -142,11 +112,7 @@ commands:
|
|||||||
qemu-user \
|
qemu-user \
|
||||||
gcc-avr \
|
gcc-avr \
|
||||||
avr-libc
|
avr-libc
|
||||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
|
||||||
- install-node
|
- install-node
|
||||||
- install-wasmtime
|
|
||||||
- install-xtensa-toolchain:
|
|
||||||
variant: "linux-amd64"
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
@@ -154,14 +120,17 @@ commands:
|
|||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-11-linux-v2-assert
|
- llvm-build-10-linux-v0-assert
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
then
|
then
|
||||||
# install dependencies
|
# install dependencies
|
||||||
sudo apt-get install cmake ninja-build
|
sudo apt-get install cmake clang ninja-build
|
||||||
|
# make build faster
|
||||||
|
export CC=clang
|
||||||
|
export CXX=clang++
|
||||||
# hack ninja to use less jobs
|
# hack ninja to use less jobs
|
||||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||||
chmod +x /go/bin/ninja
|
chmod +x /go/bin/ninja
|
||||||
@@ -169,7 +138,7 @@ commands:
|
|||||||
make ASSERT=1 llvm-build
|
make ASSERT=1 llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-linux-v2-assert
|
key: llvm-build-10-linux-v0-assert
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- run: make ASSERT=1
|
- run: make ASSERT=1
|
||||||
@@ -181,6 +150,7 @@ commands:
|
|||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
- /go/pkg/mod
|
||||||
- run: make gen-device -j4
|
- run: make gen-device -j4
|
||||||
- run: make smoketest TINYGO=build/tinygo
|
- run: make smoketest TINYGO=build/tinygo
|
||||||
@@ -191,9 +161,9 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Install apt dependencies"
|
name: "Install apt dependencies"
|
||||||
command: |
|
command: |
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install \
|
sudo apt-get install \
|
||||||
gcc-arm-linux-gnueabihf \
|
gcc-arm-linux-gnueabihf \
|
||||||
|
binutils-arm-none-eabi \
|
||||||
libc6-dev-armel-cross \
|
libc6-dev-armel-cross \
|
||||||
gcc-aarch64-linux-gnu \
|
gcc-aarch64-linux-gnu \
|
||||||
libc6-dev-arm64-cross \
|
libc6-dev-arm64-cross \
|
||||||
@@ -201,11 +171,7 @@ commands:
|
|||||||
qemu-user \
|
qemu-user \
|
||||||
gcc-avr \
|
gcc-avr \
|
||||||
avr-libc
|
avr-libc
|
||||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
|
||||||
- install-node
|
- install-node
|
||||||
- install-wasmtime
|
|
||||||
- install-xtensa-toolchain:
|
|
||||||
variant: "linux-amd64"
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
@@ -213,14 +179,17 @@ commands:
|
|||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-11-linux-v2-noassert
|
- llvm-build-10-linux-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
then
|
then
|
||||||
# install dependencies
|
# install dependencies
|
||||||
sudo apt-get install cmake ninja-build
|
sudo apt-get install cmake clang ninja-build
|
||||||
|
# make build faster
|
||||||
|
export CC=clang
|
||||||
|
export CXX=clang++
|
||||||
# hack ninja to use less jobs
|
# hack ninja to use less jobs
|
||||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||||
chmod +x /go/bin/ninja
|
chmod +x /go/bin/ninja
|
||||||
@@ -228,32 +197,25 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-linux-v2-noassert
|
key: llvm-build-10-linux-v0
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- build-wasi-libc
|
- build-wasi-libc
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test
|
command: make test
|
||||||
- run:
|
|
||||||
name: "Install fpm"
|
|
||||||
command: |
|
|
||||||
sudo apt-get install ruby ruby-dev
|
|
||||||
sudo gem install --no-document fpm
|
|
||||||
- run:
|
- run:
|
||||||
name: "Build TinyGo release"
|
name: "Build TinyGo release"
|
||||||
command: |
|
command: |
|
||||||
make release deb -j3
|
make release -j3
|
||||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||||
cp -p build/release.deb /tmp/tinygo_amd64.deb
|
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||||
- store_artifacts:
|
|
||||||
path: /tmp/tinygo_amd64.deb
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
- /go/pkg/mod
|
||||||
- run:
|
- run:
|
||||||
name: "Extract release tarball"
|
name: "Extract release tarball"
|
||||||
@@ -270,29 +232,27 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Install dependencies"
|
name: "Install dependencies"
|
||||||
command: |
|
command: |
|
||||||
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
|
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.16.darwin-amd64.tar.gz
|
sudo tar -C /usr/local -xzf go1.14.darwin-amd64.tar.gz
|
||||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||||
- install-xtensa-toolchain:
|
|
||||||
variant: "macos"
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-source-11-macos-v1
|
- llvm-source-10-macos-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Fetch LLVM source"
|
name: "Fetch LLVM source"
|
||||||
command: make llvm-source
|
command: make llvm-source
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-source-11-macos-v1
|
key: llvm-source-10-macos-v0
|
||||||
paths:
|
paths:
|
||||||
- llvm-project
|
- llvm-project
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-11-macos-v2
|
- llvm-build-10-macos-v0
|
||||||
- run:
|
- run:
|
||||||
name: "Build LLVM"
|
name: "Build LLVM"
|
||||||
command: |
|
command: |
|
||||||
@@ -304,17 +264,17 @@ commands:
|
|||||||
make llvm-build
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-macos-v2
|
key: llvm-build-10-macos-v0
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- wasi-libc-sysroot-macos-v3
|
- wasi-libc-sysroot-macos-v1
|
||||||
- run:
|
- run:
|
||||||
name: "Build wasi-libc"
|
name: "Build wasi-libc"
|
||||||
command: make wasi-libc
|
command: make wasi-libc
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: wasi-libc-sysroot-macos-v3
|
key: wasi-libc-sysroot-macos-v1
|
||||||
paths:
|
paths:
|
||||||
- lib/wasi-libc/sysroot
|
- lib/wasi-libc/sysroot
|
||||||
- run:
|
- run:
|
||||||
@@ -334,20 +294,27 @@ commands:
|
|||||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||||
tinygo version
|
tinygo version
|
||||||
|
- run:
|
||||||
|
name: "Download SiFive GNU toolchain"
|
||||||
|
command: |
|
||||||
|
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||||
|
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||||
- run: make smoketest AVR=0
|
- run: make smoketest AVR=0
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
|
- ~/.cache/tinygo
|
||||||
- /go/pkg/mod
|
- /go/pkg/mod
|
||||||
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-llvm10-go111:
|
test-llvm9-go111:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.11-buster
|
- image: circleci/golang:1.11-buster
|
||||||
steps:
|
steps:
|
||||||
- test-linux:
|
- test-linux:
|
||||||
llvm: "10"
|
llvm: "9"
|
||||||
test-llvm10-go112:
|
test-llvm10-go112:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.12-buster
|
- image: circleci/golang:1.12-buster
|
||||||
@@ -366,18 +333,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- test-linux:
|
- test-linux:
|
||||||
llvm: "10"
|
llvm: "10"
|
||||||
test-llvm11-go115:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.15-buster
|
|
||||||
steps:
|
|
||||||
- test-linux:
|
|
||||||
llvm: "11"
|
|
||||||
test-llvm11-go116:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.16-buster
|
|
||||||
steps:
|
|
||||||
- test-linux:
|
|
||||||
llvm: "11"
|
|
||||||
assert-test-linux:
|
assert-test-linux:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.14-stretch
|
- image: circleci/golang:1.14-stretch
|
||||||
@@ -396,15 +351,14 @@ jobs:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
test-all:
|
test-all:
|
||||||
jobs:
|
jobs:
|
||||||
- test-llvm10-go111
|
- test-llvm9-go111
|
||||||
- test-llvm10-go112
|
- test-llvm10-go112
|
||||||
- test-llvm10-go113
|
- test-llvm10-go113
|
||||||
- test-llvm10-go114
|
- test-llvm10-go114
|
||||||
- test-llvm11-go115
|
|
||||||
- test-llvm11-go116
|
|
||||||
- build-linux
|
- build-linux
|
||||||
- build-macos
|
- build-macos
|
||||||
- assert-test-linux
|
- assert-test-linux
|
||||||
|
|||||||
@@ -3,19 +3,14 @@ docs/_build
|
|||||||
src/device/avr/*.go
|
src/device/avr/*.go
|
||||||
src/device/avr/*.ld
|
src/device/avr/*.ld
|
||||||
src/device/avr/*.s
|
src/device/avr/*.s
|
||||||
src/device/esp/*.go
|
|
||||||
src/device/nrf/*.go
|
src/device/nrf/*.go
|
||||||
src/device/nrf/*.s
|
src/device/nrf/*.s
|
||||||
src/device/nxp/*.go
|
|
||||||
src/device/nxp/*.s
|
|
||||||
src/device/sam/*.go
|
src/device/sam/*.go
|
||||||
src/device/sam/*.s
|
src/device/sam/*.s
|
||||||
src/device/sifive/*.go
|
src/device/sifive/*.go
|
||||||
src/device/sifive/*.s
|
src/device/sifive/*.s
|
||||||
src/device/stm32/*.go
|
src/device/stm32/*.go
|
||||||
src/device/stm32/*.s
|
src/device/stm32/*.s
|
||||||
src/device/kendryte/*.go
|
|
||||||
src/device/kendryte/*.s
|
|
||||||
vendor
|
vendor
|
||||||
llvm-build
|
llvm-build
|
||||||
llvm-project
|
llvm-project
|
||||||
|
|||||||
+1
-4
@@ -9,7 +9,7 @@
|
|||||||
url = https://github.com/avr-rust/avr-mcu.git
|
url = https://github.com/avr-rust/avr-mcu.git
|
||||||
[submodule "lib/cmsis-svd"]
|
[submodule "lib/cmsis-svd"]
|
||||||
path = lib/cmsis-svd
|
path = lib/cmsis-svd
|
||||||
url = https://github.com/tinygo-org/cmsis-svd
|
url = https://github.com/posborne/cmsis-svd
|
||||||
[submodule "lib/compiler-rt"]
|
[submodule "lib/compiler-rt"]
|
||||||
path = lib/compiler-rt
|
path = lib/compiler-rt
|
||||||
url = https://github.com/llvm-mirror/compiler-rt.git
|
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||||
@@ -20,6 +20,3 @@
|
|||||||
[submodule "lib/picolibc"]
|
[submodule "lib/picolibc"]
|
||||||
path = lib/picolibc
|
path = lib/picolibc
|
||||||
url = https://github.com/keith-packard/picolibc.git
|
url = https://github.com/keith-packard/picolibc.git
|
||||||
[submodule "lib/stm32-svd"]
|
|
||||||
path = lib/stm32-svd
|
|
||||||
url = https://github.com/tinygo-org/stm32-svd
|
|
||||||
|
|||||||
-12
@@ -10,14 +10,6 @@ This guide describes how to statically link TinyGo against LLVM, libclang and
|
|||||||
lld so that the binary can be easily moved between systems. It also shows how to
|
lld so that the binary can be easily moved between systems. It also shows how to
|
||||||
build a release tarball that includes this binary and all necessary extra files.
|
build a release tarball that includes this binary and all necessary extra files.
|
||||||
|
|
||||||
**Note**: this documentation describes how to build a statically linked release
|
|
||||||
tarball. If you want to develop TinyGo, you will probably want to follow a
|
|
||||||
different guide:
|
|
||||||
|
|
||||||
* [Linux](https://tinygo.org/getting-started/linux/#source-install)
|
|
||||||
* [macOS](https://tinygo.org/getting-started/macos/#source-install)
|
|
||||||
* [Windows](https://tinygo.org/getting-started/windows/#source-install)
|
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||||
@@ -92,10 +84,6 @@ Now that we have a working static build, it's time to make a release tarball:
|
|||||||
|
|
||||||
make release
|
make release
|
||||||
|
|
||||||
If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules:
|
|
||||||
|
|
||||||
git submodule update --init
|
|
||||||
|
|
||||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||||
the following command (for example in ~/lib):
|
the following command (for example in ~/lib):
|
||||||
|
|
||||||
|
|||||||
-313
@@ -1,316 +1,3 @@
|
|||||||
0.17.0
|
|
||||||
|
|
||||||
---
|
|
||||||
* **command line**
|
|
||||||
- switch to LLVM 11 for static builds
|
|
||||||
- support gdb debugging with AVR
|
|
||||||
- add support for additional openocd commands
|
|
||||||
- add `-x` flag to print commands
|
|
||||||
- use LLVM 11 by default when linking LLVM dynamically
|
|
||||||
- update go-llvm to use LLVM 11 on macOS
|
|
||||||
- bump go.bug.st/serial to version 1.1.2
|
|
||||||
- do not build LLVM with libxml to work around a bugo on macOS
|
|
||||||
- add support for Go 1.16
|
|
||||||
- support gdb daemonization on Windows
|
|
||||||
- remove support for LLVM 9, to fix CI
|
|
||||||
- kill OpenOCD if it does not exit with a regular quit signal
|
|
||||||
- support `-ocd-output` on Windows
|
|
||||||
* **compiler**
|
|
||||||
- `builder`: parallelize most of the build
|
|
||||||
- `builder`: remove unused cacheKey parameter
|
|
||||||
- `builder`: add -mcpu flag while building libraries
|
|
||||||
- `builder`: wait for running jobs to finish
|
|
||||||
- `cgo`: add support for variadic functions
|
|
||||||
- `compiler`: fix undefined behavior in wordpack
|
|
||||||
- `compiler`: fix incorrect "exported function" panic
|
|
||||||
- `compiler`: fix non-int integer constants (fixing a crash)
|
|
||||||
- `compiler`: refactor and add tests
|
|
||||||
- `compiler`: emit a nil check when slicing an array pointer
|
|
||||||
- `compiler`: saturate float-to-int conversions
|
|
||||||
- `compiler`: test float to int conversions and fix upper-bound calculation
|
|
||||||
- `compiler`: support all kinds of deferred builtins
|
|
||||||
- `compiler`: remove ir package
|
|
||||||
- `compiler`: remove unnecessary main.main call workaround
|
|
||||||
- `compiler`: move the setting of attributes to getFunction
|
|
||||||
- `compiler`: create runtime types lazily when needed
|
|
||||||
- `compiler`: move settings to a separate Config struct
|
|
||||||
- `compiler`: work around an ARM backend bug in LLVM
|
|
||||||
- `interp`: rewrite entire package
|
|
||||||
- `interp`: fix alignment of untyped globals
|
|
||||||
- `loader`: use name "main" for the main package
|
|
||||||
- `loader`: support imports from vendor directories
|
|
||||||
- `stacksize`: add support for DW_CFA_offset_extended
|
|
||||||
- `transform`: show better error message in coroutines lowering
|
|
||||||
* **standard library**
|
|
||||||
- `machine`: accept configuration struct for ADC parameters
|
|
||||||
- `machine`: make I2C.Configure signature consistent
|
|
||||||
- `reflect`: implement PtrTo
|
|
||||||
- `runtime`: refactor to simplify stack switching
|
|
||||||
- `runtime`: put metadata at the top end of the heap
|
|
||||||
* **targets**
|
|
||||||
- `atsam`: add a length check to findPinPadMapping
|
|
||||||
- `atsam`: improve USBCDC
|
|
||||||
- `atsam`: avoid infinite loop when USBCDC is disconnected
|
|
||||||
- `avr`: add SPI support for Atmega based chips
|
|
||||||
- `avr`: use Clang for compiling C and assembly files
|
|
||||||
- `esp32`: implement task based scheduler
|
|
||||||
- `esp32`: enable the FPU
|
|
||||||
- `esp8266`: implement task based scheduler
|
|
||||||
- `esp`: add compiler-rt library
|
|
||||||
- `esp`: add picolibc
|
|
||||||
- `nrf`: refactor code a bit to reduce duplication
|
|
||||||
- `nrf`: use SPIM peripheral instead of the legacy SPI peripheral
|
|
||||||
- `nrf`: update nrfx submodule to latest commit
|
|
||||||
- `nrf52840`: ensure that USB CDC interface is only initialized once
|
|
||||||
- `nrf52840`: improve USBCDC
|
|
||||||
- `stm32`: use stm32-rs SVDs which are of much higher quality
|
|
||||||
- `stm32`: harmonization of UART logic
|
|
||||||
- `stm32`: replace I2C addressable interface with simpler type
|
|
||||||
- `stm32`: fix i2c and add stm32f407 i2c
|
|
||||||
- `stm32`: revert change that adds support for channels in interrupts
|
|
||||||
- `wasm`: implement a growable heap
|
|
||||||
- `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString()
|
|
||||||
- `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR
|
|
||||||
- `wasi`: support env variables based on libc
|
|
||||||
- `wasi`: specify wasi-libc in a different way, to improve error message
|
|
||||||
* **boards**
|
|
||||||
- `matrixportal-m4`: add support for board Adafruit Matrix Portal M4
|
|
||||||
- `mkr1000`: add this board
|
|
||||||
- `nucleo-f722ze`: add this board
|
|
||||||
- `clue`: correct volume name and add alias for release version of Adafruit Clue board
|
|
||||||
- `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR)
|
|
||||||
- `microbit-v2`: add initial support based on work done by @alankrantas thank you!
|
|
||||||
- `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device
|
|
||||||
- `nicenano`: nice!nano board support
|
|
||||||
- `circuitplay-bluefruit`: correct internal I2C pin mapping
|
|
||||||
- `clue`: correct for lack of low frequency crystal
|
|
||||||
- `digispark`: split off attiny85 target
|
|
||||||
- `nucleo-l552ze`: implementation with CLOCK, LED, and UART
|
|
||||||
- `nrf52840-mdk-usb-dongle`: add this board
|
|
||||||
|
|
||||||
0.16.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **command-line**
|
|
||||||
- add initial support for LLVM 11
|
|
||||||
- make lib64 clang include path check more robust
|
|
||||||
- `build`: improve support for GOARCH=386 and add tests
|
|
||||||
- `gdb`: add support for qemu-user targets
|
|
||||||
- `test`: support non-host tests
|
|
||||||
- `test`: add support for -c and -o flags
|
|
||||||
- `test`: implement some benchmark stubs
|
|
||||||
* **compiler**
|
|
||||||
- `builder`: improve detection of clang on Fedora
|
|
||||||
- `compiler`: fix floating point comparison bugs
|
|
||||||
- `compiler`: implement negate for complex numbers
|
|
||||||
- `loader`: fix linkname in test binaries
|
|
||||||
- `transform`: add missing return pointer restore for regular coroutine tail
|
|
||||||
calls
|
|
||||||
* **standard library**
|
|
||||||
- `machine`: switch default frequency to 4MHz
|
|
||||||
- `machine`: clarify caller's responsibility in `SetInterrupt`
|
|
||||||
- `os`: add `LookupEnv()` stub
|
|
||||||
- `reflect`: implement `Swapper`
|
|
||||||
- `runtime`: fix UTF-8 decoding
|
|
||||||
- `runtime`: gc: use raw stack access whenever possible
|
|
||||||
- `runtime`: use dedicated printfloat32
|
|
||||||
- `runtime`: allow ranging over a nil map
|
|
||||||
- `runtime`: avoid device/nxp dependency in HardFault handler
|
|
||||||
- `testing`: implement dummy Helper method
|
|
||||||
- `testing`: add Run method
|
|
||||||
* **targets**
|
|
||||||
- `arm64`: add support for SVCall intrinsic
|
|
||||||
- `atsamd51`: avoid panic when configuring SPI with SDI=NoPin
|
|
||||||
- `avr`: properly support the `.rodata` section
|
|
||||||
- `esp8266`: implement `Pin.Get` function
|
|
||||||
- `nintendoswitch`: fix crash when printing long lines (> 120)
|
|
||||||
- `nintendoswitch`: add env parser and removed unused stuff
|
|
||||||
- `nrf`: add I2C error checking
|
|
||||||
- `nrf`: give more flexibility in picking SPI speeds
|
|
||||||
- `nrf`: fix nrf52832 flash size
|
|
||||||
- `stm32f103`: support wakeups from interrupts
|
|
||||||
- `stm32f405`: add SPI support
|
|
||||||
- `stm32f405`: add I2C support
|
|
||||||
- `wasi`: add support for this target
|
|
||||||
- `wasi`: use 'generic' ABI by default
|
|
||||||
- `wasi`: remove --no-threads flag from wasm-ld
|
|
||||||
- `wasm`: add instanceof support for WebAssembly
|
|
||||||
- `wasm`: use fixed length buffer for putchar
|
|
||||||
* **boards**
|
|
||||||
- `d1mini`: add this ESP8266 based board
|
|
||||||
- `esp32`: use board definitions instead of chip names
|
|
||||||
- `qtpy`: add board definition for Adafruit QTPy
|
|
||||||
- `teensy40`: add this board
|
|
||||||
|
|
||||||
0.15.0
|
|
||||||
---
|
|
||||||
|
|
||||||
* **command-line**
|
|
||||||
- add cached GOROOT to info subcommand
|
|
||||||
- embed git-hash in tinygo-dev executable
|
|
||||||
- implement tinygo targets to list usable targets
|
|
||||||
- use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders
|
|
||||||
- use ToSlash() to specify program path
|
|
||||||
- support flashing esp32/esp8266 directly from tinygo
|
|
||||||
- when flashing call PortReset only on other than openocd
|
|
||||||
* **compiler**
|
|
||||||
- `compileopts`: add support for custom binary formats
|
|
||||||
- `compiler`: improve display of goroutine wrappers
|
|
||||||
- `interp`: don't panic in the Store method
|
|
||||||
- `interp`: replace some panics with error messages
|
|
||||||
- `interp`: show error line in first line of the traceback
|
|
||||||
- `loader`: be more robust when creating the cached GOROOT
|
|
||||||
- `loader`: rewrite/refactor much of the code to use go list directly
|
|
||||||
- `loader`: use ioutil.TempDir to create a temporary directory
|
|
||||||
- `stacksize`: deal with DW_CFA_advance_loc1
|
|
||||||
* **standard library**
|
|
||||||
- `runtime`: use waitForEvents when appropriate
|
|
||||||
* **wasm**
|
|
||||||
- `wasm`: Remove --no-threads from wasm-ld calls.
|
|
||||||
- `wasm`: update wasi-libc dependency
|
|
||||||
* **targets**
|
|
||||||
- `arduino-mega2560`: fix flashing on Windows
|
|
||||||
- `arm`: automatically determine stack sizes
|
|
||||||
- `arm64`: make dynamic loader structs and constants private
|
|
||||||
- `avr`: configure emulator in board files
|
|
||||||
- `cortexm`: fix stack size calculation with interrupts
|
|
||||||
- `flash`: add openocd settings to atsamd21 / atsamd51
|
|
||||||
- `flash`: add openocd settings to nrf5
|
|
||||||
- `microbit`: reelboard: flash using OpenOCD when needed
|
|
||||||
- `nintendoswitch`: Add dynamic loader for runtime loading PIE sections
|
|
||||||
- `nintendoswitch`: fix import cycle on dynamic_arm64.go
|
|
||||||
- `nintendoswitch`: Fix invalid memory read / write in print calls
|
|
||||||
- `nintendoswitch`: simplified assembly code
|
|
||||||
- `nintendoswitch`: support outputting .nro files directly
|
|
||||||
* **boards**
|
|
||||||
- `arduino-zero`: Adding support for the Arduino Zero (#1365)
|
|
||||||
- `atsamd2x`: fix BAUD value
|
|
||||||
- `atsamd5x`: fix BAUD value
|
|
||||||
- `bluepill`: Enable stm32's USART2 for the board and map it to UART1 tinygo's device
|
|
||||||
- `device/atsamd51x`: add all remaining bitfield values for PCHCTRLm Mapping
|
|
||||||
- `esp32`: add libgcc ROM functions to linker script
|
|
||||||
- `esp32`: add SPI support
|
|
||||||
- `esp32`: add support for basic GPIO
|
|
||||||
- `esp32`: add support for the Espressif ESP32 chip
|
|
||||||
- `esp32`: configure the I/O matrix for GPIO pins
|
|
||||||
- `esp32`: export machine.PortMask* for bitbanging implementations
|
|
||||||
- `esp8266`: add support for this chip
|
|
||||||
- `machine/atsamd51x,runtime/atsamd51x`: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage
|
|
||||||
- `machine/itsybitsy-nrf52840`: add support for Adafruit Itsybitsy nrf52840 (#1243)
|
|
||||||
- `machine/stm32f4`: refactor common code and add new build tag stm32f4 (#1332)
|
|
||||||
- `nrf`: add SoftDevice support for the Circuit Playground Bluefruit
|
|
||||||
- `nrf`: call sd_app_evt_wait when the SoftDevice is enabled
|
|
||||||
- `nrf52840`: add build tags for SoftDevice support
|
|
||||||
- `nrf52840`: use higher priority for USB-CDC code
|
|
||||||
- `runtime/atsamd51x`: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports
|
|
||||||
- `stm32f405`: add basic UART handler
|
|
||||||
- `stm32f405`: add STM32F405 machine/runtime, and new board/target feather-stm32f405
|
|
||||||
* **build**
|
|
||||||
- `all`: run test binaries in the correct directory
|
|
||||||
- `build`: Fix arch release job
|
|
||||||
- `ci`: run `tinygo test` for known-working packages
|
|
||||||
- `ci`: set git-fetch-depth to 1
|
|
||||||
- `docker`: fix the problem with the wasm build (#1357)
|
|
||||||
- `Makefile`: check whether submodules have been downloaded in some common cases
|
|
||||||
* **docs**
|
|
||||||
- add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards
|
|
||||||
|
|
||||||
0.14.1
|
|
||||||
---
|
|
||||||
* **command-line**
|
|
||||||
- support for Go 1.15
|
|
||||||
* **compiler**
|
|
||||||
- loader: work around Windows symlink limitation
|
|
||||||
|
|
||||||
0.14.0
|
|
||||||
---
|
|
||||||
* **command-line**
|
|
||||||
- fix `getDefaultPort()` on non-English Windows locales
|
|
||||||
- compileopts: improve error reporting of unsupported flags
|
|
||||||
- fix test subcommand
|
|
||||||
- use auto-retry to locate MSD for UF2 and HEX flashing
|
|
||||||
- fix touchSerialPortAt1200bps on Windows
|
|
||||||
- support package names with backslashes on Windows
|
|
||||||
* **compiler**
|
|
||||||
- fix a few crashes due to named types
|
|
||||||
- add support for atomic operations
|
|
||||||
- move the channel blocked list onto the stack
|
|
||||||
- fix -gc=none
|
|
||||||
- fix named string to `[]byte` slice conversion
|
|
||||||
- implement func value and builtin defers
|
|
||||||
- add proper parameter names to runtime.initAll, to fix a panic
|
|
||||||
- builder: fix picolibc include path
|
|
||||||
- builder: use newer version of gohex
|
|
||||||
- builder: try to determine stack size information at compile time
|
|
||||||
- builder: remove -opt=0
|
|
||||||
- interp: fix sync/atomic.Value load/store methods
|
|
||||||
- loader: add Go module support
|
|
||||||
- transform: fix debug information in func lowering pass
|
|
||||||
- transform: do not special-case zero or one implementations of a method call
|
|
||||||
- transform: introduce check for method calls on nil interfaces
|
|
||||||
- transform: gc: track 0-index GEPs to fix miscompilation
|
|
||||||
* **cgo**
|
|
||||||
- Add LDFlags support
|
|
||||||
* **standard library**
|
|
||||||
- extend stdlib to allow import of more packages
|
|
||||||
- replace master/slave terminology with appropriate alternatives (MOSI->SDO
|
|
||||||
etc)
|
|
||||||
- `internal/bytealg`: reimplement bytealg in pure Go
|
|
||||||
- `internal/task`: fix nil panic in (*internal/task.Stack).Pop
|
|
||||||
- `os`: add Args and stub it with mock data
|
|
||||||
- `os`: implement virtual filesystem support
|
|
||||||
- `reflect`: add Cap and Len support for map and chan
|
|
||||||
- `runtime`: fix return address in scheduler on RISC-V
|
|
||||||
- `runtime`: avoid recursion in printuint64 function
|
|
||||||
- `runtime`: replace ReadRegister with AsmFull inline assembly
|
|
||||||
- `runtime`: fix compilation errors when using gc.extalloc
|
|
||||||
- `runtime`: add cap and len support for chans
|
|
||||||
- `runtime`: refactor time handling (improving accuracy)
|
|
||||||
- `runtime`: make channels work in interrupts
|
|
||||||
- `runtime/interrupt`: add cross-chip disable/restore interrupt support
|
|
||||||
- `sync`: implement `sync.Cond`
|
|
||||||
- `sync`: add WaitGroup
|
|
||||||
* **targets**
|
|
||||||
- `arm`: allow nesting in DisableInterrupts and EnableInterrupts
|
|
||||||
- `arm`: make FPU configuraton consistent
|
|
||||||
- `arm`: do not mask fault handlers in critical sections
|
|
||||||
- `atmega2560`: fix pin mapping for pins D2, D5 and the L port
|
|
||||||
- `atsamd`: return an error when an incorrect PWM pin is used
|
|
||||||
- `atsamd`: add support for pin change interrupts
|
|
||||||
- `atsamd`: add DAC support
|
|
||||||
- `atsamd21`: add more ADC pins
|
|
||||||
- `atsamd51`: fix ROM / RAM size on atsamd51j20
|
|
||||||
- `atsamd51`: add more pins
|
|
||||||
- `atsamd51`: add more ADC pins
|
|
||||||
- `atsamd51`: add pin change interrupt settings
|
|
||||||
- `atsamd51`: extend pinPadMapping
|
|
||||||
- `arduino-nano33`: use (U)SB flag to ensure that device can be found when
|
|
||||||
not on default port
|
|
||||||
- `arduino-nano33`: remove (d)ebug flag to reduce console noise when flashing
|
|
||||||
- `avr`: use standard pin numbering
|
|
||||||
- `avr`: unify GPIO pin/port code
|
|
||||||
- `avr`: add support for PinInputPullup
|
|
||||||
- `avr`: work around codegen bug in LLVM 10
|
|
||||||
- `avr`: fix target triple
|
|
||||||
- `fe310`: remove extra println left in by mistake
|
|
||||||
- `feather-nrf52840`: add support for the Feather nRF52840
|
|
||||||
- `maixbit`: add board definition and dummy runtime
|
|
||||||
- `nintendoswitch`: Add experimental Nintendo Switch support without CRT
|
|
||||||
- `nrf`: expose the RAM base address
|
|
||||||
- `nrf`: add support for pin change interrupts
|
|
||||||
- `nrf`: add microbit-s110v8 target
|
|
||||||
- `nrf`: fix bug in SPI.Tx
|
|
||||||
- `nrf`: support debugging the PCA10056
|
|
||||||
- `pygamer`: add Adafruit PyGamer suport
|
|
||||||
- `riscv`: fix interrupt configuration bug
|
|
||||||
- `riscv`: disable linker relaxations during gp init
|
|
||||||
- `stm32f4disco`: add new target with ST-Link v2.1 debugger
|
|
||||||
- `teensy36`: add Teensy 3.6 support
|
|
||||||
- `wasm`: fix event handling
|
|
||||||
- `wasm`: add --no-demangle linker option
|
|
||||||
- `wioterminal`: add support for the Seeed Wio Terminal
|
|
||||||
- `xiao`: add support for the Seeed XIAO
|
|
||||||
|
|
||||||
0.13.1
|
0.13.1
|
||||||
---
|
---
|
||||||
* **standard library**
|
* **standard library**
|
||||||
|
|||||||
+1
-1
@@ -32,7 +32,7 @@ Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't h
|
|||||||
|
|
||||||
## How to use our Github repository
|
## How to use our Github repository
|
||||||
|
|
||||||
The `release` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
The `master` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||||
|
|
||||||
Here is how to contribute back some code or documentation:
|
Here is how to contribute back some code or documentation:
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -15,4 +15,4 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
|
|||||||
Daniel Esteban <conejo@conejo.me>
|
Daniel Esteban <conejo@conejo.me>
|
||||||
Loon, LLC.
|
Loon, LLC.
|
||||||
Ron Evans <ron@hybridgroup.com>
|
Ron Evans <ron@hybridgroup.com>
|
||||||
Nia Weiss <niaow1234@gmail.com>
|
Jaden Weiss <jaden@jadendw.dev>
|
||||||
|
|||||||
+9
-9
@@ -1,10 +1,10 @@
|
|||||||
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
|
# TinyGo base stage installs Go 1.14, LLVM 10 and the TinyGo compiler itself.
|
||||||
FROM golang:1.15 AS tinygo-base
|
FROM golang:1.14 AS tinygo-base
|
||||||
|
|
||||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-10 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
|
apt-get install -y llvm-10-dev libclang-10-dev git
|
||||||
|
|
||||||
COPY . /tinygo
|
COPY . /tinygo
|
||||||
|
|
||||||
@@ -27,10 +27,10 @@ COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
|||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
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 && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y make clang-11 libllvm11 lld-11 && \
|
apt-get install -y libllvm10 lld-10
|
||||||
make wasi-libc
|
|
||||||
|
|
||||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||||
FROM tinygo-base AS tinygo-avr
|
FROM tinygo-base AS tinygo-avr
|
||||||
@@ -61,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
|||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils make clang-11 && \
|
apt-get install -y apt-utils make clang-10 && \
|
||||||
make gen-device-nrf && make gen-device-stm32
|
make gen-device-nrf && make gen-device-stm32
|
||||||
|
|
||||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
# 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/ && \
|
RUN cd /tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
|
apt-get install -y apt-utils make clang-10 binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device
|
make gen-device
|
||||||
|
|
||||||
CMD ["tinygo"]
|
CMD ["tinygo"]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
|
Copyright (c) 2018-2020 TinyGo Authors. All rights reserved.
|
||||||
|
|
||||||
TinyGo includes portions of the Go standard library.
|
TinyGo includes portions of the Go standard library.
|
||||||
Copyright (c) 2009-2021 The Go Authors. All rights reserved.
|
Copyright (c) 2009-2020 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
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.
|
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||||
|
|||||||
@@ -9,10 +9,25 @@ CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
|||||||
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
||||||
|
|
||||||
# Try to autodetect LLVM build tools.
|
# Try to autodetect LLVM build tools.
|
||||||
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
|
ifneq (, $(shell command -v llvm-build/bin/clang 2> /dev/null))
|
||||||
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
|
CLANG ?= $(abspath llvm-build/bin/clang)
|
||||||
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
|
else
|
||||||
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
|
CLANG ?= clang-10
|
||||||
|
endif
|
||||||
|
ifneq (, $(shell command -v llvm-build/bin/llvm-ar 2> /dev/null))
|
||||||
|
LLVM_AR ?= $(abspath llvm-build/bin/llvm-ar)
|
||||||
|
else ifneq (, $(shell command -v llvm-ar-10 2> /dev/null))
|
||||||
|
LLVM_AR ?= llvm-ar-10
|
||||||
|
else
|
||||||
|
LLVM_AR ?= llvm-ar
|
||||||
|
endif
|
||||||
|
ifneq (, $(shell command -v llvm-build/bin/llvm-nm 2> /dev/null))
|
||||||
|
LLVM_NM ?= $(abspath llvm-build/bin/llvm-nm)
|
||||||
|
else ifneq (, $(shell command -v llvm-nm-10 2> /dev/null))
|
||||||
|
LLVM_NM ?= llvm-nm-10
|
||||||
|
else
|
||||||
|
LLVM_NM ?= llvm-nm
|
||||||
|
endif
|
||||||
|
|
||||||
# Go binary and GOROOT to select
|
# Go binary and GOROOT to select
|
||||||
GO ?= go
|
GO ?= go
|
||||||
@@ -22,7 +37,7 @@ export GOROOT = $(shell $(GO) env GOROOT)
|
|||||||
MD5SUM = md5sum
|
MD5SUM = md5sum
|
||||||
|
|
||||||
# tinygo binary for tests
|
# tinygo binary for tests
|
||||||
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
TINYGO ?= tinygo
|
||||||
|
|
||||||
# Use CCACHE for LLVM if possible
|
# Use CCACHE for LLVM if possible
|
||||||
ifneq (, $(shell command -v ccache 2> /dev/null))
|
ifneq (, $(shell command -v ccache 2> /dev/null))
|
||||||
@@ -36,7 +51,7 @@ else
|
|||||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
||||||
endif
|
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-avr
|
||||||
|
|
||||||
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
|
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,69 +63,64 @@ ifeq ($(OS),Windows_NT)
|
|||||||
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
||||||
# Without this flag, linking results in errors like these:
|
# 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()'
|
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
||||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
|
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
|
||||||
|
|
||||||
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
|
|
||||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||||
CGO_LDFLAGS_EXTRA += -lversion
|
CGO_LDFLAGS_EXTRA += -lversion
|
||||||
|
|
||||||
LIBCLANG_NAME = libclang
|
# Build libclang manually because the CMake-based build system on Windows
|
||||||
|
# doesn't allow building libclang as a static library.
|
||||||
|
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
|
||||||
|
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
|
||||||
|
|
||||||
|
# Add the libclang dependency to the tinygo binary target.
|
||||||
|
tinygo: $(LIBCLANG_PATH)
|
||||||
|
test: $(LIBCLANG_PATH)
|
||||||
|
# Build libclang.
|
||||||
|
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
|
||||||
|
@mkdir -p build
|
||||||
|
ar rcs $(LIBCLANG_PATH) $^
|
||||||
|
|
||||||
else ifeq ($(shell uname -s),Darwin)
|
else ifeq ($(shell uname -s),Darwin)
|
||||||
MD5SUM = md5
|
MD5SUM = md5
|
||||||
LIBCLANG_NAME = clang
|
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||||
else ifeq ($(shell uname -s),FreeBSD)
|
else ifeq ($(shell uname -s),FreeBSD)
|
||||||
MD5SUM = md5
|
MD5SUM = md5
|
||||||
LIBCLANG_NAME = clang
|
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||||
START_GROUP = -Wl,--start-group
|
START_GROUP = -Wl,--start-group
|
||||||
END_GROUP = -Wl,--end-group
|
END_GROUP = -Wl,--end-group
|
||||||
else
|
else
|
||||||
LIBCLANG_NAME = clang
|
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||||
START_GROUP = -Wl,--start-group
|
START_GROUP = -Wl,--start-group
|
||||||
END_GROUP = -Wl,--end-group
|
END_GROUP = -Wl,--end-group
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Libraries that should be linked in for the statically linked Clang.
|
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++
|
||||||
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
|
||||||
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
|
||||||
|
|
||||||
# Libraries that should be linked in for the statically linked LLD.
|
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||||
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.
|
# For static linking.
|
||||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
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_CXXFLAGS=-std=c++14
|
||||||
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)
|
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++14 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm -rf build
|
@rm -rf build
|
||||||
|
|
||||||
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_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:
|
fmt:
|
||||||
@gofmt -l -w $(FMT_PATHS)
|
@gofmt -l -w $(FMT_PATHS)
|
||||||
fmt-check:
|
fmt-check:
|
||||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
@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-nrf gen-device-sam gen-device-sifive gen-device-stm32
|
||||||
|
|
||||||
gen-device-avr:
|
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
|
|
||||||
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
||||||
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
||||||
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
||||||
@@ -119,18 +129,10 @@ gen-device-avr:
|
|||||||
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||||
$(GO) build -o $@ ./tools/gen-device-svd/
|
$(GO) build -o $@ ./tools/gen-device-svd/
|
||||||
|
|
||||||
gen-device-esp: build/gen-device-svd
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/esp
|
|
||||||
|
|
||||||
gen-device-nrf: build/gen-device-svd
|
gen-device-nrf: build/gen-device-svd
|
||||||
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||||
|
|
||||||
gen-device-nxp: build/gen-device-svd
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/nxp
|
|
||||||
|
|
||||||
gen-device-sam: build/gen-device-svd
|
gen-device-sam: build/gen-device-svd
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||||
@@ -139,70 +141,48 @@ gen-device-sifive: build/gen-device-svd
|
|||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||||
|
|
||||||
gen-device-kendryte: build/gen-device-svd
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/kendryte
|
|
||||||
|
|
||||||
gen-device-stm32: build/gen-device-svd
|
gen-device-stm32: build/gen-device-svd
|
||||||
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||||
|
|
||||||
|
|
||||||
# Get LLVM sources.
|
# Get LLVM sources.
|
||||||
$(LLVM_PROJECTDIR)/README.md:
|
$(LLVM_PROJECTDIR)/README.md:
|
||||||
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
git clone -b release/10.x https://github.com/llvm/llvm-project $(LLVM_PROJECTDIR)
|
||||||
llvm-source: $(LLVM_PROJECTDIR)/README.md
|
llvm-source: $(LLVM_PROJECTDIR)/README.md
|
||||||
|
|
||||||
# Configure LLVM.
|
# Configure LLVM.
|
||||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
$(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_LIBXML2=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" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||||
|
|
||||||
# Build LLVM.
|
# Build LLVM.
|
||||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||||
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
|
cd $(LLVM_BUILDDIR); ninja
|
||||||
|
|
||||||
|
|
||||||
# Build wasi-libc sysroot
|
# Build wasi-libc sysroot
|
||||||
.PHONY: wasi-libc
|
.PHONY: wasi-libc
|
||||||
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
|
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
|
||||||
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
|
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
|
||||||
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
|
||||||
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
||||||
|
|
||||||
|
|
||||||
# Build the Go compiler.
|
# Build the Go compiler.
|
||||||
tinygo:
|
tinygo:
|
||||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||||
|
|
||||||
test: wasi-libc
|
test: wasi-libc
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||||
|
|
||||||
# Test known-working standard library packages.
|
|
||||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
|
||||||
# implied -v flag).
|
|
||||||
.PHONY: tinygo-test
|
|
||||||
tinygo-test:
|
tinygo-test:
|
||||||
$(TINYGO) test container/heap
|
cd tests/tinygotest && tinygo test
|
||||||
$(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/adler32
|
|
||||||
$(TINYGO) test hash/fnv
|
|
||||||
$(TINYGO) test hash/crc64
|
|
||||||
$(TINYGO) test math
|
|
||||||
$(TINYGO) test math/cmplx
|
|
||||||
$(TINYGO) test text/scanner
|
|
||||||
$(TINYGO) test unicode/utf8
|
|
||||||
|
|
||||||
.PHONY: smoketest
|
.PHONY: smoketest
|
||||||
smoketest:
|
smoketest:
|
||||||
$(TINYGO) version
|
$(TINYGO) version
|
||||||
# test all examples (except pwm)
|
# test all examples
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
||||||
@@ -223,9 +203,7 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
@@ -236,6 +214,8 @@ smoketest:
|
|||||||
# test simulated boards on play.tinygo.org
|
# test simulated boards on play.tinygo.org
|
||||||
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
||||||
@$(MD5SUM) test.wasm
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=hifive1-qemu examples/serial
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||||
@$(MD5SUM) test.wasm
|
@$(MD5SUM) test.wasm
|
||||||
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||||
@@ -251,8 +231,6 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||||
@$(MD5SUM) test.hex
|
@$(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=nrf52840-mdk examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||||
@@ -279,15 +257,11 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||||
@$(MD5SUM) test.hex
|
@$(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
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=clue_alpha examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
||||||
@$(MD5SUM) test.gba
|
@$(MD5SUM) test.gba
|
||||||
@@ -311,56 +285,17 @@ smoketest:
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(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=x9pro examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/dac
|
|
||||||
@$(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=itsybitsy-nrf52840 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
|
|
||||||
@$(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=nucleo-f722ze 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=p1am-100 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
# test pwm
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm
|
|
||||||
@$(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
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
ifneq ($(AVR), 0)
|
ifneq ($(AVR), 0)
|
||||||
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
||||||
@@ -369,33 +304,13 @@ ifneq ($(AVR), 0)
|
|||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
endif
|
|
||||||
ifneq ($(XTENSA), 0)
|
|
||||||
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
|
|
||||||
@$(MD5SUM) test.bin
|
|
||||||
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
|
|
||||||
@$(MD5SUM) test.bin
|
|
||||||
endif
|
endif
|
||||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||||
@$(MD5SUM) test.hex
|
@$(MD5SUM) test.hex
|
||||||
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
# test various compiler flags
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
|
|
||||||
@$(MD5SUM) test.nro
|
|
||||||
|
|
||||||
wasmtest:
|
release: tinygo gen-device wasi-libc
|
||||||
$(GO) test ./tests/wasm
|
|
||||||
|
|
||||||
build/release: tinygo gen-device wasi-libc
|
|
||||||
@mkdir -p build/release/tinygo/bin
|
@mkdir -p build/release/tinygo/bin
|
||||||
@mkdir -p build/release/tinygo/lib/clang/include
|
@mkdir -p build/release/tinygo/lib/clang/include
|
||||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@@ -430,13 +345,4 @@ build/release: tinygo gen-device wasi-libc
|
|||||||
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
|
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
|
||||||
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
|
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
|
||||||
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
|
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
|
||||||
|
|
||||||
release: build/release
|
|
||||||
tar -czf build/release.tar.gz -C build/release tinygo
|
tar -czf build/release.tar.gz -C build/release tinygo
|
||||||
|
|
||||||
deb: build/release
|
|
||||||
@mkdir -p build/release-deb/usr/local/bin
|
|
||||||
@mkdir -p build/release-deb/usr/local/lib
|
|
||||||
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
|
|
||||||
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
|
|
||||||
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
|
|
||||||
|
|||||||
@@ -43,41 +43,27 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
|||||||
|
|
||||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||||
|
|
||||||
The following 53 microcontroller boards are currently supported:
|
The following 32 microcontroller boards are currently supported:
|
||||||
|
|
||||||
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
||||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||||
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
|
* [Adafruit CLUE Alpha](https://www.adafruit.com/product/4500)
|
||||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||||
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
|
|
||||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
|
||||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
* [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 Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
* [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 PyPortal](https://www.adafruit.com/product/4116)
|
||||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
* [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 Nano](https://store.arduino.cc/arduino-nano)
|
||||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
* [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](https://microbit.org/)
|
||||||
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
|
|
||||||
* [Digispark](http://digistump.com/products/1)
|
* [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)
|
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
|
||||||
* [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 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 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 PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||||
@@ -86,16 +72,9 @@ The following 53 microcontroller boards are currently supported:
|
|||||||
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
||||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
|
||||||
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
|
||||||
* [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)
|
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||||
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
* [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 STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||||
* [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)
|
* [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/)
|
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||||
|
|
||||||
@@ -157,6 +136,4 @@ 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.
|
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/11.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/10.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
-14
@@ -1,7 +1,7 @@
|
|||||||
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||||
# be built anyway.
|
# be built anyway.
|
||||||
trigger:
|
trigger:
|
||||||
- release
|
- master
|
||||||
- dev
|
- dev
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -12,13 +12,12 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- task: GoTool@0
|
- task: GoTool@0
|
||||||
inputs:
|
inputs:
|
||||||
version: '1.16'
|
version: '1.14.1'
|
||||||
- checkout: self
|
- checkout: self
|
||||||
fetchDepth: 1
|
- task: CacheBeta@0
|
||||||
- task: Cache@2
|
|
||||||
displayName: Cache LLVM source
|
displayName: Cache LLVM source
|
||||||
inputs:
|
inputs:
|
||||||
key: llvm-source-11-windows-v1
|
key: llvm-source-10-windows-v0
|
||||||
path: llvm-project
|
path: llvm-project
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Download LLVM source
|
displayName: Download LLVM source
|
||||||
@@ -28,7 +27,7 @@ jobs:
|
|||||||
- task: CacheBeta@0
|
- task: CacheBeta@0
|
||||||
displayName: Cache LLVM build
|
displayName: Cache LLVM build
|
||||||
inputs:
|
inputs:
|
||||||
key: llvm-build-11-windows-v4
|
key: llvm-build-10-windows-v0
|
||||||
path: llvm-build
|
path: llvm-build
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Build LLVM
|
displayName: Build LLVM
|
||||||
@@ -37,22 +36,18 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
then
|
then
|
||||||
# install dependencies
|
|
||||||
choco install ninja
|
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
|
make llvm-build
|
||||||
fi
|
fi
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Install QEMU
|
displayName: Install QEMU
|
||||||
inputs:
|
inputs:
|
||||||
targetType: inline
|
targetType: inline
|
||||||
script: choco install qemu --version=2020.06.12
|
script: choco install qemu
|
||||||
- task: CacheBeta@0
|
- task: CacheBeta@0
|
||||||
displayName: Cache wasi-libc sysroot
|
displayName: Cache wasi-libc sysroot
|
||||||
inputs:
|
inputs:
|
||||||
key: wasi-libc-sysroot-v4
|
key: wasi-libc-sysroot-v2
|
||||||
path: lib/wasi-libc/sysroot
|
path: lib/wasi-libc/sysroot
|
||||||
- task: Bash@3
|
- task: Bash@3
|
||||||
displayName: Build wasi-libc
|
displayName: Build wasi-libc
|
||||||
@@ -74,7 +69,7 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
unset GOROOT
|
unset GOROOT
|
||||||
make build/release -j4
|
make release -j4
|
||||||
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
|
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
|
||||||
displayName: Publish zip as artifact
|
displayName: Publish zip as artifact
|
||||||
artifact: tinygo
|
artifact: tinygo
|
||||||
@@ -85,4 +80,4 @@ jobs:
|
|||||||
script: |
|
script: |
|
||||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
unset GOROOT
|
unset GOROOT
|
||||||
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
make smoketest TINYGO=build/tinygo AVR=0
|
||||||
|
|||||||
+140
-594
@@ -4,15 +4,11 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -20,359 +16,25 @@ import (
|
|||||||
"github.com/tinygo-org/tinygo/compiler"
|
"github.com/tinygo-org/tinygo/compiler"
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
"github.com/tinygo-org/tinygo/interp"
|
"github.com/tinygo-org/tinygo/interp"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"github.com/tinygo-org/tinygo/stacksize"
|
|
||||||
"github.com/tinygo-org/tinygo/transform"
|
"github.com/tinygo-org/tinygo/transform"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BuildResult is the output of a build. This includes the binary itself and
|
|
||||||
// some other metadata that is obtained while building the binary.
|
|
||||||
type BuildResult struct {
|
|
||||||
// A path to the output binary. It will be removed after Build returns, so
|
|
||||||
// if it should be kept it must be copied or moved away.
|
|
||||||
Binary string
|
|
||||||
|
|
||||||
// The directory of the main package. This is useful for testing as the test
|
|
||||||
// binary must be run in the directory of the tested package.
|
|
||||||
MainDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build performs a single package to executable Go build. It takes in a package
|
// Build performs a single package to executable Go build. It takes in a package
|
||||||
// name, an output path, and set of compile options and from that it manages the
|
// name, an output path, and set of compile options and from that it manages the
|
||||||
// whole compilation process.
|
// whole compilation process.
|
||||||
//
|
//
|
||||||
// The error value may be of type *MultiError. Callers will likely want to check
|
// The error value may be of type *MultiError. Callers will likely want to check
|
||||||
// for this case and print such errors individually.
|
// for this case and print such errors individually.
|
||||||
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
|
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
|
||||||
compilerConfig := &compiler.Config{
|
// Compile Go code to IR.
|
||||||
Triple: config.Triple(),
|
machine, err := compiler.NewTargetMachine(config)
|
||||||
CPU: config.CPU(),
|
|
||||||
Features: config.Features(),
|
|
||||||
GOOS: config.GOOS(),
|
|
||||||
GOARCH: config.GOARCH(),
|
|
||||||
CodeModel: config.CodeModel(),
|
|
||||||
RelocationModel: config.RelocationModel(),
|
|
||||||
|
|
||||||
Scheduler: config.Scheduler(),
|
|
||||||
FuncImplementation: config.FuncImplementation(),
|
|
||||||
AutomaticStackSize: config.AutomaticStackSize(),
|
|
||||||
DefaultStackSize: config.Target.DefaultStackSize,
|
|
||||||
NeedsStackObjects: config.NeedsStackObjects(),
|
|
||||||
Debug: config.Debug(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the target machine, which is the LLVM object that contains all
|
|
||||||
// details of a target (alignment restrictions, pointer size, default
|
|
||||||
// address spaces, etc).
|
|
||||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
mod, extraFiles, errs := compiler.Compile(pkgName, machine, config)
|
||||||
// Load entire program AST into memory.
|
|
||||||
lprogram, err := loader.Load(config, []string{pkgName}, config.ClangHeaders, types.Config{
|
|
||||||
Sizes: compiler.Sizes(machine),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = lprogram.Parse()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The slice of jobs that orchestrates most of the build.
|
|
||||||
// This is somewhat like an in-memory Makefile with each job being a
|
|
||||||
// Makefile target.
|
|
||||||
var jobs []*compileJob
|
|
||||||
|
|
||||||
// Add job to compile and optimize all Go files at once.
|
|
||||||
// TODO: parallelize this.
|
|
||||||
var mod llvm.Module
|
|
||||||
var stackSizeLoads []string
|
|
||||||
programJob := &compileJob{
|
|
||||||
description: "compile Go files",
|
|
||||||
run: func() (err error) {
|
|
||||||
mod, err = compileWholeProgram(pkgName, config, compilerConfig, lprogram, machine)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Make sure stack sizes are loaded from a separate section so they can be
|
|
||||||
// modified after linking.
|
|
||||||
if config.AutomaticStackSize() {
|
|
||||||
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, programJob)
|
|
||||||
|
|
||||||
// Check whether we only need to create an object file.
|
|
||||||
// If so, we don't need to link anything and will be finished quickly.
|
|
||||||
outext := filepath.Ext(outpath)
|
|
||||||
if outext == ".o" || outext == ".bc" || outext == ".ll" {
|
|
||||||
// Run jobs to produce the LLVM module.
|
|
||||||
err := runJobs(jobs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Generate output.
|
|
||||||
switch outext {
|
|
||||||
case ".o":
|
|
||||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
|
||||||
case ".bc":
|
|
||||||
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
|
|
||||||
return ioutil.WriteFile(outpath, data, 0666)
|
|
||||||
case ".ll":
|
|
||||||
data := []byte(mod.String())
|
|
||||||
return ioutil.WriteFile(outpath, data, 0666)
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Act as a compiler driver, as we need to produce a complete executable.
|
|
||||||
// First add all jobs necessary to build this object file, then afterwards
|
|
||||||
// run all jobs in parallel as far as possible.
|
|
||||||
|
|
||||||
// Create a temporary directory for intermediary files.
|
|
||||||
dir, err := ioutil.TempDir("", "tinygo")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
// Add job to write the output object file.
|
|
||||||
objfile := filepath.Join(dir, "main.o")
|
|
||||||
outputObjectFileJob := &compileJob{
|
|
||||||
description: "generate output file",
|
|
||||||
dependencies: []*compileJob{programJob},
|
|
||||||
run: func() error {
|
|
||||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, outputObjectFileJob)
|
|
||||||
|
|
||||||
// Prepare link command.
|
|
||||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
|
||||||
executable := filepath.Join(dir, "main")
|
|
||||||
tmppath := executable // final file
|
|
||||||
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
|
||||||
|
|
||||||
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
|
||||||
// a cache.
|
|
||||||
if config.Target.RTLib == "compiler-rt" {
|
|
||||||
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if job != nil {
|
|
||||||
// The library was not loaded from cache so needs to be compiled
|
|
||||||
// (and then stored in the cache).
|
|
||||||
jobs = append(jobs, job.dependencies...)
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
linkerDependencies = append(linkerDependencies, job)
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add libc dependency if needed.
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
switch config.Target.Libc {
|
|
||||||
case "picolibc":
|
|
||||||
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if job != nil {
|
|
||||||
// The library needs to be compiled (cache miss).
|
|
||||||
jobs = append(jobs, job.dependencies...)
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
linkerDependencies = append(linkerDependencies, job)
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, path)
|
|
||||||
case "wasi-libc":
|
|
||||||
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
||||||
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, path)
|
|
||||||
case "":
|
|
||||||
// no library specified, so nothing to do
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add jobs to compile extra files. These files are in C or assembly and
|
|
||||||
// contain things like the interrupt vector table and low level operations
|
|
||||||
// such as stack switching.
|
|
||||||
for i, path := range config.ExtraFiles() {
|
|
||||||
abspath := filepath.Join(root, path)
|
|
||||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
|
||||||
job := &compileJob{
|
|
||||||
description: "compile extra file " + path,
|
|
||||||
run: func() error {
|
|
||||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", path, err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
linkerDependencies = append(linkerDependencies, job)
|
|
||||||
ldflags = append(ldflags, outpath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add jobs to compile C files in all packages. This is part of CGo.
|
|
||||||
// TODO: do this as part of building the package to be able to link the
|
|
||||||
// bitcode files together.
|
|
||||||
for i, pkg := range lprogram.Sorted() {
|
|
||||||
for j, filename := range pkg.CFiles {
|
|
||||||
file := filepath.Join(pkg.Dir, filename)
|
|
||||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o")
|
|
||||||
job := &compileJob{
|
|
||||||
description: "compile CGo file " + file,
|
|
||||||
run: func() error {
|
|
||||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", file, err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
linkerDependencies = append(linkerDependencies, job)
|
|
||||||
ldflags = append(ldflags, outpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linker flags from CGo lines:
|
|
||||||
// #cgo LDFLAGS: foo
|
|
||||||
if len(lprogram.LDFlags) > 0 {
|
|
||||||
ldflags = append(ldflags, lprogram.LDFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a linker job, which links all object files together and does some
|
|
||||||
// extra stuff that can only be done after linking.
|
|
||||||
jobs = append(jobs, &compileJob{
|
|
||||||
description: "link",
|
|
||||||
dependencies: linkerDependencies,
|
|
||||||
run: func() error {
|
|
||||||
err = link(config.Target.Linker, ldflags...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to link", executable, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
var calculatedStacks []string
|
|
||||||
var stackSizes map[string]functionStackSize
|
|
||||||
if config.Options.PrintStacks || config.AutomaticStackSize() {
|
|
||||||
// Try to determine stack sizes at compile time.
|
|
||||||
// Don't do this by default as it usually doesn't work on
|
|
||||||
// unsupported architectures.
|
|
||||||
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.AutomaticStackSize() {
|
|
||||||
// Modify the .tinygo_stacksizes section that contains a stack size
|
|
||||||
// for each goroutine.
|
|
||||||
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not modify stack sizes: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
|
||||||
sizes, err := loadProgramSize(executable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if config.Options.PrintSizes == "short" {
|
|
||||||
fmt.Printf(" code data bss | flash ram\n")
|
|
||||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
|
||||||
for _, name := range sizes.sortedPackageNames() {
|
|
||||||
pkgSize := sizes.Packages[name]
|
|
||||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
|
||||||
}
|
|
||||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
|
||||||
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Print goroutine stack sizes, as far as possible.
|
|
||||||
if config.Options.PrintStacks {
|
|
||||||
printStacks(calculatedStacks, stackSizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// Run all jobs to compile and link the program.
|
|
||||||
// Do this now (instead of after elf-to-hex and similar conversions) as it
|
|
||||||
// is simpler and cannot be parallelized.
|
|
||||||
err = runJobs(jobs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an Intel .hex file or .bin file from the .elf file.
|
|
||||||
outputBinaryFormat := config.BinaryFormat(outext)
|
|
||||||
switch outputBinaryFormat {
|
|
||||||
case "elf":
|
|
||||||
// do nothing, file is already in ELF format
|
|
||||||
case "hex", "bin":
|
|
||||||
// Extract raw binary, either encoding it as a hex file or as a raw
|
|
||||||
// firmware file.
|
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
|
||||||
err := objcopy(executable, tmppath, outputBinaryFormat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "uf2":
|
|
||||||
// Get UF2 from the .elf file.
|
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
|
||||||
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case "esp32", "esp8266":
|
|
||||||
// Special format for the ESP family of chips (parsed by the ROM
|
|
||||||
// bootloader).
|
|
||||||
tmppath = filepath.Join(dir, "main"+outext)
|
|
||||||
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
|
|
||||||
}
|
|
||||||
return action(BuildResult{
|
|
||||||
Binary: tmppath,
|
|
||||||
MainDir: lprogram.MainPkg().Dir,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// compileWholeProgram compiles the entire *loader.Program to a LLVM module and
|
|
||||||
// applies most necessary optimizations and transformations.
|
|
||||||
func compileWholeProgram(pkgName string, config *compileopts.Config, compilerConfig *compiler.Config, lprogram *loader.Program, machine llvm.TargetMachine) (llvm.Module, error) {
|
|
||||||
// Compile AST to IR.
|
|
||||||
mod, errs := compiler.CompileProgram(lprogram, machine, compilerConfig, config.DumpSSA())
|
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
return mod, newMultiError(errs)
|
return newMultiError(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Options.PrintIR {
|
if config.Options.PrintIR {
|
||||||
@@ -380,15 +42,15 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon
|
|||||||
fmt.Println(mod.String())
|
fmt.Println(mod.String())
|
||||||
}
|
}
|
||||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||||
return mod, errors.New("verification error after IR construction")
|
return errors.New("verification error after IR construction")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := interp.Run(mod, config.DumpSSA())
|
err = interp.Run(mod, config.DumpSSA())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mod, err
|
return err
|
||||||
}
|
}
|
||||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||||
return mod, errors.New("verification error after interpreting runtime.initAll")
|
return errors.New("verification error after interpreting runtime.initAll")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.GOOS() != "darwin" {
|
if config.GOOS() != "darwin" {
|
||||||
@@ -400,10 +62,10 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon
|
|||||||
// keep functions interoperable, pass int64 types as pointers to
|
// keep functions interoperable, pass int64 types as pointers to
|
||||||
// stack-allocated values.
|
// stack-allocated values.
|
||||||
// Use -wasm-abi=generic to disable this behaviour.
|
// Use -wasm-abi=generic to disable this behaviour.
|
||||||
if config.WasmAbi() == "js" {
|
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
|
||||||
err := transform.ExternalInt64AsPtr(mod)
|
err := transform.ExternalInt64AsPtr(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mod, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,15 +73,8 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon
|
|||||||
// exactly.
|
// exactly.
|
||||||
errs = nil
|
errs = nil
|
||||||
switch config.Options.Opt {
|
switch config.Options.Opt {
|
||||||
/*
|
case "none", "0":
|
||||||
Currently, turning optimizations off causes compile failures.
|
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
|
||||||
We rely on the optimizer removing some dead symbols.
|
|
||||||
Avoid providing an option that does not work right now.
|
|
||||||
In the future once everything has been fixed we can re-enable this.
|
|
||||||
|
|
||||||
case "none", "0":
|
|
||||||
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
|
|
||||||
*/
|
|
||||||
case "1":
|
case "1":
|
||||||
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
|
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
|
||||||
case "2":
|
case "2":
|
||||||
@@ -432,255 +87,146 @@ func compileWholeProgram(pkgName string, config *compileopts.Config, compilerCon
|
|||||||
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
||||||
}
|
}
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return mod, newMultiError(errs)
|
return newMultiError(errs)
|
||||||
}
|
}
|
||||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||||
return mod, errors.New("verification failure after LLVM optimization passes")
|
return errors.New("verification failure after LLVM optimization passes")
|
||||||
}
|
}
|
||||||
|
|
||||||
// LLVM 11 by default tries to emit tail calls (even with the target feature
|
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
||||||
// disabled) unless it is explicitly disabled with a function attribute.
|
// know. As a temporary fix, load all global variables in RAM.
|
||||||
// This is a problem, as it tries to emit them and prints an error when it
|
// In the future, there should be a compiler pass that determines which
|
||||||
// can't with this feature disabled.
|
// pointers are flash and which are in RAM so that pointers can have a
|
||||||
// Because as of september 2020 tail calls are not yet widely supported,
|
// correct address space parameter (address space 1 is for flash).
|
||||||
// they need to be disabled until they are widely supported (at which point
|
if strings.HasPrefix(config.Triple(), "avr") {
|
||||||
// the +tail-call target feautre can be set).
|
transform.NonConstGlobals(mod)
|
||||||
if strings.HasPrefix(config.Triple(), "wasm") {
|
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||||
transform.DisableTailCalls(mod)
|
return errors.New("verification error after making all globals non-constant on AVR")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return mod, nil
|
// Generate output.
|
||||||
}
|
outext := filepath.Ext(outpath)
|
||||||
|
switch outext {
|
||||||
|
case ".o":
|
||||||
|
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
||||||
|
case ".bc":
|
||||||
|
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
|
||||||
|
return ioutil.WriteFile(outpath, data, 0666)
|
||||||
|
case ".ll":
|
||||||
|
data := []byte(mod.String())
|
||||||
|
return ioutil.WriteFile(outpath, data, 0666)
|
||||||
|
default:
|
||||||
|
// Act as a compiler driver.
|
||||||
|
|
||||||
// functionStackSizes keeps stack size information about a single function
|
// Create a temporary directory for intermediary files.
|
||||||
// (usually a goroutine).
|
dir, err := ioutil.TempDir("", "tinygo")
|
||||||
type functionStackSize struct {
|
if err != nil {
|
||||||
humanName string
|
return err
|
||||||
stackSize uint64
|
}
|
||||||
stackSizeType stacksize.SizeType
|
defer os.RemoveAll(dir)
|
||||||
missingStackSize *stacksize.CallNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// determineStackSizes tries to determine the stack sizes of all started
|
// Write the object file.
|
||||||
// goroutines and of the reset vector. The LLVM module is necessary to find
|
objfile := filepath.Join(dir, "main.o")
|
||||||
// functions that call a function pointer.
|
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||||
func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) {
|
if err != nil {
|
||||||
var callsIndirectFunction []string
|
return err
|
||||||
gowrappers := []string{}
|
}
|
||||||
gowrapperNames := make(map[string]string)
|
err = ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
if err != nil {
|
||||||
// Determine which functions call a function pointer.
|
return err
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
}
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
||||||
if inst.IsACallInst().IsNil() {
|
// Prepare link command.
|
||||||
continue
|
executable := filepath.Join(dir, "main")
|
||||||
}
|
tmppath := executable // final file
|
||||||
if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() {
|
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||||
callsIndirectFunction = append(callsIndirectFunction, fn.Name())
|
|
||||||
|
// Load builtins library from the cache, possibly compiling it on the
|
||||||
|
// fly.
|
||||||
|
if config.Target.RTLib == "compiler-rt" {
|
||||||
|
librt, err := CompilerRT.Load(config.Triple())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, librt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add libc.
|
||||||
|
if config.Target.Libc == "picolibc" {
|
||||||
|
libc, err := Picolibc.Load(config.Triple())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, libc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile extra files.
|
||||||
|
root := goenv.Get("TINYGOROOT")
|
||||||
|
for i, path := range config.ExtraFiles() {
|
||||||
|
abspath := filepath.Join(root, path)
|
||||||
|
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||||
|
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to build", path, err}
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, outpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile C files in packages.
|
||||||
|
for i, file := range extraFiles {
|
||||||
|
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+filepath.Base(file)+".o")
|
||||||
|
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to build", file, err}
|
||||||
|
}
|
||||||
|
ldflags = append(ldflags, outpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link the object files together.
|
||||||
|
err = link(config.Target.Linker, ldflags...)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to link", executable, err}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||||
|
sizes, err := loadProgramSize(executable)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if config.Options.PrintSizes == "short" {
|
||||||
|
fmt.Printf(" code data bss | flash ram\n")
|
||||||
|
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||||
|
} else {
|
||||||
|
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
||||||
|
for _, name := range sizes.sortedPackageNames() {
|
||||||
|
pkgSize := sizes.Packages[name]
|
||||||
|
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
||||||
|
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of "go wrappers", small wrapper functions that decode
|
// Get an Intel .hex file or .bin file from the .elf file.
|
||||||
// parameters when starting a new goroutine.
|
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
||||||
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper")
|
tmppath = filepath.Join(dir, "main"+outext)
|
||||||
if !attr.IsNil() {
|
err := objcopy(executable, tmppath)
|
||||||
gowrappers = append(gowrappers, fn.Name())
|
if err != nil {
|
||||||
gowrapperNames[fn.Name()] = attr.GetStringValue()
|
return err
|
||||||
}
|
}
|
||||||
}
|
} else if outext == ".uf2" {
|
||||||
sort.Strings(gowrappers)
|
// Get UF2 from the .elf file.
|
||||||
|
tmppath = filepath.Join(dir, "main"+outext)
|
||||||
// Load the ELF binary.
|
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
|
||||||
f, err := elf.Open(executable)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// Determine the frame size of each function (if available) and the callgraph.
|
|
||||||
functions, err := stacksize.CallGraph(f, callsIndirectFunction)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Goroutines need to be started and finished and take up some stack space
|
|
||||||
// that way. This can be measured by measuing the stack size of
|
|
||||||
// tinygo_startTask.
|
|
||||||
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
|
|
||||||
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
|
|
||||||
}
|
|
||||||
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
|
|
||||||
|
|
||||||
sizes := make(map[string]functionStackSize)
|
|
||||||
|
|
||||||
// Add the reset handler function, for convenience. The reset handler runs
|
|
||||||
// startup code and the scheduler. The listed stack size is not the full
|
|
||||||
// stack size: interrupts are not counted.
|
|
||||||
var resetFunction string
|
|
||||||
switch f.Machine {
|
|
||||||
case elf.EM_ARM:
|
|
||||||
// Note: all interrupts happen on this stack so the real size is bigger.
|
|
||||||
resetFunction = "Reset_Handler"
|
|
||||||
}
|
|
||||||
if resetFunction != "" {
|
|
||||||
funcs := functions[resetFunction]
|
|
||||||
if len(funcs) != 1 {
|
|
||||||
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs))
|
|
||||||
}
|
|
||||||
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
|
|
||||||
sizes[resetFunction] = functionStackSize{
|
|
||||||
stackSize: stackSize,
|
|
||||||
stackSizeType: stackSizeType,
|
|
||||||
missingStackSize: missingStackSize,
|
|
||||||
humanName: resetFunction,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all goroutine wrapper functions.
|
|
||||||
for _, name := range gowrappers {
|
|
||||||
funcs := functions[name]
|
|
||||||
if len(funcs) != 1 {
|
|
||||||
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs))
|
|
||||||
}
|
|
||||||
humanName := gowrapperNames[name]
|
|
||||||
if humanName == "" {
|
|
||||||
humanName = name // fallback
|
|
||||||
}
|
|
||||||
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
|
|
||||||
if baseStackSizeType != stacksize.Bounded {
|
|
||||||
// It was not possible to determine the stack size at compile time
|
|
||||||
// because tinygo_startTask does not have a fixed stack size. This
|
|
||||||
// can happen when using -opt=1.
|
|
||||||
stackSizeType = baseStackSizeType
|
|
||||||
missingStackSize = baseStackSizeFailedAt
|
|
||||||
} else if stackSize < baseStackSize {
|
|
||||||
// This goroutine has a very small stack, but still needs to fit all
|
|
||||||
// registers to start and suspend the goroutine. Otherwise a stack
|
|
||||||
// overflow will occur even before the goroutine is started.
|
|
||||||
stackSize = baseStackSize
|
|
||||||
}
|
|
||||||
sizes[name] = functionStackSize{
|
|
||||||
stackSize: stackSize,
|
|
||||||
stackSizeType: stackSizeType,
|
|
||||||
missingStackSize: missingStackSize,
|
|
||||||
humanName: humanName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resetFunction != "" {
|
|
||||||
return append([]string{resetFunction}, gowrappers...), sizes, nil
|
|
||||||
}
|
|
||||||
return gowrappers, sizes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// modifyStackSizes modifies the .tinygo_stacksizes section with the updated
|
|
||||||
// stack size information. Before this modification, all stack sizes in the
|
|
||||||
// section assume the default stack size (which is relatively big).
|
|
||||||
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
|
|
||||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
elfFile, err := elf.NewFile(fp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
section := elfFile.Section(".tinygo_stacksizes")
|
|
||||||
if section == nil {
|
|
||||||
return errors.New("could not find .tinygo_stacksizes section")
|
|
||||||
}
|
|
||||||
|
|
||||||
if section.Size != section.FileSize {
|
|
||||||
// Sanity check.
|
|
||||||
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read all goroutine stack sizes.
|
|
||||||
data := make([]byte, section.Size)
|
|
||||||
_, err = fp.ReadAt(data, int64(section.Offset))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(stackSizeLoads)*4 != len(data) {
|
|
||||||
// Note: while AVR should use 2 byte stack sizes, even 64-bit platforms
|
|
||||||
// should probably stick to 4 byte stack sizes as a larger than 4GB
|
|
||||||
// stack doesn't make much sense.
|
|
||||||
return errors.New("expected 4 byte stack sizes")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modify goroutine stack sizes with a compile-time known worst case stack
|
|
||||||
// size.
|
|
||||||
for i, name := range stackSizeLoads {
|
|
||||||
fn, ok := stackSizes[name]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("could not find symbol %s in ELF file", name)
|
|
||||||
}
|
|
||||||
if fn.stackSizeType == stacksize.Bounded {
|
|
||||||
stackSize := uint32(fn.stackSize)
|
|
||||||
|
|
||||||
// Adding 4 for the stack canary. Even though the size may be
|
|
||||||
// automatically determined, stack overflow checking is still
|
|
||||||
// important as the stack size cannot be determined for all
|
|
||||||
// goroutines.
|
|
||||||
stackSize += 4
|
|
||||||
|
|
||||||
// Add stack size used by interrupts.
|
|
||||||
switch elfFile.Machine {
|
|
||||||
case elf.EM_ARM:
|
|
||||||
// On Cortex-M (assumed here), this stack size is 8 words or 32
|
|
||||||
// bytes. This is only to store the registers that the interrupt
|
|
||||||
// may modify, the interrupt will switch to the interrupt stack
|
|
||||||
// (MSP).
|
|
||||||
// Some background:
|
|
||||||
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
|
|
||||||
stackSize += 32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally write the stack size to the binary.
|
|
||||||
binary.LittleEndian.PutUint32(data[i*4:], stackSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write back the modified stack sizes.
|
|
||||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// printStacks prints the maximum stack depth for functions that are started as
|
|
||||||
// goroutines. Stack sizes cannot always be determined statically, in particular
|
|
||||||
// recursive functions and functions that call interface methods or function
|
|
||||||
// pointers may have an unknown stack depth (depending on what the optimizer
|
|
||||||
// manages to optimize away).
|
|
||||||
//
|
|
||||||
// It might print something like the following:
|
|
||||||
//
|
|
||||||
// function stack usage (in bytes)
|
|
||||||
// Reset_Handler 316
|
|
||||||
// examples/blinky2.led1 92
|
|
||||||
// runtime.run$1 300
|
|
||||||
func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) {
|
|
||||||
// Print the sizes of all stacks.
|
|
||||||
fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)")
|
|
||||||
for _, name := range calculatedStacks {
|
|
||||||
fn := stackSizes[name]
|
|
||||||
switch fn.stackSizeType {
|
|
||||||
case stacksize.Bounded:
|
|
||||||
fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize)
|
|
||||||
case stacksize.Unknown:
|
|
||||||
fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize)
|
|
||||||
case stacksize.Recursive:
|
|
||||||
fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize)
|
|
||||||
case stacksize.IndirectCall:
|
|
||||||
fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize)
|
|
||||||
}
|
}
|
||||||
|
return action(tmppath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-6
@@ -30,7 +30,10 @@ func cacheTimestamp(paths []string) (time.Time, error) {
|
|||||||
// Try to load a given file from the cache. Return "", nil if no cached file can
|
// 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
|
// be found (or the file is stale), return the absolute path if there is a cache
|
||||||
// and return an error on I/O errors.
|
// and return an error on I/O errors.
|
||||||
func cacheLoad(name string, sourceFiles []string) (string, error) {
|
//
|
||||||
|
// TODO: the configKey is currently ignored. It is supposed to be used as extra
|
||||||
|
// data for the cache key, like the compiler version and arguments.
|
||||||
|
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||||
cacheStat, err := os.Stat(cachepath)
|
cacheStat, err := os.Stat(cachepath)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
@@ -55,7 +58,9 @@ func cacheLoad(name string, sourceFiles []string) (string, error) {
|
|||||||
|
|
||||||
// Store the file located at tmppath in the cache with the given name. The
|
// Store the file located at tmppath in the cache with the given name. The
|
||||||
// tmppath may or may not be gone afterwards.
|
// tmppath may or may not be gone afterwards.
|
||||||
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
//
|
||||||
|
// Note: the configKey is ignored, see cacheLoad.
|
||||||
|
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
|
||||||
// get the last modified time
|
// get the last modified time
|
||||||
if len(sourceFiles) == 0 {
|
if len(sourceFiles) == 0 {
|
||||||
panic("cache: no source files")
|
panic("cache: no source files")
|
||||||
@@ -69,16 +74,24 @@ func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
cachepath := filepath.Join(dir, name)
|
cachepath := filepath.Join(dir, name)
|
||||||
err = copyFile(tmppath, cachepath)
|
err = moveFile(tmppath, cachepath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cachepath, nil
|
return cachepath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyFile copies the given file from src to dst. It can copy over
|
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||||
// a possibly already existing file at the destination.
|
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||||
func copyFile(src, dst string) error {
|
// the old file is removed.
|
||||||
|
func moveFile(src, dst string) error {
|
||||||
|
err := os.Rename(src, dst)
|
||||||
|
if err == nil {
|
||||||
|
// Success!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Failed to move, probably a different filesystem.
|
||||||
|
// Do a copy + remove.
|
||||||
inf, err := os.Open(src)
|
inf, err := os.Open(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+18
-21
@@ -101,7 +101,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
|
|
||||||
// Target Options
|
// Target Options
|
||||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||||
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
|
||||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||||
|
|
||||||
// Use the default target triple if unspecified.
|
// Use the default target triple if unspecified.
|
||||||
@@ -132,19 +132,13 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
|
|
||||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
||||||
Opts.DwarfDebugFlags =
|
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
|
||||||
Opts.DwarfDebugProducer =
|
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
|
||||||
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)) {
|
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
|
||||||
auto Split = StringRef(Arg).split('=');
|
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
|
||||||
Opts.DebugPrefixMap.insert(
|
|
||||||
{std::string(Split.first), std::string(Split.second)});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frontend Options
|
// Frontend Options
|
||||||
if (Args.hasArg(OPT_INPUT)) {
|
if (Args.hasArg(OPT_INPUT)) {
|
||||||
@@ -160,9 +154,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
||||||
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
|
Opts.OutputPath = Args.getLastArgValue(OPT_o);
|
||||||
Opts.SplitDwarfOutput =
|
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
|
||||||
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
|
|
||||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
||||||
StringRef Name = A->getValue();
|
StringRef Name = A->getValue();
|
||||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
unsigned OutputType = StringSwitch<unsigned>(Name)
|
||||||
@@ -190,9 +183,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
||||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
||||||
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
|
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
|
||||||
Opts.RelocationModel =
|
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
|
||||||
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
|
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
|
||||||
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
|
|
||||||
Opts.IncrementalLinkerCompatible =
|
Opts.IncrementalLinkerCompatible =
|
||||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
Args.hasArg(OPT_mincremental_linker_compatible);
|
||||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
||||||
@@ -322,7 +314,12 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
|||||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||||
|
|
||||||
// Build up the feature string from the target feature list.
|
// Build up the feature string from the target feature list.
|
||||||
std::string FS = llvm::join(Opts.Features, ",");
|
std::string FS;
|
||||||
|
if (!Opts.Features.empty()) {
|
||||||
|
FS = Opts.Features[0];
|
||||||
|
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
|
||||||
|
FS += "," + Opts.Features[i];
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<MCStreamer> Str;
|
std::unique_ptr<MCStreamer> Str;
|
||||||
|
|
||||||
@@ -386,7 +383,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
|||||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||||
Str.get()->SwitchSection(AsmLabel);
|
Str.get()->SwitchSection(AsmLabel);
|
||||||
Str.get()->emitZeros(1);
|
Str.get()->EmitZeros(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assembly to object compilation should leverage assembly info.
|
// Assembly to object compilation should leverage assembly info.
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <clang/FrontendTool/Utils.h>
|
#include <clang/FrontendTool/Utils.h>
|
||||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
||||||
#include <llvm/Option/Option.h>
|
#include <llvm/Option/Option.h>
|
||||||
#include <llvm/Support/Host.h>
|
|
||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
using namespace clang;
|
using namespace clang;
|
||||||
|
|||||||
+2
-2
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||||
}
|
}
|
||||||
if major != 1 || minor < 11 || minor > 16 {
|
if major != 1 || minor < 11 || minor > 14 {
|
||||||
return nil, fmt.Errorf("requires go version 1.11 through 1.16, got go%d.%d", major, minor)
|
return nil, fmt.Errorf("requires go version 1.11, 1.12, 1.13, or 1.14, got go%d.%d", major, minor)
|
||||||
}
|
}
|
||||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||||
return &compileopts.Config{
|
return &compileopts.Config{
|
||||||
|
|||||||
+9
-29
@@ -6,9 +6,6 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||||
@@ -29,7 +26,6 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
|||||||
|
|
||||||
// It looks like we are built with a system-installed LLVM. Do a last
|
// It looks like we are built with a system-installed LLVM. Do a last
|
||||||
// attempt: try to use Clang headers relative to the clang binary.
|
// attempt: try to use Clang headers relative to the clang binary.
|
||||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
|
||||||
for _, cmdName := range commands["clang"] {
|
for _, cmdName := range commands["clang"] {
|
||||||
binpath, err := exec.LookPath(cmdName)
|
binpath, err := exec.LookPath(cmdName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -44,38 +40,22 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
|||||||
// Example executable:
|
// Example executable:
|
||||||
// /usr/lib/llvm-9/bin/clang
|
// /usr/lib/llvm-9/bin/clang
|
||||||
// Example include path:
|
// Example include path:
|
||||||
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
|
|
||||||
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
|
||||||
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
|
|
||||||
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
|
|
||||||
// Example include path:
|
|
||||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||||
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
|
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
||||||
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
|
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
|
||||||
if err64 != nil && err32 != nil {
|
dirs, err := ioutil.ReadDir(clangVersionRoot)
|
||||||
|
if err != nil {
|
||||||
// Unexpected.
|
// Unexpected.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
dirnames := make([]string, len(dirs64)+len(dirs32))
|
dirnames := make([]string, len(dirs))
|
||||||
dirCount := 0
|
for i, d := range dirs {
|
||||||
for _, d := range dirs32 {
|
dirnames[i] = d.Name()
|
||||||
name := d.Name()
|
|
||||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
|
||||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
|
|
||||||
dirCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, d := range dirs64 {
|
|
||||||
name := d.Name()
|
|
||||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
|
||||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
|
|
||||||
dirCount++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sort.Strings(dirnames)
|
sort.Strings(dirnames)
|
||||||
// Check for the highest version first.
|
// Check for the highest version first.
|
||||||
for i := dirCount - 1; i >= 0; i-- {
|
for i := len(dirnames) - 1; i >= 0; i-- {
|
||||||
path := filepath.Join(dirnames[i], "include")
|
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
|
||||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return path
|
return path
|
||||||
|
|||||||
-153
@@ -1,153 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
// This file implements support for writing ESP image files. These image files
|
|
||||||
// are read by the ROM bootloader so have to be in a particular format.
|
|
||||||
//
|
|
||||||
// In the future, it may be necessary to implement support for other image
|
|
||||||
// formats, such as the ESP8266 image formats (again, used by the ROM bootloader
|
|
||||||
// to load the firmware).
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/sha256"
|
|
||||||
"debug/elf"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type espImageSegment struct {
|
|
||||||
addr uint32
|
|
||||||
data []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
|
|
||||||
// ESP8266 chip. This is a special purpose image format just for the ESP chip
|
|
||||||
// family, and is parsed by the on-chip mask ROM bootloader.
|
|
||||||
//
|
|
||||||
// The following documentation has been used:
|
|
||||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
|
|
||||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
|
|
||||||
// https://github.com/espressif/esptool/blob/master/esptool.py
|
|
||||||
func makeESPFirmareImage(infile, outfile, format string) error {
|
|
||||||
inf, err := elf.Open(infile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer inf.Close()
|
|
||||||
|
|
||||||
// Load all segments to be written to the image. These are actually ELF
|
|
||||||
// sections, not true ELF segments (similar to how esptool does it).
|
|
||||||
var segments []*espImageSegment
|
|
||||||
for _, section := range inf.Sections {
|
|
||||||
if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
data, err := section.Data()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to read section data: %w", err)
|
|
||||||
}
|
|
||||||
for len(data)%4 != 0 {
|
|
||||||
// Align segment to 4 bytes.
|
|
||||||
data = append(data, 0)
|
|
||||||
}
|
|
||||||
if uint64(uint32(section.Addr)) != section.Addr {
|
|
||||||
return fmt.Errorf("section address too big: 0x%x", section.Addr)
|
|
||||||
}
|
|
||||||
segments = append(segments, &espImageSegment{
|
|
||||||
addr: uint32(section.Addr),
|
|
||||||
data: data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the segments by address. This is what esptool does too.
|
|
||||||
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
|
|
||||||
|
|
||||||
// Calculate checksum over the segment data. This is used in the image
|
|
||||||
// footer.
|
|
||||||
checksum := uint8(0xef)
|
|
||||||
for _, segment := range segments {
|
|
||||||
for _, b := range segment.data {
|
|
||||||
checksum ^= b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write first to an in-memory buffer, primarily so that we can easily
|
|
||||||
// calculate a hash over the entire image.
|
|
||||||
// An added benefit is that we don't need to check for errors all the time.
|
|
||||||
outf := &bytes.Buffer{}
|
|
||||||
|
|
||||||
// Image header.
|
|
||||||
switch format {
|
|
||||||
case "esp32":
|
|
||||||
// Header format:
|
|
||||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
|
|
||||||
binary.Write(outf, binary.LittleEndian, struct {
|
|
||||||
magic uint8
|
|
||||||
segment_count uint8
|
|
||||||
spi_mode uint8
|
|
||||||
spi_speed_size uint8
|
|
||||||
entry_addr uint32
|
|
||||||
wp_pin uint8
|
|
||||||
spi_pin_drv [3]uint8
|
|
||||||
reserved [11]uint8
|
|
||||||
hash_appended bool
|
|
||||||
}{
|
|
||||||
magic: 0xE9,
|
|
||||||
segment_count: byte(len(segments)),
|
|
||||||
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
|
||||||
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
|
|
||||||
entry_addr: uint32(inf.Entry),
|
|
||||||
wp_pin: 0xEE, // disable WP pin
|
|
||||||
hash_appended: true, // add a SHA256 hash
|
|
||||||
})
|
|
||||||
case "esp8266":
|
|
||||||
// Header format:
|
|
||||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
|
|
||||||
// Basically a truncated version of the ESP32 header.
|
|
||||||
binary.Write(outf, binary.LittleEndian, struct {
|
|
||||||
magic uint8
|
|
||||||
segment_count uint8
|
|
||||||
spi_mode uint8
|
|
||||||
spi_speed_size uint8
|
|
||||||
entry_addr uint32
|
|
||||||
}{
|
|
||||||
magic: 0xE9,
|
|
||||||
segment_count: byte(len(segments)),
|
|
||||||
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
|
||||||
spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing
|
|
||||||
entry_addr: uint32(inf.Entry),
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write all segments to the image.
|
|
||||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
|
|
||||||
for _, segment := range segments {
|
|
||||||
binary.Write(outf, binary.LittleEndian, struct {
|
|
||||||
addr uint32
|
|
||||||
length uint32
|
|
||||||
}{
|
|
||||||
addr: segment.addr,
|
|
||||||
length: uint32(len(segment.data)),
|
|
||||||
})
|
|
||||||
outf.Write(segment.data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Footer, including checksum.
|
|
||||||
// The entire image size must be a multiple of 16, so pad the image to one
|
|
||||||
// byte less than that before writing the checksum.
|
|
||||||
outf.Write(make([]byte, 15-outf.Len()%16))
|
|
||||||
outf.WriteByte(checksum)
|
|
||||||
|
|
||||||
if format == "esp32" {
|
|
||||||
// SHA256 hash (to protect against image corruption, not for security).
|
|
||||||
hash := sha256.Sum256(outf.Bytes())
|
|
||||||
outf.Write(hash[:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the image to the output file.
|
|
||||||
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
|
|
||||||
}
|
|
||||||
-160
@@ -1,160 +0,0 @@
|
|||||||
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
|
|
||||||
run func() error
|
|
||||||
state jobState
|
|
||||||
err error // error if finished
|
|
||||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
err := job.run()
|
|
||||||
if err != nil {
|
|
||||||
job.err = err
|
|
||||||
}
|
|
||||||
job.duration = time.Since(start)
|
|
||||||
doneChan <- job
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+30
-78
@@ -1,6 +1,7 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -39,112 +40,63 @@ func (l *Library) sourcePaths(target string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load the library archive, possibly generating and caching it if needed.
|
// Load the library archive, possibly generating and caching it if needed.
|
||||||
// The resulting file is stored in the provided tmpdir, which is expected to be
|
func (l *Library) Load(target string) (path string, err error) {
|
||||||
// removed after the Load call.
|
|
||||||
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
|
||||||
path, job, err := l.load(target, "", tmpdir)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if job != nil {
|
|
||||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
|
||||||
err = runJobs(jobs)
|
|
||||||
}
|
|
||||||
return path, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load returns a path to the library file for the given target, loading it from
|
|
||||||
// cache if possible. It will return a non-zero compiler job if the library
|
|
||||||
// wasn't cached, this job (and its dependencies) must be run before the library
|
|
||||||
// path is valid.
|
|
||||||
// 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) (path string, job *compileJob, err error) {
|
|
||||||
// Try to load a precompiled library.
|
// Try to load a precompiled library.
|
||||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
||||||
if _, err := os.Stat(precompiledPath); err == nil {
|
if _, err := os.Stat(precompiledPath); err == nil {
|
||||||
// Found a precompiled library for this OS/architecture. Return the path
|
// Found a precompiled library for this OS/architecture. Return the path
|
||||||
// directly.
|
// directly.
|
||||||
return precompiledPath, nil, nil
|
return precompiledPath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var outfile string
|
outfile := l.name + "-" + target + ".a"
|
||||||
if cpu != "" {
|
|
||||||
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
|
||||||
} else {
|
|
||||||
outfile = l.name + "-" + target + ".a"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to fetch this library from the cache.
|
// Try to fetch this library from the cache.
|
||||||
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
if path, err := cacheLoad(outfile, commands["clang"][0], l.sourcePaths(target)); path != "" || err != nil {
|
||||||
// Cache hit.
|
// Cache hit.
|
||||||
return path, nil, err
|
return path, err
|
||||||
}
|
}
|
||||||
// Cache miss, build it now.
|
// Cache miss, build it now.
|
||||||
|
|
||||||
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
dirPrefix := "tinygo-" + l.name
|
||||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||||
err = os.Mkdir(dir, 0777)
|
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
// Precalculate the flags to the compiler invocation.
|
// 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)
|
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") {
|
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
args = append(args, "-fshort-enums", "-fomit-frame-pointer")
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(target, "riscv32-") {
|
if strings.HasPrefix(target, "riscv32-") {
|
||||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(target, "riscv64-") {
|
|
||||||
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create job to put all the object files in a single archive. This archive
|
// Compile all sources.
|
||||||
// file is the (static) library file.
|
|
||||||
var objs []string
|
var objs []string
|
||||||
arpath := filepath.Join(dir, l.name+".a")
|
|
||||||
job = &compileJob{
|
|
||||||
description: "ar " + l.name + ".a",
|
|
||||||
run: func() 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) {
|
for _, srcpath := range l.sourcePaths(target) {
|
||||||
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
|
|
||||||
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
||||||
objs = append(objs, objpath)
|
objs = append(objs, objpath)
|
||||||
job.dependencies = append(job.dependencies, &compileJob{
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||||
description: "compile " + srcpath,
|
// reproducible. Otherwise the temporary directory is stored in the
|
||||||
run: func() error {
|
// archive itself, which varies each run.
|
||||||
var compileArgs []string
|
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
|
||||||
compileArgs = append(compileArgs, args...)
|
if err != nil {
|
||||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
return "", &commandError{"failed to build", srcpath, err}
|
||||||
err := runCCompiler("clang", compileArgs...)
|
}
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", srcpath, err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return arpath, job, 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))
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-27
@@ -4,15 +4,12 @@ import (
|
|||||||
"debug/elf"
|
"debug/elf"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/marcinbor85/gohex"
|
"github.com/marcinbor85/gohex"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
|
|
||||||
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
|
|
||||||
const maxPadBytes = 4095
|
|
||||||
|
|
||||||
// objcopyError is an error returned by functions that act like objcopy.
|
// objcopyError is an error returned by functions that act like objcopy.
|
||||||
type objcopyError struct {
|
type objcopyError struct {
|
||||||
Op string
|
Op string
|
||||||
@@ -61,7 +58,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
|||||||
|
|
||||||
progs := make(progSlice, 0, 2)
|
progs := make(progSlice, 0, 2)
|
||||||
for _, prog := range f.Progs {
|
for _, prog := range f.Progs {
|
||||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
|
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
progs = append(progs, prog)
|
progs = append(progs, prog)
|
||||||
@@ -73,19 +70,8 @@ func extractROM(path string) (uint64, []byte, error) {
|
|||||||
|
|
||||||
var rom []byte
|
var rom []byte
|
||||||
for _, prog := range progs {
|
for _, prog := range progs {
|
||||||
romEnd := progs[0].Paddr + uint64(len(rom))
|
|
||||||
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
|
||||||
// Sometimes, the linker seems to insert a bit of padding between
|
|
||||||
// segments. Simply zero-fill these parts.
|
|
||||||
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
|
||||||
}
|
|
||||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||||
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||||
if diff > maxPadBytes {
|
|
||||||
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
|
||||||
}
|
|
||||||
// Pad the difference
|
|
||||||
rom = append(rom, make([]byte, diff)...)
|
|
||||||
}
|
}
|
||||||
data, err := ioutil.ReadAll(prog.Open())
|
data, err := ioutil.ReadAll(prog.Open())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,7 +93,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
|||||||
|
|
||||||
// objcopy converts an ELF file to a different (simpler) output file format:
|
// objcopy converts an ELF file to a different (simpler) output file format:
|
||||||
// .bin or .hex. It extracts only the .text section.
|
// .bin or .hex. It extracts only the .text section.
|
||||||
func objcopy(infile, outfile, binaryFormat string) error {
|
func objcopy(infile, outfile string) error {
|
||||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -121,20 +107,24 @@ func objcopy(infile, outfile, binaryFormat string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write to the file, in the correct format.
|
// Write to the file, in the correct format.
|
||||||
switch binaryFormat {
|
switch filepath.Ext(outfile) {
|
||||||
case "hex":
|
case ".gba":
|
||||||
// Intel hex file, includes the firmware start address.
|
// The address is not stored in a .gba file.
|
||||||
|
_, err := f.Write(data)
|
||||||
|
return err
|
||||||
|
case ".bin":
|
||||||
|
// The address is not stored in a .bin file (therefore you
|
||||||
|
// should use .hex files in most cases).
|
||||||
|
_, err := f.Write(data)
|
||||||
|
return err
|
||||||
|
case ".hex":
|
||||||
mem := gohex.NewMemory()
|
mem := gohex.NewMemory()
|
||||||
err := mem.AddBinary(uint32(addr), data)
|
err := mem.AddBinary(uint32(addr), data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return objcopyError{"failed to create .hex file", err}
|
return objcopyError{"failed to create .hex file", err}
|
||||||
}
|
}
|
||||||
return mem.DumpIntelHex(f, 16)
|
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||||
case "bin":
|
return nil
|
||||||
// The start address is not stored in raw firmware files (therefore you
|
|
||||||
// should use .hex files in most cases).
|
|
||||||
_, err := f.Write(data)
|
|
||||||
return err
|
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-31
@@ -41,7 +41,6 @@ type cgoPackage struct {
|
|||||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||||
enums map[string]enumInfo
|
enums map[string]enumInfo
|
||||||
anonStructNum int
|
anonStructNum int
|
||||||
ldflags []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// constantInfo stores some information about a CGo constant found by libclang
|
// constantInfo stores some information about a CGo constant found by libclang
|
||||||
@@ -54,10 +53,9 @@ type constantInfo struct {
|
|||||||
// functionInfo stores some information about a CGo function found by libclang
|
// functionInfo stores some information about a CGo function found by libclang
|
||||||
// and declared in the AST.
|
// and declared in the AST.
|
||||||
type functionInfo struct {
|
type functionInfo struct {
|
||||||
args []paramInfo
|
args []paramInfo
|
||||||
results *ast.FieldList
|
results *ast.FieldList
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
variadic bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// paramInfo is a parameter of a CGo function (see functionInfo).
|
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||||
@@ -158,7 +156,7 @@ typedef unsigned long long _Cgo_ulonglong;
|
|||||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
// 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
|
// files. If there is one or more error, it returns these in the []error slice
|
||||||
// but still modifies the AST.
|
// but still modifies the AST.
|
||||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
|
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []error) {
|
||||||
p := &cgoPackage{
|
p := &cgoPackage{
|
||||||
dir: dir,
|
dir: dir,
|
||||||
fset: fset,
|
fset: fset,
|
||||||
@@ -185,7 +183,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
// Find the absolute path for this package.
|
// Find the absolute path for this package.
|
||||||
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, []error{
|
return nil, []error{
|
||||||
scanner.Error{
|
scanner.Error{
|
||||||
Pos: fset.Position(files[0].Pos()),
|
Pos: fset.Position(files[0].Pos()),
|
||||||
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
||||||
@@ -361,19 +359,6 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
}
|
}
|
||||||
makePathsAbsolute(flags, packagePath)
|
makePathsAbsolute(flags, packagePath)
|
||||||
cflags = append(cflags, flags...)
|
cflags = append(cflags, flags...)
|
||||||
case "LDFLAGS":
|
|
||||||
flags, err := shlex.Split(value)
|
|
||||||
if err != nil {
|
|
||||||
// TODO: find the exact location where the error happened.
|
|
||||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := checkLinkerFlags(name, flags); err != nil {
|
|
||||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
makePathsAbsolute(flags, packagePath)
|
|
||||||
p.ldflags = append(p.ldflags, flags...)
|
|
||||||
default:
|
default:
|
||||||
startPos := strings.LastIndex(line[4:colon], name) + 4
|
startPos := strings.LastIndex(line[4:colon], name) + 4
|
||||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
|
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
|
||||||
@@ -427,7 +412,7 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
|||||||
// Print the newly generated in-memory AST, for debugging.
|
// Print the newly generated in-memory AST, for debugging.
|
||||||
//ast.Print(fset, p.generated)
|
//ast.Print(fset, p.generated)
|
||||||
|
|
||||||
return p.generated, p.ldflags, p.errors
|
return p.generated, p.errors
|
||||||
}
|
}
|
||||||
|
|
||||||
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
||||||
@@ -485,16 +470,6 @@ func (p *cgoPackage) addFuncDecls() {
|
|||||||
Results: fn.results,
|
Results: fn.results,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if fn.variadic {
|
|
||||||
decl.Doc = &ast.CommentGroup{
|
|
||||||
List: []*ast.Comment{
|
|
||||||
&ast.Comment{
|
|
||||||
Slash: fn.pos,
|
|
||||||
Text: "//go:variadic",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
obj.Decl = decl
|
obj.Decl = decl
|
||||||
for i, arg := range fn.args {
|
for i, arg := range fn.args {
|
||||||
args[i] = &ast.Field{
|
args[i] = &ast.Field{
|
||||||
|
|||||||
+1
-16
@@ -5,7 +5,6 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
|
||||||
"go/format"
|
"go/format"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
@@ -42,20 +41,6 @@ func TestCGo(t *testing.T) {
|
|||||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||||
name := name // avoid a race condition
|
name := name // avoid a race condition
|
||||||
t.Run(name, func(t *testing.T) {
|
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.
|
// Read the AST in memory.
|
||||||
path := filepath.Join("testdata", name+".go")
|
path := filepath.Join("testdata", name+".go")
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
@@ -65,7 +50,7 @@ func TestCGo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process the AST with CGo.
|
// 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.
|
// Check the AST for type errors.
|
||||||
var typecheckErrors []error
|
var typecheckErrors []error
|
||||||
|
|||||||
+7
-5
@@ -152,10 +152,12 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
|||||||
return C.CXChildVisit_Continue
|
return C.CXChildVisit_Continue
|
||||||
}
|
}
|
||||||
cursorType := C.tinygo_clang_getCursorType(c)
|
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))
|
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||||
fn := &functionInfo{
|
fn := &functionInfo{
|
||||||
pos: pos,
|
pos: pos,
|
||||||
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
|
|
||||||
}
|
}
|
||||||
p.functions[name] = fn
|
p.functions[name] = fn
|
||||||
for i := 0; i < numArgs; i++ {
|
for i := 0; i < numArgs; i++ {
|
||||||
@@ -244,9 +246,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
|||||||
}
|
}
|
||||||
value := source[len(name):]
|
value := source[len(name):]
|
||||||
// Try to convert this #define into a Go constant expression.
|
// Try to convert this #define into a Go constant expression.
|
||||||
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
||||||
if scannerError != nil {
|
if err != nil {
|
||||||
p.errors = append(p.errors, *scannerError)
|
p.errors = append(p.errors, err)
|
||||||
}
|
}
|
||||||
if expr != nil {
|
if expr != nil {
|
||||||
// Parsing was successful.
|
// Parsing was successful.
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
// +build !byollvm
|
// +build !byollvm
|
||||||
// +build !llvm10
|
// +build !llvm9
|
||||||
|
|
||||||
package cgo
|
package cgo
|
||||||
|
|
||||||
/*
|
/*
|
||||||
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
|
||||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
|
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
|
||||||
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
|
||||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
|
||||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// +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"
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// +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
+2
-2
@@ -4,10 +4,10 @@
|
|||||||
// testdata/errors.go:13:23: unexpected token )
|
// testdata/errors.go:13:23: unexpected token )
|
||||||
|
|
||||||
// Type checking errors after CGo processing:
|
// Type checking errors after CGo processing:
|
||||||
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
|
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
|
||||||
// testdata/errors.go:105: unknown field z in struct literal
|
// testdata/errors.go:105: unknown field z in struct literal
|
||||||
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
|
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
|
||||||
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
|
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
Vendored
-7
@@ -21,13 +21,6 @@ package main
|
|||||||
#if defined(NOTDEFINED)
|
#if defined(NOTDEFINED)
|
||||||
#warning flag must not be defined
|
#warning flag must not be defined
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Check Compiler flags
|
|
||||||
#cgo LDFLAGS: -lc
|
|
||||||
|
|
||||||
// This flag is not valid ldflags
|
|
||||||
#cgo LDFLAGS: -does-not-exists
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
|
|||||||
Vendored
-1
@@ -1,7 +1,6 @@
|
|||||||
// CGo errors:
|
// CGo errors:
|
||||||
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
||||||
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
||||||
// testdata/flags.go:29:14: invalid flag: -does-not-exists
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|||||||
Vendored
-10
@@ -104,10 +104,6 @@ typedef struct {
|
|||||||
unsigned char e : 3;
|
unsigned char e : 3;
|
||||||
// Note that C++ allows bitfields bigger than the underlying type.
|
// Note that C++ allows bitfields bigger than the underlying type.
|
||||||
} bitfield_t;
|
} bitfield_t;
|
||||||
|
|
||||||
// Function signatures.
|
|
||||||
void variadic0();
|
|
||||||
void variadic2(int x, int y, ...);
|
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -167,9 +163,3 @@ func accessUnion() {
|
|||||||
var _ *C.int = union2d.unionfield_i()
|
var _ *C.int = union2d.unionfield_i()
|
||||||
var _ *[2]float64 = union2d.unionfield_d()
|
var _ *[2]float64 = union2d.unionfield_d()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test function signatures.
|
|
||||||
func accessFunctions() {
|
|
||||||
C.variadic0()
|
|
||||||
C.variadic2(3, 5)
|
|
||||||
}
|
|
||||||
|
|||||||
Vendored
-5
@@ -4,11 +4,6 @@ import "unsafe"
|
|||||||
|
|
||||||
var _ unsafe.Pointer
|
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.option2A = 20
|
||||||
const C.optionA = 0
|
const C.optionA = 0
|
||||||
const C.optionB = 1
|
const C.optionB = 1
|
||||||
|
|||||||
+41
-90
@@ -7,6 +7,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
@@ -21,6 +22,29 @@ type Config struct {
|
|||||||
TestConfig TestConfig
|
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.
|
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||||
func (c *Config) Triple() string {
|
func (c *Config) Triple() string {
|
||||||
return c.Target.Triple
|
return c.Target.Triple
|
||||||
@@ -94,19 +118,19 @@ func (c *Config) NeedsStackObjects() bool {
|
|||||||
switch c.GC() {
|
switch c.GC() {
|
||||||
case "conservative", "extalloc":
|
case "conservative", "extalloc":
|
||||||
for _, tag := range c.BuildTags() {
|
for _, tag := range c.BuildTags() {
|
||||||
if tag == "wasm" {
|
if tag == "baremetal" {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scheduler returns the scheduler implementation. Valid values are "none",
|
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
|
||||||
//"coroutines" and "tasks".
|
// and "tasks".
|
||||||
func (c *Config) Scheduler() string {
|
func (c *Config) Scheduler() string {
|
||||||
if c.Options.Scheduler != "" {
|
if c.Options.Scheduler != "" {
|
||||||
return c.Options.Scheduler
|
return c.Options.Scheduler
|
||||||
@@ -120,24 +144,14 @@ func (c *Config) Scheduler() string {
|
|||||||
|
|
||||||
// FuncImplementation picks an appropriate func value implementation for the
|
// FuncImplementation picks an appropriate func value implementation for the
|
||||||
// target.
|
// target.
|
||||||
func (c *Config) FuncImplementation() string {
|
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.
|
||||||
switch c.Scheduler() {
|
switch c.Scheduler() {
|
||||||
case "tasks":
|
|
||||||
// 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":
|
case "none", "coroutines":
|
||||||
// As "doubleword", but with the function pointer replaced by a unique
|
return FuncValueSwitch
|
||||||
// ID per function signature. Function values are called by using a
|
case "tasks":
|
||||||
// switch statement and choosing which function to call.
|
return FuncValueDoubleword
|
||||||
// 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:
|
default:
|
||||||
panic("unknown scheduler type")
|
panic("unknown scheduler type")
|
||||||
}
|
}
|
||||||
@@ -150,16 +164,6 @@ func (c *Config) PanicStrategy() string {
|
|||||||
return c.Options.PanicStrategy
|
return c.Options.PanicStrategy
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutomaticStackSize returns whether goroutine stack sizes should be determined
|
|
||||||
// automatically at compile time, if possible. If it is false, no attempt is
|
|
||||||
// made.
|
|
||||||
func (c *Config) AutomaticStackSize() bool {
|
|
||||||
if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
|
|
||||||
return *c.Target.AutoStackSize
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||||
// preprocessing.
|
// preprocessing.
|
||||||
func (c *Config) CFlags() []string {
|
func (c *Config) CFlags() []string {
|
||||||
@@ -172,9 +176,6 @@ func (c *Config) CFlags() []string {
|
|||||||
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
|
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
|
||||||
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
|
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
|
||||||
}
|
}
|
||||||
if c.Debug() {
|
|
||||||
cflags = append(cflags, "-g")
|
|
||||||
}
|
|
||||||
return cflags
|
return cflags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,12 @@ func (c *Config) LDFlags() []string {
|
|||||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||||
}
|
}
|
||||||
ldflags = append(ldflags, "-L", 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 != "" {
|
if c.Target.LinkerScript != "" {
|
||||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||||
}
|
}
|
||||||
@@ -219,31 +226,6 @@ func (c *Config) Debug() bool {
|
|||||||
return c.Options.Debug
|
return c.Options.Debug
|
||||||
}
|
}
|
||||||
|
|
||||||
// BinaryFormat returns an appropriate binary format, based on the file
|
|
||||||
// extension and the configured binary format in the target JSON file.
|
|
||||||
func (c *Config) BinaryFormat(ext string) string {
|
|
||||||
switch ext {
|
|
||||||
case ".bin", ".gba", ".nro":
|
|
||||||
// The simplest format possible: dump everything in a raw binary file.
|
|
||||||
if c.Target.BinaryFormat != "" {
|
|
||||||
return c.Target.BinaryFormat
|
|
||||||
}
|
|
||||||
return "bin"
|
|
||||||
case ".hex":
|
|
||||||
// Similar to bin, but includes the start address and is thus usually a
|
|
||||||
// better format.
|
|
||||||
return "hex"
|
|
||||||
case ".uf2":
|
|
||||||
// Special purpose firmware format, mainly used on Adafruit boards.
|
|
||||||
// More information:
|
|
||||||
// https://github.com/Microsoft/uf2
|
|
||||||
return "uf2"
|
|
||||||
default:
|
|
||||||
// Use the ELF format for unrecognized file formats.
|
|
||||||
return "elf"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Programmer returns the flash method and OpenOCD interface name given a
|
// Programmer returns the flash method and OpenOCD interface name given a
|
||||||
// particular configuration. It may either be all configured in the target JSON
|
// particular configuration. It may either be all configured in the target JSON
|
||||||
// file or be modified using the -programmmer command-line option.
|
// file or be modified using the -programmmer command-line option.
|
||||||
@@ -283,9 +265,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
|||||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
||||||
}
|
}
|
||||||
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
||||||
for _, cmd := range c.Target.OpenOCDCommands {
|
|
||||||
args = append(args, "-c", cmd)
|
|
||||||
}
|
|
||||||
if c.Target.OpenOCDTransport != "" {
|
if c.Target.OpenOCDTransport != "" {
|
||||||
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||||
}
|
}
|
||||||
@@ -293,34 +272,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
|||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeModel returns the code model used on this platform.
|
|
||||||
func (c *Config) CodeModel() string {
|
|
||||||
if c.Target.CodeModel != "" {
|
|
||||||
return c.Target.CodeModel
|
|
||||||
}
|
|
||||||
|
|
||||||
return "default"
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelocationModel returns the relocation model in use on this platform. Valid
|
|
||||||
// values are "static", "pic", "dynamicnopic".
|
|
||||||
func (c *Config) RelocationModel() string {
|
|
||||||
if c.Target.RelocationModel != "" {
|
|
||||||
return c.Target.RelocationModel
|
|
||||||
}
|
|
||||||
|
|
||||||
return "static"
|
|
||||||
}
|
|
||||||
|
|
||||||
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
|
|
||||||
// the value is overridden by `-wasm-abi` flag if it is provided
|
|
||||||
func (c *Config) WasmAbi() string {
|
|
||||||
if c.Options.WasmAbi != "" {
|
|
||||||
return c.Options.WasmAbi
|
|
||||||
}
|
|
||||||
return c.Target.WasmAbi
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestConfig struct {
|
type TestConfig struct {
|
||||||
CompileTestBinary bool
|
CompileTestBinary bool
|
||||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||||
|
|||||||
+1
-64
@@ -1,17 +1,5 @@
|
|||||||
package compileopts
|
package compileopts
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
|
||||||
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
|
|
||||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
|
||||||
validPanicStrategyOptions = []string{"print", "trap"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options contains extra options to give to the compiler. These options are
|
// Options contains extra options to give to the compiler. These options are
|
||||||
// usually passed from the command line.
|
// usually passed from the command line.
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -23,64 +11,13 @@ type Options struct {
|
|||||||
PrintIR bool
|
PrintIR bool
|
||||||
DumpSSA bool
|
DumpSSA bool
|
||||||
VerifyIR bool
|
VerifyIR bool
|
||||||
PrintCommands bool
|
|
||||||
Debug bool
|
Debug bool
|
||||||
PrintSizes string
|
PrintSizes string
|
||||||
PrintStacks bool
|
|
||||||
CFlags []string
|
CFlags []string
|
||||||
LDFlags []string
|
LDFlags []string
|
||||||
Tags string
|
Tags string
|
||||||
WasmAbi string
|
WasmAbi string
|
||||||
|
HeapSize int64
|
||||||
TestConfig TestConfig
|
TestConfig TestConfig
|
||||||
Programmer string
|
Programmer string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
|
||||||
func (o *Options) Verify() error {
|
|
||||||
if o.GC != "" {
|
|
||||||
valid := isInArray(validGCOptions, o.GC)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
|
|
||||||
o.GC,
|
|
||||||
strings.Join(validGCOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.Scheduler != "" {
|
|
||||||
valid := isInArray(validSchedulerOptions, o.Scheduler)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
|
|
||||||
o.Scheduler,
|
|
||||||
strings.Join(validSchedulerOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.PrintSizes != "" {
|
|
||||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
|
|
||||||
o.PrintSizes,
|
|
||||||
strings.Join(validPrintSizeOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.PanicStrategy != "" {
|
|
||||||
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
|
|
||||||
o.PanicStrategy,
|
|
||||||
strings.Join(validPanicStrategyOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isInArray(arr []string, item string) bool {
|
|
||||||
for _, i := range arr {
|
|
||||||
if i == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
package compileopts_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVerifyOptions(t *testing.T) {
|
|
||||||
|
|
||||||
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`)
|
|
||||||
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`)
|
|
||||||
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
|
|
||||||
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
opts compileopts.Options
|
|
||||||
expectedError error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "OptionsEmpty",
|
|
||||||
opts: compileopts.Options{},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "InvalidGCOption",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
GC: "incorrect",
|
|
||||||
},
|
|
||||||
expectedError: expectedGCError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GCOptionNone",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
GC: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GCOptionLeaking",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
GC: "leaking",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GCOptionExtalloc",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
GC: "extalloc",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "GCOptionConservative",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
GC: "conservative",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "InvalidSchedulerOption",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
Scheduler: "incorrect",
|
|
||||||
},
|
|
||||||
expectedError: expectedSchedulerError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SchedulerOptionNone",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
Scheduler: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SchedulerOptionTasks",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
Scheduler: "tasks",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "SchedulerOptionCoroutines",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
Scheduler: "coroutines",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "InvalidPrintSizeOption",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PrintSizes: "incorrect",
|
|
||||||
},
|
|
||||||
expectedError: expectedPrintSizeError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PrintSizeOptionNone",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PrintSizes: "none",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PrintSizeOptionShort",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PrintSizes: "short",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PrintSizeOptionFull",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PrintSizes: "full",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "InvalidPanicOption",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PanicStrategy: "incorrect",
|
|
||||||
},
|
|
||||||
expectedError: expectedPanicStrategyError,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PanicOptionPrint",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PanicStrategy: "print",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "PanicOptionTrap",
|
|
||||||
opts: compileopts.Options{
|
|
||||||
PanicStrategy: "trap",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
|
||||||
err := tc.opts.Verify()
|
|
||||||
if tc.expectedError != err {
|
|
||||||
if tc.expectedError.Error() != err.Error() {
|
|
||||||
t.Errorf("expected %v, got %v", tc.expectedError, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+96
-68
@@ -8,7 +8,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -34,13 +33,11 @@ type TargetSpec struct {
|
|||||||
Linker string `json:"linker"`
|
Linker string `json:"linker"`
|
||||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||||
Libc string `json:"libc"`
|
Libc string `json:"libc"`
|
||||||
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
|
|
||||||
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
|
|
||||||
CFlags []string `json:"cflags"`
|
CFlags []string `json:"cflags"`
|
||||||
LDFlags []string `json:"ldflags"`
|
LDFlags []string `json:"ldflags"`
|
||||||
LinkerScript string `json:"linkerscript"`
|
LinkerScript string `json:"linkerscript"`
|
||||||
ExtraFiles []string `json:"extra-files"`
|
ExtraFiles []string `json:"extra-files"`
|
||||||
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
Emulator []string `json:"emulator"`
|
||||||
FlashCommand string `json:"flash-command"`
|
FlashCommand string `json:"flash-command"`
|
||||||
GDB string `json:"gdb"`
|
GDB string `json:"gdb"`
|
||||||
PortReset string `json:"flash-1200-bps-reset"`
|
PortReset string `json:"flash-1200-bps-reset"`
|
||||||
@@ -48,57 +45,90 @@ type TargetSpec struct {
|
|||||||
FlashVolume string `json:"msd-volume-name"`
|
FlashVolume string `json:"msd-volume-name"`
|
||||||
FlashFilename string `json:"msd-firmware-name"`
|
FlashFilename string `json:"msd-firmware-name"`
|
||||||
UF2FamilyID string `json:"uf2-family-id"`
|
UF2FamilyID string `json:"uf2-family-id"`
|
||||||
BinaryFormat string `json:"binary-format"`
|
|
||||||
OpenOCDInterface string `json:"openocd-interface"`
|
OpenOCDInterface string `json:"openocd-interface"`
|
||||||
OpenOCDTarget string `json:"openocd-target"`
|
OpenOCDTarget string `json:"openocd-target"`
|
||||||
OpenOCDTransport string `json:"openocd-transport"`
|
OpenOCDTransport string `json:"openocd-transport"`
|
||||||
OpenOCDCommands []string `json:"openocd-commands"`
|
|
||||||
JLinkDevice string `json:"jlink-device"`
|
JLinkDevice string `json:"jlink-device"`
|
||||||
CodeModel string `json:"code-model"`
|
|
||||||
RelocationModel string `json:"relocation-model"`
|
|
||||||
WasmAbi string `json:"wasm-abi"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// overrideProperties overrides all properties that are set in child into itself using reflection.
|
// copyProperties copies all properties that are set in spec2 into itself.
|
||||||
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||||
specType := reflect.TypeOf(spec).Elem()
|
// TODO: simplify this using reflection? Inherits and BuildTags are special
|
||||||
specValue := reflect.ValueOf(spec).Elem()
|
// cases, but the rest can simply be copied if set.
|
||||||
childValue := reflect.ValueOf(child).Elem()
|
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
|
||||||
|
if spec2.Triple != "" {
|
||||||
for i := 0; i < specType.NumField(); i++ {
|
spec.Triple = spec2.Triple
|
||||||
field := specType.Field(i)
|
}
|
||||||
src := childValue.Field(i)
|
if spec2.CPU != "" {
|
||||||
dst := specValue.Field(i)
|
spec.CPU = spec2.CPU
|
||||||
|
}
|
||||||
switch kind := field.Type.Kind(); kind {
|
spec.Features = append(spec.Features, spec2.Features...)
|
||||||
case reflect.String: // for strings, just copy the field of child to spec if not empty
|
if spec2.GOOS != "" {
|
||||||
if src.Len() > 0 {
|
spec.GOOS = spec2.GOOS
|
||||||
dst.Set(src)
|
}
|
||||||
}
|
if spec2.GOARCH != "" {
|
||||||
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
|
spec.GOARCH = spec2.GOARCH
|
||||||
if src.Uint() != 0 {
|
}
|
||||||
dst.Set(src)
|
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
|
||||||
}
|
if spec2.GC != "" {
|
||||||
case reflect.Ptr: // for pointers, copy if not nil
|
spec.GC = spec2.GC
|
||||||
if !src.IsNil() {
|
}
|
||||||
dst.Set(src)
|
if spec2.Scheduler != "" {
|
||||||
}
|
spec.Scheduler = spec2.Scheduler
|
||||||
case reflect.Slice: // for slices...
|
}
|
||||||
if src.Len() > 0 { // ... if not empty ...
|
if spec2.Compiler != "" {
|
||||||
switch tag := field.Tag.Get("override"); tag {
|
spec.Compiler = spec2.Compiler
|
||||||
case "copy":
|
}
|
||||||
// copy the field of child to spec
|
if spec2.Linker != "" {
|
||||||
dst.Set(src)
|
spec.Linker = spec2.Linker
|
||||||
case "append", "":
|
}
|
||||||
// or append the field of child to spec
|
if spec2.RTLib != "" {
|
||||||
dst.Set(reflect.AppendSlice(src, dst))
|
spec.RTLib = spec2.RTLib
|
||||||
default:
|
}
|
||||||
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
if spec2.Libc != "" {
|
||||||
}
|
spec.Libc = spec2.Libc
|
||||||
}
|
}
|
||||||
default:
|
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
|
||||||
panic("unknown field type : " + kind.String())
|
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
|
||||||
}
|
if spec2.LinkerScript != "" {
|
||||||
|
spec.LinkerScript = spec2.LinkerScript
|
||||||
|
}
|
||||||
|
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
|
||||||
|
if len(spec2.Emulator) != 0 {
|
||||||
|
spec.Emulator = spec2.Emulator
|
||||||
|
}
|
||||||
|
if spec2.FlashCommand != "" {
|
||||||
|
spec.FlashCommand = spec2.FlashCommand
|
||||||
|
}
|
||||||
|
if spec2.GDB != "" {
|
||||||
|
spec.GDB = spec2.GDB
|
||||||
|
}
|
||||||
|
if spec2.PortReset != "" {
|
||||||
|
spec.PortReset = spec2.PortReset
|
||||||
|
}
|
||||||
|
if spec2.FlashMethod != "" {
|
||||||
|
spec.FlashMethod = spec2.FlashMethod
|
||||||
|
}
|
||||||
|
if spec2.FlashVolume != "" {
|
||||||
|
spec.FlashVolume = spec2.FlashVolume
|
||||||
|
}
|
||||||
|
if spec2.FlashFilename != "" {
|
||||||
|
spec.FlashFilename = spec2.FlashFilename
|
||||||
|
}
|
||||||
|
if spec2.UF2FamilyID != "" {
|
||||||
|
spec.UF2FamilyID = spec2.UF2FamilyID
|
||||||
|
}
|
||||||
|
if spec2.OpenOCDInterface != "" {
|
||||||
|
spec.OpenOCDInterface = spec2.OpenOCDInterface
|
||||||
|
}
|
||||||
|
if spec2.OpenOCDTarget != "" {
|
||||||
|
spec.OpenOCDTarget = spec2.OpenOCDTarget
|
||||||
|
}
|
||||||
|
if spec2.OpenOCDTransport != "" {
|
||||||
|
spec.OpenOCDTransport = spec2.OpenOCDTransport
|
||||||
|
}
|
||||||
|
if spec2.JLinkDevice != "" {
|
||||||
|
spec.JLinkDevice = spec2.JLinkDevice
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,11 +177,11 @@ func (spec *TargetSpec) resolveInherits() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
newSpec.overrideProperties(subtarget)
|
newSpec.copyProperties(subtarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
// When all properties are loaded, make sure they are properly inherited.
|
// When all properties are loaded, make sure they are properly inherited.
|
||||||
newSpec.overrideProperties(spec)
|
newSpec.copyProperties(spec)
|
||||||
*spec = *newSpec
|
*spec = *newSpec
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -217,7 +247,6 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
|||||||
}
|
}
|
||||||
goarch := map[string]string{ // map from LLVM arch to Go arch
|
goarch := map[string]string{ // map from LLVM arch to Go arch
|
||||||
"i386": "386",
|
"i386": "386",
|
||||||
"i686": "386",
|
|
||||||
"x86_64": "amd64",
|
"x86_64": "amd64",
|
||||||
"aarch64": "arm64",
|
"aarch64": "arm64",
|
||||||
"armv7": "arm",
|
"armv7": "arm",
|
||||||
@@ -233,40 +262,39 @@ func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|||||||
// No target spec available. Use the default one, useful on most systems
|
// No target spec available. Use the default one, useful on most systems
|
||||||
// with a regular OS.
|
// with a regular OS.
|
||||||
spec := TargetSpec{
|
spec := TargetSpec{
|
||||||
Triple: triple,
|
Triple: triple,
|
||||||
GOOS: goos,
|
GOOS: goos,
|
||||||
GOARCH: goarch,
|
GOARCH: goarch,
|
||||||
BuildTags: []string{goos, goarch},
|
BuildTags: []string{goos, goarch},
|
||||||
Compiler: "clang",
|
Compiler: "clang",
|
||||||
Linker: "cc",
|
Linker: "cc",
|
||||||
CFlags: []string{"--target=" + triple},
|
CFlags: []string{"--target=" + triple},
|
||||||
GDB: "gdb",
|
GDB: "gdb",
|
||||||
PortReset: "false",
|
PortReset: "false",
|
||||||
|
FlashMethod: "native",
|
||||||
}
|
}
|
||||||
if goos == "darwin" {
|
if goos == "darwin" {
|
||||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||||
} else {
|
} else {
|
||||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
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")
|
|
||||||
}
|
|
||||||
if goarch != runtime.GOARCH {
|
if goarch != runtime.GOARCH {
|
||||||
// Some educated guesses as to how to invoke helper programs.
|
// Some educated guesses as to how to invoke helper programs.
|
||||||
spec.GDB = "gdb-multiarch"
|
|
||||||
if goarch == "arm" && goos == "linux" {
|
if goarch == "arm" && goos == "linux" {
|
||||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
||||||
spec.Linker = "arm-linux-gnueabihf-gcc"
|
spec.Linker = "arm-linux-gnueabihf-gcc"
|
||||||
|
spec.GDB = "arm-linux-gnueabihf-gdb"
|
||||||
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
||||||
}
|
}
|
||||||
if goarch == "arm64" && goos == "linux" {
|
if goarch == "arm64" && goos == "linux" {
|
||||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
||||||
spec.Linker = "aarch64-linux-gnu-gcc"
|
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||||
|
spec.GDB = "aarch64-linux-gnu-gdb"
|
||||||
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||||
}
|
}
|
||||||
if goarch == "386" && runtime.GOARCH == "amd64" {
|
if goarch == "386" {
|
||||||
spec.CFlags = append(spec.CFlags, "-m32")
|
spec.CFlags = []string{"-m32"}
|
||||||
spec.LDFlags = append(spec.LDFlags, "-m32")
|
spec.LDFlags = []string{"-m32"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &spec, nil
|
return &spec, nil
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package compileopts
|
package compileopts
|
||||||
|
|
||||||
import (
|
import "testing"
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadTarget(t *testing.T) {
|
func TestLoadTarget(t *testing.T) {
|
||||||
_, err := LoadTarget("arduino")
|
_, err := LoadTarget("arduino")
|
||||||
@@ -20,67 +17,3 @@ func TestLoadTarget(t *testing.T) {
|
|||||||
t.Error("LoadTarget failed for wrong reason:", err)
|
t.Error("LoadTarget failed for wrong reason:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOverrideProperties(t *testing.T) {
|
|
||||||
baseAutoStackSize := true
|
|
||||||
base := &TargetSpec{
|
|
||||||
GOOS: "baseGoos",
|
|
||||||
CPU: "baseCpu",
|
|
||||||
Features: []string{"bf1", "bf2"},
|
|
||||||
BuildTags: []string{"bt1", "bt2"},
|
|
||||||
Emulator: []string{"be1", "be2"},
|
|
||||||
DefaultStackSize: 42,
|
|
||||||
AutoStackSize: &baseAutoStackSize,
|
|
||||||
}
|
|
||||||
childAutoStackSize := false
|
|
||||||
child := &TargetSpec{
|
|
||||||
GOOS: "",
|
|
||||||
CPU: "chlidCpu",
|
|
||||||
Features: []string{"cf1", "cf2"},
|
|
||||||
Emulator: []string{"ce1", "ce2"},
|
|
||||||
AutoStackSize: &childAutoStackSize,
|
|
||||||
DefaultStackSize: 64,
|
|
||||||
}
|
|
||||||
|
|
||||||
base.overrideProperties(child)
|
|
||||||
|
|
||||||
if base.GOOS != "baseGoos" {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.GOOS)
|
|
||||||
}
|
|
||||||
if base.CPU != "chlidCpu" {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.CPU)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(base.Features, []string{"cf1", "cf2", "bf1", "bf2"}) {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.Features)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.Emulator)
|
|
||||||
}
|
|
||||||
if *base.AutoStackSize != false {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
|
||||||
}
|
|
||||||
if base.DefaultStackSize != 64 {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
baseAutoStackSize = true
|
|
||||||
base = &TargetSpec{
|
|
||||||
AutoStackSize: &baseAutoStackSize,
|
|
||||||
DefaultStackSize: 42,
|
|
||||||
}
|
|
||||||
child = &TargetSpec{
|
|
||||||
AutoStackSize: nil,
|
|
||||||
DefaultStackSize: 0,
|
|
||||||
}
|
|
||||||
base.overrideProperties(child)
|
|
||||||
if *base.AutoStackSize != true {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
|
||||||
}
|
|
||||||
if base.DefaultStackSize != 42 {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
+6
-6
@@ -16,7 +16,7 @@ import (
|
|||||||
// slice. This is required by the Go language spec: an index out of bounds must
|
// slice. This is required by the Go language spec: an index out of bounds must
|
||||||
// cause a panic.
|
// cause a panic.
|
||||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
||||||
if b.info.nobounds {
|
if b.fn.IsNoBounds() {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
// checking.
|
// checking.
|
||||||
return
|
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
|
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||||
// logic is the same in both cases.
|
// logic is the same in both cases.
|
||||||
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||||
if b.info.nobounds {
|
if b.fn.IsNoBounds() {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
// checking.
|
// checking.
|
||||||
return
|
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
|
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||||
// check that the value is not too big for runtime.chanMake.
|
// 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) {
|
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
||||||
if b.info.nobounds {
|
if b.fn.IsNoBounds() {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
// checking.
|
// checking.
|
||||||
return
|
return
|
||||||
@@ -189,7 +189,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.
|
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
||||||
// This function assumes that the shift value is signed.
|
// This function assumes that the shift value is signed.
|
||||||
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
||||||
if b.info.nobounds {
|
if b.fn.IsNoBounds() {
|
||||||
// Function disabled bounds checking - skip shift check.
|
// Function disabled bounds checking - skip shift check.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -212,8 +212,8 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
faultBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".throw")
|
||||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
|
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, blockPrefix+".next")
|
||||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
|
|
||||||
// Now branch to the out-of-bounds or the regular block.
|
// Now branch to the out-of-bounds or the regular block.
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
|
|
||||||
// operation. It returns the result of the operation and true if the call could
|
|
||||||
// be lowered inline, and false otherwise.
|
|
||||||
func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
|
||||||
name := call.Value.(*ssa.Function).Name()
|
|
||||||
switch name {
|
|
||||||
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
|
|
||||||
ptr := b.getValue(call.Args[0])
|
|
||||||
val := b.getValue(call.Args[1])
|
|
||||||
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
|
||||||
// Return the new value, not the original value returned by atomicrmw.
|
|
||||||
return b.CreateAdd(oldVal, val, ""), true
|
|
||||||
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
|
|
||||||
ptr := b.getValue(call.Args[0])
|
|
||||||
val := b.getValue(call.Args[1])
|
|
||||||
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
|
|
||||||
if isPointer {
|
|
||||||
// atomicrmw only supports integers, so cast to an integer.
|
|
||||||
val = b.CreatePtrToInt(val, b.uintptrType, "")
|
|
||||||
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
|
|
||||||
}
|
|
||||||
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
|
||||||
if isPointer {
|
|
||||||
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
|
|
||||||
}
|
|
||||||
return oldVal, true
|
|
||||||
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
|
|
||||||
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
|
|
||||||
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
|
|
||||||
ptr := b.getValue(call.Args[0])
|
|
||||||
val := b.CreateLoad(ptr, "")
|
|
||||||
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
|
|
||||||
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
|
|
||||||
return val, true
|
|
||||||
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
|
|
||||||
ptr := b.getValue(call.Args[0])
|
|
||||||
val := b.getValue(call.Args[1])
|
|
||||||
store := b.CreateStore(val, ptr)
|
|
||||||
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
|
|
||||||
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
|
|
||||||
return store, true
|
|
||||||
default:
|
|
||||||
return llvm.Value{}, false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+5
-6
@@ -4,7 +4,6 @@ import (
|
|||||||
"go/types"
|
"go/types"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,14 +34,14 @@ const (
|
|||||||
|
|
||||||
// createCall creates a new call to runtime.<fnName> with the given arguments.
|
// 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 {
|
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||||
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
|
fullName := "runtime." + fnName
|
||||||
llvmFn := b.getFunction(fn)
|
fn := b.mod.NamedFunction(fullName)
|
||||||
if llvmFn.IsNil() {
|
if fn.IsNil() {
|
||||||
panic("trying to call non-existent function: " + fn.RelString(nil))
|
panic("trying to call non-existent function: " + fullName)
|
||||||
}
|
}
|
||||||
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
||||||
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
||||||
return b.createCall(llvmFn, args, name)
|
return b.createCall(fn, args, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createCall creates a call to the given function with the arguments possibly
|
// createCall creates a call to the given function with the arguments possibly
|
||||||
|
|||||||
+8
-17
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
||||||
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
|
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
||||||
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
|
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
|
||||||
bufSize := b.getValue(expr.Size)
|
bufSize := b.getValue(expr.Size)
|
||||||
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
|
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
|
||||||
@@ -35,37 +35,27 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
|||||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||||
b.CreateStore(chanValue, valueAlloca)
|
b.CreateStore(chanValue, valueAlloca)
|
||||||
|
|
||||||
// Allocate blockedlist buffer.
|
|
||||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
|
||||||
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
|
||||||
|
|
||||||
// Do the send.
|
// Do the send.
|
||||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
|
||||||
|
|
||||||
// End the lifetime of the allocas.
|
// End the lifetime of the alloca.
|
||||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
|
||||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||||
// actual channel receive operation during goroutine lowering.
|
// actual channel receive operation during goroutine lowering.
|
||||||
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||||
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
|
valueType := b.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||||
ch := b.getValue(unop.X)
|
ch := b.getValue(unop.X)
|
||||||
|
|
||||||
// Allocate memory to receive into.
|
// Allocate memory to receive into.
|
||||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||||
|
|
||||||
// Allocate blockedlist buffer.
|
|
||||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
|
||||||
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
|
||||||
|
|
||||||
// Do the receive.
|
// Do the receive.
|
||||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
|
||||||
received := b.CreateLoad(valueAlloca, "chan.received")
|
received := b.CreateLoad(valueAlloca, "chan.received")
|
||||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
|
||||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
|
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
@@ -79,7 +69,8 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createChanClose closes the given channel.
|
// createChanClose closes the given channel.
|
||||||
func (b *builder) createChanClose(ch llvm.Value) {
|
func (b *builder) createChanClose(param ssa.Value) {
|
||||||
|
ch := b.getValue(param)
|
||||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +117,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
|||||||
switch state.Dir {
|
switch state.Dir {
|
||||||
case types.RecvOnly:
|
case types.RecvOnly:
|
||||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
// Make sure the receive buffer is big enough and has the correct alignment.
|
||||||
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
|
llvmType := b.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
|
||||||
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
||||||
recvbufSize = size
|
recvbufSize = size
|
||||||
}
|
}
|
||||||
|
|||||||
+370
-416
File diff suppressed because it is too large
Load Diff
@@ -1,172 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
|
||||||
"regexp"
|
|
||||||
"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) {
|
|
||||||
target, err := compileopts.LoadTarget("i686--linux")
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []string{
|
|
||||||
"basic.go",
|
|
||||||
"pointer.go",
|
|
||||||
"slice.go",
|
|
||||||
"float.go",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, testCase := range tests {
|
|
||||||
t.Run(testCase, func(t *testing.T) {
|
|
||||||
// Load entire program AST into memory.
|
|
||||||
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, 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", testCase, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile AST to IR.
|
|
||||||
pkg := lprogram.MainPkg()
|
|
||||||
mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false)
|
|
||||||
if errs != nil {
|
|
||||||
for _, err := range errs {
|
|
||||||
t.Log("error:", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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()
|
|
||||||
|
|
||||||
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
|
|
||||||
|
|
||||||
// Update test if needed. Do not check the result.
|
|
||||||
if *flagUpdate {
|
|
||||||
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("failed to write updated output file:", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected, err := ioutil.ReadFile(outfile)
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
|
|
||||||
|
|
||||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
|
||||||
// equal. That means, only relevant lines are compared (excluding comments
|
|
||||||
// etc.).
|
|
||||||
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]
|
|
||||||
match1 := alignRegexp.MatchString(line1)
|
|
||||||
match2 := alignRegexp.MatchString(line2)
|
|
||||||
if match1 != match2 {
|
|
||||||
// Only one of the lines has the align keyword. Remove it.
|
|
||||||
// This is a change to make the test work in both LLVM 10 and LLVM
|
|
||||||
// 11 (LLVM 11 appears to automatically add alignment everywhere).
|
|
||||||
line1 = alignRegexp.ReplaceAllString(line1, "")
|
|
||||||
line2 = alignRegexp.ReplaceAllString(line2, "")
|
|
||||||
}
|
|
||||||
if line1 != line2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
|
||||||
if err != nil {
|
|
||||||
// Note: this should never happen and if it does, it will always happen
|
|
||||||
// for a particular build because llvm.Version is a constant.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") {
|
|
||||||
// Ignore attribute groups. These may change between LLVM versions.
|
|
||||||
// Right now test outputs are for LLVM 10.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") {
|
|
||||||
// Ignore the target layout. This may change between LLVM versions.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, line)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
+34
-129
@@ -14,9 +14,8 @@ package compiler
|
|||||||
// frames.
|
// frames.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
|
"github.com/tinygo-org/tinygo/ir"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -26,11 +25,9 @@ import (
|
|||||||
// calls.
|
// calls.
|
||||||
func (b *builder) deferInitFunc() {
|
func (b *builder) deferInitFunc() {
|
||||||
// Some setup.
|
// Some setup.
|
||||||
b.deferFuncs = make(map[*ssa.Function]int)
|
b.deferFuncs = make(map[*ir.Function]int)
|
||||||
b.deferInvokeFuncs = make(map[string]int)
|
b.deferInvokeFuncs = make(map[string]int)
|
||||||
b.deferClosureFuncs = make(map[*ssa.Function]int)
|
b.deferClosureFuncs = make(map[*ir.Function]int)
|
||||||
b.deferExprFuncs = make(map[ssa.Value]int)
|
|
||||||
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
|
||||||
|
|
||||||
// Create defer list pointer.
|
// Create defer list pointer.
|
||||||
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
|
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
|
||||||
@@ -107,11 +104,13 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
|
|
||||||
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
||||||
// Regular function call.
|
// Regular function call.
|
||||||
if _, ok := b.deferFuncs[callee]; !ok {
|
fn := b.ir.GetFunction(callee)
|
||||||
b.deferFuncs[callee] = len(b.allDeferFuncs)
|
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, callee)
|
if _, ok := b.deferFuncs[fn]; !ok {
|
||||||
|
b.deferFuncs[fn] = len(b.allDeferFuncs)
|
||||||
|
b.allDeferFuncs = append(b.allDeferFuncs, fn)
|
||||||
}
|
}
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
|
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[fn]), false)
|
||||||
|
|
||||||
// Collect all values to be put in the struct (starting with
|
// Collect all values to be put in the struct (starting with
|
||||||
// runtime._defer fields).
|
// runtime._defer fields).
|
||||||
@@ -133,7 +132,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
context := b.CreateExtractValue(closure, 0, "")
|
context := b.CreateExtractValue(closure, 0, "")
|
||||||
|
|
||||||
// Get the callback number.
|
// Get the callback number.
|
||||||
fn := makeClosure.Fn.(*ssa.Function)
|
fn := b.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
|
||||||
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
||||||
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
||||||
@@ -152,53 +151,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
values = append(values, context)
|
values = append(values, context)
|
||||||
valueTypes = append(valueTypes, context.Type())
|
valueTypes = append(valueTypes, context.Type())
|
||||||
|
|
||||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
|
||||||
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{
|
|
||||||
callName: builtin.Name(),
|
|
||||||
pos: builtin.Pos(),
|
|
||||||
argTypes: argTypes,
|
|
||||||
callback: len(b.allDeferFuncs),
|
|
||||||
}
|
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
|
|
||||||
}
|
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false)
|
|
||||||
|
|
||||||
// Collect all values to be put in the struct (starting with
|
|
||||||
// runtime._defer fields).
|
|
||||||
values = []llvm.Value{callback, next}
|
|
||||||
for _, param := range argValues {
|
|
||||||
values = append(values, param)
|
|
||||||
valueTypes = append(valueTypes, param.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
funcValue := b.getValue(instr.Call.Value)
|
b.addError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||||
|
return
|
||||||
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
|
|
||||||
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
|
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
|
||||||
}
|
|
||||||
|
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false)
|
|
||||||
|
|
||||||
// Collect all values to be put in the struct (starting with
|
|
||||||
// runtime._defer fields, followed by all parameters including the
|
|
||||||
// context pointer).
|
|
||||||
values = []llvm.Value{callback, next, funcValue}
|
|
||||||
valueTypes = append(valueTypes, funcValue.Type())
|
|
||||||
for _, param := range instr.Call.Args {
|
|
||||||
llvmParam := b.getValue(param)
|
|
||||||
values = append(values, llvmParam)
|
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a struct out of the collected values to put in the defer frame.
|
// Make a struct out of the collected values to put in the defer frame.
|
||||||
@@ -220,7 +175,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||||
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||||
}
|
}
|
||||||
if b.NeedsStackObjects {
|
if b.NeedsStackObjects() {
|
||||||
b.trackPointer(alloca)
|
b.trackPointer(alloca)
|
||||||
}
|
}
|
||||||
b.CreateStore(deferFrame, alloca)
|
b.CreateStore(deferFrame, alloca)
|
||||||
@@ -248,10 +203,10 @@ func (b *builder) createRunDefers() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Create loop.
|
// Create loop.
|
||||||
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
|
loophead := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loophead")
|
||||||
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
|
loop := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.loop")
|
||||||
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
|
unreachable := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.default")
|
||||||
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
|
end := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.end")
|
||||||
b.CreateBr(loophead)
|
b.CreateBr(loophead)
|
||||||
|
|
||||||
// Create loop head:
|
// Create loop head:
|
||||||
@@ -283,28 +238,21 @@ func (b *builder) createRunDefers() {
|
|||||||
// Create switch case, for example:
|
// Create switch case, for example:
|
||||||
// case 0:
|
// case 0:
|
||||||
// // run first deferred call
|
// // run first deferred call
|
||||||
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
|
block := b.ctx.AddBasicBlock(b.fn.LLVMFn, "rundefers.callback")
|
||||||
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
||||||
b.SetInsertPointAtEnd(block)
|
b.SetInsertPointAtEnd(block)
|
||||||
switch callback := callback.(type) {
|
switch callback := callback.(type) {
|
||||||
case *ssa.CallCommon:
|
case *ssa.CallCommon:
|
||||||
// Call on an value or interface value.
|
// Call on an interface value.
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
if !callback.IsInvoke() {
|
||||||
//Expect funcValue to be passed through the defer frame.
|
panic("expected an invoke call, not a direct call")
|
||||||
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
|
||||||
} else {
|
|
||||||
//Expect typecode
|
|
||||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the real defer struct type and cast to it.
|
||||||
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0), b.uintptrType, b.i8ptrType}
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
@@ -317,37 +265,21 @@ func (b *builder) createRunDefers() {
|
|||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fnPtr llvm.Value
|
// Isolate the typecode.
|
||||||
|
typecode, forwardParams := forwardParams[0], forwardParams[1:]
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
// Add the context parameter. An interface call cannot also be a
|
||||||
// Isolate the func value.
|
// closure but we have to supply the parameter anyway for platforms
|
||||||
funcValue := forwardParams[0]
|
// with a strict calling convention.
|
||||||
forwardParams = forwardParams[1:]
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
//Get function pointer and context
|
|
||||||
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
|
|
||||||
fnPtr = fp
|
|
||||||
|
|
||||||
//Pass context
|
|
||||||
forwardParams = append(forwardParams, context)
|
|
||||||
} else {
|
|
||||||
// Isolate the typecode.
|
|
||||||
typecode := forwardParams[0]
|
|
||||||
forwardParams = forwardParams[1:]
|
|
||||||
fnPtr = b.getInvokePtr(callback, typecode)
|
|
||||||
|
|
||||||
// Add the context parameter. An interface call cannot also be a
|
|
||||||
// closure but we have to supply the parameter anyway for platforms
|
|
||||||
// with a strict calling convention.
|
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
|
fnPtr := b.getInvokePtr(callback, typecode)
|
||||||
b.createCall(fnPtr, forwardParams, "")
|
b.createCall(fnPtr, forwardParams, "")
|
||||||
|
|
||||||
case *ssa.Function:
|
case *ir.Function:
|
||||||
// Direct call.
|
// Direct call.
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
@@ -369,7 +301,7 @@ func (b *builder) createRunDefers() {
|
|||||||
|
|
||||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
|
// 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.
|
// These parameters should not be supplied when calling into an external C/ASM function.
|
||||||
if !b.getFunctionInfo(callback).exported {
|
if !callback.IsExported() {
|
||||||
// Add the context parameter. We know it is ignored by the receiving
|
// Add the context parameter. We know it is ignored by the receiving
|
||||||
// function, but we have to pass one anyway.
|
// function, but we have to pass one anyway.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
@@ -379,11 +311,11 @@ func (b *builder) createRunDefers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call real function.
|
// Call real function.
|
||||||
b.createCall(b.getFunction(callback), forwardParams, "")
|
b.createCall(callback.LLVMFn, forwardParams, "")
|
||||||
|
|
||||||
case *ssa.MakeClosure:
|
case *ssa.MakeClosure:
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
fn := callback.Fn.(*ssa.Function)
|
fn := b.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
@@ -406,35 +338,8 @@ func (b *builder) createRunDefers() {
|
|||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
// Call deferred function.
|
// Call deferred function.
|
||||||
b.createCall(b.getFunction(fn), forwardParams, "")
|
b.createCall(fn.LLVMFn, forwardParams, "")
|
||||||
case *ssa.Builtin:
|
|
||||||
db := b.deferBuiltinFuncs[callback]
|
|
||||||
|
|
||||||
//Get parameter types
|
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
|
||||||
|
|
||||||
//Get signature from call results
|
|
||||||
params := callback.Type().Underlying().(*types.Signature).Params()
|
|
||||||
for i := 0; i < params.Len(); i++ {
|
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
|
||||||
}
|
|
||||||
|
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
|
||||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
|
||||||
|
|
||||||
// Extract the params from the struct.
|
|
||||||
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")
|
|
||||||
argValues = append(argValues, forwardParam)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
|
||||||
if err != nil {
|
|
||||||
b.diagnostics = append(b.diagnostics, err)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
panic("unknown deferred function type")
|
panic("unknown deferred function type")
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
// makeError makes it easy to create an error from a token.Pos with a message.
|
// 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 {
|
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
|
||||||
return types.Error{
|
return types.Error{
|
||||||
Fset: c.program.Fset,
|
Fset: c.ir.Program.Fset,
|
||||||
Pos: pos,
|
Pos: pos,
|
||||||
Msg: msg,
|
Msg: msg,
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-11
@@ -6,6 +6,7 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -20,11 +21,11 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
|
|||||||
// context.
|
// context.
|
||||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||||
var funcValueScalar llvm.Value
|
var funcValueScalar llvm.Value
|
||||||
switch c.FuncImplementation {
|
switch c.FuncImplementation() {
|
||||||
case "doubleword":
|
case compileopts.FuncValueDoubleword:
|
||||||
// Closure is: {context, function pointer}
|
// Closure is: {context, function pointer}
|
||||||
funcValueScalar = funcPtr
|
funcValueScalar = funcPtr
|
||||||
case "switch":
|
case compileopts.FuncValueSwitch:
|
||||||
sigGlobal := c.getTypeCode(sig)
|
sigGlobal := c.getTypeCode(sig)
|
||||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||||
@@ -66,10 +67,10 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
|||||||
// value. This may be an expensive operation.
|
// value. This may be an expensive operation.
|
||||||
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||||
context = b.CreateExtractValue(funcValue, 0, "")
|
context = b.CreateExtractValue(funcValue, 0, "")
|
||||||
switch b.FuncImplementation {
|
switch b.FuncImplementation() {
|
||||||
case "doubleword":
|
case compileopts.FuncValueDoubleword:
|
||||||
funcPtr = b.CreateExtractValue(funcValue, 1, "")
|
funcPtr = b.CreateExtractValue(funcValue, 1, "")
|
||||||
case "switch":
|
case compileopts.FuncValueSwitch:
|
||||||
llvmSig := b.getRawFuncType(sig)
|
llvmSig := b.getRawFuncType(sig)
|
||||||
sigGlobal := b.getTypeCode(sig)
|
sigGlobal := b.getTypeCode(sig)
|
||||||
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||||
@@ -82,11 +83,11 @@ func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (f
|
|||||||
|
|
||||||
// getFuncType returns the type of a func value given a signature.
|
// getFuncType returns the type of a func value given a signature.
|
||||||
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
||||||
switch c.FuncImplementation {
|
switch c.FuncImplementation() {
|
||||||
case "doubleword":
|
case compileopts.FuncValueDoubleword:
|
||||||
rawPtr := c.getRawFuncType(typ)
|
rawPtr := c.getRawFuncType(typ)
|
||||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||||
case "switch":
|
case compileopts.FuncValueSwitch:
|
||||||
return c.getLLVMRuntimeType("funcValue")
|
return c.getLLVMRuntimeType("funcValue")
|
||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
@@ -148,7 +149,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
|||||||
if len(expr.Bindings) == 0 {
|
if len(expr.Bindings) == 0 {
|
||||||
panic("unexpected: MakeClosure without bound variables")
|
panic("unexpected: MakeClosure without bound variables")
|
||||||
}
|
}
|
||||||
f := expr.Fn.(*ssa.Function)
|
f := b.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||||
|
|
||||||
// Collect all bound variables.
|
// Collect all bound variables.
|
||||||
boundVars := make([]llvm.Value, len(expr.Bindings))
|
boundVars := make([]llvm.Value, len(expr.Bindings))
|
||||||
@@ -163,5 +164,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
|||||||
context := b.emitPointerPack(boundVars)
|
context := b.emitPointerPack(boundVars)
|
||||||
|
|
||||||
// Create the closure.
|
// Create the closure.
|
||||||
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
|
return b.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-27
@@ -7,7 +7,6 @@ import (
|
|||||||
"go/token"
|
"go/token"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,32 +19,16 @@ import (
|
|||||||
// Because a go statement doesn't return anything, return undef.
|
// 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 {
|
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||||
paramBundle := b.emitPointerPack(params)
|
paramBundle := b.emitPointerPack(params)
|
||||||
var callee, stackSize llvm.Value
|
var callee llvm.Value
|
||||||
switch b.Scheduler {
|
switch b.Scheduler() {
|
||||||
case "none", "tasks":
|
case "none", "tasks":
|
||||||
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
|
callee = b.createGoroutineStartWrapper(funcPtr, prefix, 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).
|
|
||||||
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.DefaultStackSize, false)
|
|
||||||
}
|
|
||||||
case "coroutines":
|
case "coroutines":
|
||||||
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
||||||
// There is no goroutine stack size: coroutines are used instead of
|
|
||||||
// stacks.
|
|
||||||
stackSize = llvm.Undef(b.uintptrType)
|
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
b.createCall(b.mod.NamedFunction("internal/task.start"), []llvm.Value{callee, paramBundle, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
||||||
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
|
||||||
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
|
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,14 +67,13 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
|||||||
// Create the wrapper.
|
// Create the wrapper.
|
||||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
||||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
wrapper.SetLinkage(llvm.PrivateLinkage)
|
||||||
wrapper.SetUnnamedAddr(true)
|
wrapper.SetUnnamedAddr(true)
|
||||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
|
||||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||||
builder.SetInsertPointAtEnd(entry)
|
builder.SetInsertPointAtEnd(entry)
|
||||||
|
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
pos := c.program.Fset.Position(pos)
|
pos := c.ir.Program.Fset.Position(pos)
|
||||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||||
File: c.getDIFile(pos.Filename),
|
File: c.getDIFile(pos.Filename),
|
||||||
Parameters: nil, // do not show parameters in debugger
|
Parameters: nil, // do not show parameters in debugger
|
||||||
@@ -143,12 +125,11 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
|||||||
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
||||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||||
wrapper.SetUnnamedAddr(true)
|
wrapper.SetUnnamedAddr(true)
|
||||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
|
||||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||||
builder.SetInsertPointAtEnd(entry)
|
builder.SetInsertPointAtEnd(entry)
|
||||||
|
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
pos := c.program.Fset.Position(pos)
|
pos := c.ir.Program.Fset.Position(pos)
|
||||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||||
File: c.getDIFile(pos.Filename),
|
File: c.getDIFile(pos.Filename),
|
||||||
Parameters: nil, // do not show parameters in debugger
|
Parameters: nil, // do not show parameters in debugger
|
||||||
|
|||||||
@@ -163,44 +163,6 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
|||||||
return b.CreateCall(target, llvmArgs, ""), nil
|
return b.CreateCall(target, llvmArgs, ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
|
||||||
// be one of:
|
|
||||||
//
|
|
||||||
// func SVCall0(num uintptr) uintptr
|
|
||||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
|
||||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
|
||||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
|
||||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
|
||||||
//
|
|
||||||
// The num parameter must be a constant. All other parameters may be any scalar
|
|
||||||
// value supported by LLVM inline assembly.
|
|
||||||
// Same as emitSVCall but for AArch64
|
|
||||||
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
|
|
||||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
|
||||||
llvmArgs := []llvm.Value{}
|
|
||||||
argTypes := []llvm.Type{}
|
|
||||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
|
||||||
constraints := "={x0}"
|
|
||||||
for i, arg := range args[1:] {
|
|
||||||
arg = arg.(*ssa.MakeInterface).X
|
|
||||||
if i == 0 {
|
|
||||||
constraints += ",0"
|
|
||||||
} else {
|
|
||||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
|
||||||
}
|
|
||||||
llvmValue := b.getValue(arg)
|
|
||||||
llvmArgs = append(llvmArgs, llvmValue)
|
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
|
||||||
}
|
|
||||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
|
||||||
// clobbered. x0 is used as an output register so doesn't have to be
|
|
||||||
// marked as clobbered.
|
|
||||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
|
||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
|
||||||
return b.CreateCall(target, llvmArgs, ""), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a compiler builtin which emits CSR instructions. It can be one of:
|
// This is a compiler builtin which emits CSR instructions. It can be one of:
|
||||||
//
|
//
|
||||||
// func (csr CSR) Get() uintptr
|
// func (csr CSR) Get() uintptr
|
||||||
|
|||||||
+24
-69
@@ -11,6 +11,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/ir"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -235,7 +236,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
|||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
}
|
}
|
||||||
|
|
||||||
ms := c.program.MethodSets.MethodSet(typ)
|
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||||
if ms.Len() == 0 {
|
if ms.Len() == 0 {
|
||||||
// no methods, so can leave that one out
|
// no methods, so can leave that one out
|
||||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
||||||
@@ -246,16 +247,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
|||||||
for i := 0; i < ms.Len(); i++ {
|
for i := 0; i < ms.Len(); i++ {
|
||||||
method := ms.At(i)
|
method := ms.At(i)
|
||||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||||
fn := c.program.MethodValue(method)
|
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
|
||||||
llvmFn := c.getFunction(fn)
|
if f.LLVMFn.IsNil() {
|
||||||
if llvmFn.IsNil() {
|
|
||||||
// compiler error, so panic
|
// compiler error, so panic
|
||||||
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
|
panic("cannot find function: " + f.LinkName())
|
||||||
}
|
}
|
||||||
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
|
fn := c.getInterfaceInvokeWrapper(f)
|
||||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||||
signatureGlobal,
|
signatureGlobal,
|
||||||
llvm.ConstPtrToInt(wrapper, c.uintptrType),
|
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||||
})
|
})
|
||||||
methods[i] = methodInfo
|
methods[i] = methodInfo
|
||||||
}
|
}
|
||||||
@@ -303,7 +303,7 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
|||||||
// external *i8 indicating the indicating the signature of this method. It is
|
// external *i8 indicating the indicating the signature of this method. It is
|
||||||
// used during the interface lowering pass.
|
// used during the interface lowering pass.
|
||||||
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
|
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
|
||||||
signature := methodSignature(method)
|
signature := ir.MethodSignature(method)
|
||||||
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||||
if signatureGlobal.IsNil() {
|
if signatureGlobal.IsNil() {
|
||||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||||
@@ -357,8 +357,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
|||||||
// value.
|
// value.
|
||||||
|
|
||||||
prevBlock := b.GetInsertBlock()
|
prevBlock := b.GetInsertBlock()
|
||||||
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
|
okBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.ok")
|
||||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
|
nextBlock := b.ctx.AddBasicBlock(b.fn.LLVMFn, "typeassert.next")
|
||||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||||
|
|
||||||
@@ -436,8 +436,8 @@ func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value
|
|||||||
// value, dereferences or unpacks it if necessary, and calls the real method.
|
// 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
|
// If the method to wrap has a pointer receiver, no wrapping is necessary and
|
||||||
// the function is returned directly.
|
// the function is returned directly.
|
||||||
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
|
func (c *compilerContext) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||||
wrapperName := llvmFn.Name() + "$invoke"
|
wrapperName := f.LinkName() + "$invoke"
|
||||||
wrapper := c.mod.NamedFunction(wrapperName)
|
wrapper := c.mod.NamedFunction(wrapperName)
|
||||||
if !wrapper.IsNil() {
|
if !wrapper.IsNil() {
|
||||||
// Wrapper already created. Return it directly.
|
// Wrapper already created. Return it directly.
|
||||||
@@ -445,7 +445,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the expanded receiver type.
|
// Get the expanded receiver type.
|
||||||
receiverType := c.getLLVMType(fn.Params[0].Type())
|
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||||
var expandedReceiverType []llvm.Type
|
var expandedReceiverType []llvm.Type
|
||||||
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
||||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||||
@@ -457,15 +457,17 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
|||||||
// Casting a function signature to a different signature and calling it
|
// Casting a function signature to a different signature and calling it
|
||||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||||
// interface) is hopefully a safe (defined) operation.
|
// interface) is hopefully a safe (defined) operation.
|
||||||
return llvmFn
|
return f.LLVMFn
|
||||||
}
|
}
|
||||||
|
|
||||||
// create wrapper function
|
// create wrapper function
|
||||||
fnType := llvmFn.Type().ElementType()
|
fnType := f.LLVMFn.Type().ElementType()
|
||||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||||
wrapper.LastParam().SetName("parentHandle")
|
if f.LLVMFn.LastParam().Name() == "parentHandle" {
|
||||||
|
wrapper.LastParam().SetName("parentHandle")
|
||||||
|
}
|
||||||
|
|
||||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||||
wrapper.SetUnnamedAddr(true)
|
wrapper.SetUnnamedAddr(true)
|
||||||
@@ -478,9 +480,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
|||||||
defer b.Builder.Dispose()
|
defer b.Builder.Dispose()
|
||||||
|
|
||||||
// add debug info if needed
|
// add debug info if needed
|
||||||
if c.Debug {
|
if c.Debug() {
|
||||||
pos := c.program.Fset.Position(fn.Pos())
|
pos := c.ir.Program.Fset.Position(f.Pos())
|
||||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,60 +492,13 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
|||||||
|
|
||||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||||
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
if f.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||||
b.CreateCall(llvmFn, params, "")
|
b.CreateCall(f.LLVMFn, params, "")
|
||||||
b.CreateRetVoid()
|
b.CreateRetVoid()
|
||||||
} else {
|
} else {
|
||||||
ret := b.CreateCall(llvmFn, params, "ret")
|
ret := b.CreateCall(f.LLVMFn, params, "ret")
|
||||||
b.CreateRet(ret)
|
b.CreateRet(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper
|
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,7 +39,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
|||||||
|
|
||||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||||
// type are lowered in the interrupt lowering pass.
|
// type are lowered in the interrupt lowering pass.
|
||||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
globalType := b.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||||
globalLLVMType := b.getLLVMType(globalType)
|
globalLLVMType := b.getLLVMType(globalType)
|
||||||
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||||
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||||
@@ -55,8 +55,8 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
|||||||
global.SetInitializer(initializer)
|
global.SetInitializer(initializer)
|
||||||
|
|
||||||
// Add debug info to the interrupt global.
|
// Add debug info to the interrupt global.
|
||||||
if b.Debug {
|
if b.Debug() {
|
||||||
pos := b.program.Fset.Position(instr.Pos())
|
pos := b.ir.Program.Fset.Position(instr.Pos())
|
||||||
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
||||||
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
||||||
LinkageName: globalName,
|
LinkageName: globalName,
|
||||||
@@ -79,7 +79,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
|||||||
// PLIC where each interrupt must be enabled using the interrupt number, and
|
// PLIC where each interrupt must be enabled using the interrupt number, and
|
||||||
// thus keeps the Interrupt object alive.
|
// thus keeps the Interrupt object alive.
|
||||||
// This call is removed during interrupt lowering.
|
// 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")
|
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
||||||
if useFn.IsNil() {
|
if useFn.IsNil() {
|
||||||
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
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
|
// 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.
|
// pointer value directly. It returns the pointer with the packed data.
|
||||||
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
|
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.Config, values)
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package llvmutil
|
|||||||
// itself if possible and legal.
|
// itself if possible and legal.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
// 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.
|
// 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.
|
// If the values are all constants, they are be stored in a constant global and deduplicated.
|
||||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
|
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
|
||||||
ctx := mod.Context()
|
ctx := mod.Context()
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||||
@@ -25,6 +26,7 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
|||||||
packedType := ctx.StructType(valueTypes, false)
|
packedType := ctx.StructType(valueTypes, false)
|
||||||
|
|
||||||
// Allocate memory for the packed data.
|
// Allocate memory for the packed data.
|
||||||
|
var packedAlloc, packedHeapAlloc llvm.Value
|
||||||
size := targetData.TypeAllocSize(packedType)
|
size := targetData.TypeAllocSize(packedType)
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return llvm.ConstPointerNull(i8ptrType)
|
return llvm.ConstPointerNull(i8ptrType)
|
||||||
@@ -37,39 +39,9 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
|||||||
// Try to keep this cast in SSA form.
|
// Try to keep this cast in SSA form.
|
||||||
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
|
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||||
// it in a *i8 alloca first and load the *i8 value from there. This is
|
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||||
// effectively a bitcast.
|
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
|
||||||
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 {
|
} else {
|
||||||
// Check if the values are all constants.
|
// Check if the values are all constants.
|
||||||
constant := true
|
constant := true
|
||||||
@@ -95,12 +67,12 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
|||||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||||
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
||||||
alloc := mod.NamedFunction("runtime.alloc")
|
alloc := mod.NamedFunction("runtime.alloc")
|
||||||
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
|
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
|
||||||
sizeValue,
|
sizeValue,
|
||||||
llvm.Undef(i8ptrType), // unused context parameter
|
llvm.Undef(i8ptrType), // unused context parameter
|
||||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||||
}, "")
|
}, "")
|
||||||
if needsStackObjects {
|
if config.NeedsStackObjects() {
|
||||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||||
builder.CreateCall(trackPointer, []llvm.Value{
|
builder.CreateCall(trackPointer, []llvm.Value{
|
||||||
packedHeapAlloc,
|
packedHeapAlloc,
|
||||||
@@ -108,19 +80,28 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
|||||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||||
}, "")
|
}, "")
|
||||||
}
|
}
|
||||||
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||||
|
}
|
||||||
// Store all values in the heap pointer.
|
// Store all values in the alloca or heap pointer.
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
indices := []llvm.Value{
|
indices := []llvm.Value{
|
||||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||||
}
|
|
||||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
|
||||||
builder.CreateStore(value, gep)
|
|
||||||
}
|
}
|
||||||
|
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||||
|
builder.CreateStore(value, gep)
|
||||||
|
}
|
||||||
|
|
||||||
// Return the original heap allocation pointer, which already is an *i8.
|
if packedHeapAlloc.IsNil() {
|
||||||
|
// Load value (as *i8) from the alloca.
|
||||||
|
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
|
||||||
|
result := builder.CreateLoad(packedAlloc, "")
|
||||||
|
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||||
|
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||||
|
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
// Get the original heap allocation pointer, which already is an *i8.
|
||||||
return packedHeapAlloc
|
return packedHeapAlloc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file implements a simple reachability analysis, to reduce compile time.
|
|
||||||
// This DCE pass used to be necessary for improving other passes but now it
|
|
||||||
// isn't necessary anymore.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"go/types"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dceState struct {
|
|
||||||
*compilerContext
|
|
||||||
functions []*dceFunction
|
|
||||||
functionMap map[*ssa.Function]*dceFunction
|
|
||||||
}
|
|
||||||
|
|
||||||
type dceFunction struct {
|
|
||||||
*ssa.Function
|
|
||||||
functionInfo
|
|
||||||
flag bool // used by dead code elimination
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *dceState) addFunction(ssaFn *ssa.Function) {
|
|
||||||
if _, ok := p.functionMap[ssaFn]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f := &dceFunction{Function: ssaFn}
|
|
||||||
f.functionInfo = p.getFunctionInfo(ssaFn)
|
|
||||||
p.functions = append(p.functions, f)
|
|
||||||
p.functionMap[ssaFn] = f
|
|
||||||
|
|
||||||
for _, anon := range ssaFn.AnonFuncs {
|
|
||||||
p.addFunction(anon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// simpleDCE returns a list of alive functions in the program. Compiling only
|
|
||||||
// these functions makes the compiler faster.
|
|
||||||
//
|
|
||||||
// This functionality will likely be replaced in the future with build caching.
|
|
||||||
func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) {
|
|
||||||
mainPkg := c.program.Package(lprogram.MainPkg().Pkg)
|
|
||||||
if mainPkg == nil {
|
|
||||||
panic("could not find main package")
|
|
||||||
}
|
|
||||||
p := &dceState{
|
|
||||||
compilerContext: c,
|
|
||||||
functionMap: make(map[*ssa.Function]*dceFunction),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, pkg := range lprogram.Sorted() {
|
|
||||||
pkg := c.program.Package(pkg.Pkg)
|
|
||||||
memberNames := make([]string, 0)
|
|
||||||
for name := range pkg.Members {
|
|
||||||
memberNames = append(memberNames, name)
|
|
||||||
}
|
|
||||||
sort.Strings(memberNames)
|
|
||||||
|
|
||||||
for _, name := range memberNames {
|
|
||||||
member := pkg.Members[name]
|
|
||||||
switch member := member.(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
p.addFunction(member)
|
|
||||||
case *ssa.Type:
|
|
||||||
methods := getAllMethods(pkg.Prog, member.Type())
|
|
||||||
if !types.IsInterface(member.Type()) {
|
|
||||||
// named type
|
|
||||||
for _, method := range methods {
|
|
||||||
p.addFunction(pkg.Prog.MethodValue(method))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ssa.Global:
|
|
||||||
// Ignore. Globals are not handled here.
|
|
||||||
case *ssa.NamedConst:
|
|
||||||
// Ignore: these are already resolved.
|
|
||||||
default:
|
|
||||||
panic("unknown member type: " + member.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initial set of live functions. Include main.main, *.init and runtime.*
|
|
||||||
// functions.
|
|
||||||
main, ok := mainPkg.Members["main"].(*ssa.Function)
|
|
||||||
if !ok {
|
|
||||||
if mainPkg.Members["main"] == nil {
|
|
||||||
return nil, errors.New("function main is undeclared in the main package")
|
|
||||||
} else {
|
|
||||||
return nil, errors.New("cannot declare main - must be func")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runtimePkg := c.program.ImportedPackage("runtime")
|
|
||||||
mathPkg := c.program.ImportedPackage("math")
|
|
||||||
taskPkg := c.program.ImportedPackage("internal/task")
|
|
||||||
p.functionMap[main].flag = true
|
|
||||||
worklist := []*ssa.Function{main}
|
|
||||||
for _, f := range p.functions {
|
|
||||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
|
||||||
if f.flag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
f.flag = true
|
|
||||||
worklist = append(worklist, f.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark all called functions recursively.
|
|
||||||
for len(worklist) != 0 {
|
|
||||||
f := worklist[len(worklist)-1]
|
|
||||||
worklist = worklist[:len(worklist)-1]
|
|
||||||
for _, block := range f.Blocks {
|
|
||||||
for _, instr := range block.Instrs {
|
|
||||||
if instr, ok := instr.(*ssa.MakeInterface); ok {
|
|
||||||
for _, sel := range getAllMethods(c.program, instr.X.Type()) {
|
|
||||||
fn := c.program.MethodValue(sel)
|
|
||||||
callee := p.functionMap[fn]
|
|
||||||
if callee == nil {
|
|
||||||
// TODO: why is this necessary?
|
|
||||||
p.addFunction(fn)
|
|
||||||
callee = p.functionMap[fn]
|
|
||||||
}
|
|
||||||
if !callee.flag {
|
|
||||||
callee.flag = true
|
|
||||||
worklist = append(worklist, callee.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, operand := range instr.Operands(nil) {
|
|
||||||
if operand == nil || *operand == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch operand := (*operand).(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
f := p.functionMap[operand]
|
|
||||||
if f == nil {
|
|
||||||
// FIXME HACK: this function should have been
|
|
||||||
// discovered already. It is not for bound methods.
|
|
||||||
p.addFunction(operand)
|
|
||||||
f = p.functionMap[operand]
|
|
||||||
}
|
|
||||||
if !f.flag {
|
|
||||||
f.flag = true
|
|
||||||
worklist = append(worklist, operand)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all live functions.
|
|
||||||
liveFunctions := []*ssa.Function{}
|
|
||||||
for _, f := range p.functions {
|
|
||||||
if f.flag {
|
|
||||||
liveFunctions = append(liveFunctions, f.Function)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return liveFunctions, nil
|
|
||||||
}
|
|
||||||
+2
-285
@@ -15,269 +15,6 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"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
|
|
||||||
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 fn.Params {
|
|
||||||
paramType := c.getLLVMType(param.Type())
|
|
||||||
paramFragmentInfos := 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.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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: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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// globalInfo contains some information about a specific global. By default,
|
// 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
|
// 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).
|
// some symbols this is different (due to //go:extern for example).
|
||||||
@@ -345,11 +82,11 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Debug && !info.extern {
|
if c.Debug() && !info.extern {
|
||||||
// Add debug info.
|
// Add debug info.
|
||||||
// TODO: this should be done for every global in the program, not just
|
// TODO: this should be done for every global in the program, not just
|
||||||
// the ones that are referenced from some code.
|
// the ones that are referenced from some code.
|
||||||
pos := c.program.Fset.Position(g.Pos())
|
pos := c.ir.Program.Fset.Position(g.Pos())
|
||||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||||
Name: g.RelString(nil),
|
Name: g.RelString(nil),
|
||||||
LinkageName: info.linkName,
|
LinkageName: info.linkName,
|
||||||
@@ -408,23 +145,3 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-8
@@ -16,8 +16,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
num := b.getValue(call.Args[0])
|
num := b.getValue(call.Args[0])
|
||||||
var syscallResult llvm.Value
|
var syscallResult llvm.Value
|
||||||
switch {
|
switch {
|
||||||
case b.GOARCH == "amd64":
|
case b.GOARCH() == "amd64":
|
||||||
if b.GOOS == "darwin" {
|
if b.GOOS() == "darwin" {
|
||||||
// Darwin adds this magic number to system call numbers:
|
// Darwin adds this magic number to system call numbers:
|
||||||
//
|
//
|
||||||
// > Syscall classes for 64-bit system call entry.
|
// > Syscall classes for 64-bit system call entry.
|
||||||
@@ -58,7 +58,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||||
syscallResult = b.CreateCall(target, args, "")
|
syscallResult = b.CreateCall(target, args, "")
|
||||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
case b.GOARCH() == "386" && b.GOOS() == "linux":
|
||||||
// Sources:
|
// Sources:
|
||||||
// syscall(2) man page
|
// syscall(2) man page
|
||||||
// https://stackoverflow.com/a/2538212
|
// https://stackoverflow.com/a/2538212
|
||||||
@@ -84,7 +84,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||||
syscallResult = b.CreateCall(target, args, "")
|
syscallResult = b.CreateCall(target, args, "")
|
||||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
case b.GOARCH() == "arm" && b.GOOS() == "linux":
|
||||||
// Implement the EABI system call convention for Linux.
|
// Implement the EABI system call convention for Linux.
|
||||||
// Source: syscall(2) man page.
|
// Source: syscall(2) man page.
|
||||||
args := []llvm.Value{}
|
args := []llvm.Value{}
|
||||||
@@ -116,7 +116,7 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||||
syscallResult = b.CreateCall(target, args, "")
|
syscallResult = b.CreateCall(target, args, "")
|
||||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
case b.GOARCH() == "arm64" && b.GOOS() == "linux":
|
||||||
// Source: syscall(2) man page.
|
// Source: syscall(2) man page.
|
||||||
args := []llvm.Value{}
|
args := []llvm.Value{}
|
||||||
argTypes := []llvm.Type{}
|
argTypes := []llvm.Type{}
|
||||||
@@ -149,9 +149,9 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||||
syscallResult = b.CreateCall(target, args, "")
|
syscallResult = b.CreateCall(target, args, "")
|
||||||
default:
|
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 {
|
switch b.GOOS() {
|
||||||
case "linux", "freebsd":
|
case "linux", "freebsd":
|
||||||
// Return values: r0, r1 uintptr, err Errno
|
// Return values: r0, r1 uintptr, err Errno
|
||||||
// Pseudocode:
|
// Pseudocode:
|
||||||
@@ -187,6 +187,6 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
|||||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||||
return retval, nil
|
return retval, nil
|
||||||
default:
|
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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
-57
@@ -1,57 +0,0 @@
|
|||||||
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
-98
@@ -1,98 +0,0 @@
|
|||||||
; ModuleID = 'basic.go'
|
|
||||||
source_filename = "basic.go"
|
|
||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
|
||||||
target triple = "i686--linux"
|
|
||||||
|
|
||||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = add i32 %x, %y
|
|
||||||
ret i32 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = icmp eq i32 %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp oeq float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp une float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp olt float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp ole float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp ogt float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = fcmp oge float %x, %y
|
|
||||||
ret i1 %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret float %x.r
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret float %x.i
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal { 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 internal { 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 internal { 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
@@ -1,39 +0,0 @@
|
|||||||
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
-80
@@ -1,80 +0,0 @@
|
|||||||
; ModuleID = 'float.go'
|
|
||||||
source_filename = "float.go"
|
|
||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
|
||||||
target triple = "i686--linux"
|
|
||||||
|
|
||||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal 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 internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret float 0x41F0000000000000
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret i32 -1
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal { 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 internal 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 internal 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 internal 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 internal 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
-41
@@ -1,41 +0,0 @@
|
|||||||
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
-49
@@ -1,49 +0,0 @@
|
|||||||
; ModuleID = 'pointer.go'
|
|
||||||
source_filename = "pointer.go"
|
|
||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
|
||||||
target triple = "i686--linux"
|
|
||||||
|
|
||||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret [0 x i32] zeroinitializer
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
%0 = bitcast i8* %x to i32*
|
|
||||||
ret i32* %0
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal 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 internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret i8* %x
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal 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 internal 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 internal 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
-9
@@ -1,9 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
func sliceLen(ints []int) int {
|
|
||||||
return len(ints)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sliceCap(ints []int) int {
|
|
||||||
return cap(ints)
|
|
||||||
}
|
|
||||||
Vendored
-19
@@ -1,19 +0,0 @@
|
|||||||
; ModuleID = 'slice.go'
|
|
||||||
source_filename = "slice.go"
|
|
||||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
|
||||||
target triple = "i686--linux"
|
|
||||||
|
|
||||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret void
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret i32 %ints.len
|
|
||||||
}
|
|
||||||
|
|
||||||
define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
|
||||||
entry:
|
|
||||||
ret i32 %ints.cap
|
|
||||||
}
|
|
||||||
@@ -4,13 +4,10 @@ go 1.11
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||||
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/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||||
github.com/mattn/go-colorable v0.1.8
|
go.bug.st/serial v1.0.0
|
||||||
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
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
|
||||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f
|
google.golang.org/appengine v1.4.0 // indirect
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,61 +1,59 @@
|
|||||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
|
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/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
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 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
|
||||||
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
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 h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||||
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/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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
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 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
|
||||||
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
|
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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-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 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
|
||||||
|
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
|
||||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f h1:FP5Do5omlQ/dLQ3Hfy7oyJo69VS5Hn46rZw004r0lGU=
|
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
|
||||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d h1:mtgZh/e8a3wxneQFuLXoQYO//1mvlki02yZ1JCwMKp4=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200226165415-53522ab6713d/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a h1:Ugje2Lxuv8CFncHzs5W+hWfJvPsM+W4K0zRvzFbLvoE=
|
||||||
|
tinygo.org/x/go-llvm v0.0.0-20200401165421-8d120882fc7a/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||||
|
|||||||
@@ -10,10 +10,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Version of TinyGo.
|
|
||||||
// Update this value before release of new version of software.
|
|
||||||
const Version = "0.17.0"
|
|
||||||
|
|
||||||
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||||
// If the goroot cannot be determined, (0, 0) is returned.
|
// If the goroot cannot be determined, (0, 0) is returned.
|
||||||
func GetGorootVersion(goroot string) (major, minor int, err error) {
|
func GetGorootVersion(goroot string) (major, minor int, err error) {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
# Hooks for Docker Hub
|
|
||||||
|
|
||||||
Files in this directory are custom commands to be run during the different Docker Hub build phases.
|
|
||||||
|
|
||||||
See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Docker hub does a recursive clone, then checks the branch out,
|
|
||||||
# so when a PR adds a submodule (or updates it), it fails.
|
|
||||||
git submodule update --init
|
|
||||||
+37
-92
@@ -6,81 +6,50 @@ 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
|
is in practice a partial evaluator of the `runtime.initAll` function, which
|
||||||
calls each package initializer.
|
calls each package initializer.
|
||||||
|
|
||||||
This package is a rewrite of a previous partial evaluator that worked
|
It works by directly interpreting LLVM IR:
|
||||||
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:
|
|
||||||
|
|
||||||
* Much better error handling. By being able to revert to runtime execution
|
* Almost all operations work directly on constants, and are implemented using
|
||||||
without the need for scanning functions, this version is able to
|
the llvm.Const* set of functions that are evaluated directly.
|
||||||
automatically work around many bugs in the previous implementation.
|
* External function calls and some other operations (inline assembly, volatile
|
||||||
* More correct memory model. This is not inherent to the new design, but the
|
load, volatile store) are seen as having limited side effects. Limited in
|
||||||
new design also made the memory model easier to reason about.
|
the sense that it is known at compile time which globals it affects, which
|
||||||
* Faster execution of initialization code. While it is not much faster for
|
then are marked 'dirty' (meaning, further operations on it must be done at
|
||||||
normal interpretation (maybe 25% or so) due to the compilation overhead,
|
runtime). These operations are emitted directly in the `runtime.initAll`
|
||||||
it should be a whole lot faster for loops as it doesn't have to call into
|
function. Return values are also considered 'dirty'.
|
||||||
LLVM (via CGo) for every operation.
|
* Such 'dirty' objects and local values must be executed at runtime instead of
|
||||||
|
at compile time. This dirtyness propagates further through the IR, for
|
||||||
As mentioned, this partial evaulator comes in three parts: a compiler, an
|
example storing a dirty local value to a global also makes the global dirty,
|
||||||
interpreter, and a memory manager.
|
meaning that the global may not be read or written at compile time as it's
|
||||||
|
contents at that point during interpretation is unknown.
|
||||||
## Compiler
|
* 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
|
||||||
The main task of the compiler is that it extracts all necessary data from
|
function itself as having side effect (as if it is an external function).
|
||||||
every instruction in a function so that when this instruction is interpreted,
|
However, all globals it touches are still taken into account and when a call
|
||||||
no additional CGo calls are necessary. This is not currently done for all
|
is inserted in `runtime.initAll`, all globals it references are also marked
|
||||||
instructions (`runtime.alloc` is a notable exception), but at least it does
|
dirty.
|
||||||
so for the vast majority of instructions.
|
* 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
|
||||||
## Interpreter
|
the zero initializer.
|
||||||
|
* Stack allocation (`alloca`) is often emulated using a fake alloca object,
|
||||||
The interpreter runs an instruction just like it would if it were executed
|
until the address of the alloca is taken in which case it is also created as
|
||||||
'for real'. The vast majority of instructions can be executed at compile
|
a real `alloca` in `runtime.initAll` and marked dirty. This may be necessary
|
||||||
time. As indicated above, some instructions need to be executed at runtime
|
when calling an external function with the given alloca as paramter.
|
||||||
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?
|
## Why is this necessary?
|
||||||
|
|
||||||
A partial evaluator is hard to get right, so why go through all the trouble of
|
A partial evaluator is hard to get right, so why go through all the trouble of
|
||||||
writing one?
|
writing one?
|
||||||
|
|
||||||
The answer is that globals with initializers are much easier to optimize by
|
The main reason is that the previous attempt wasn't complete and wasn't sound.
|
||||||
LLVM than initialization code. Also, there are a few other benefits:
|
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:
|
||||||
|
|
||||||
* Dead globals are trivial to optimize away.
|
* Dead globals are trivial to optimize away.
|
||||||
* Constant globals are easier to detect. Remember that Go does not have global
|
* Constant globals are easier to detect. Remember that Go does not have global
|
||||||
@@ -91,29 +60,5 @@ LLVM than initialization code. Also, there are a few other benefits:
|
|||||||
* Constants are much more efficent on microcontrollers, as they can be
|
* Constants are much more efficent on microcontrollers, as they can be
|
||||||
allocated in flash instead of RAM.
|
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
|
For more details, see [this section of the
|
||||||
documentation](https://tinygo.org/compiler-internals/differences-from-go/).
|
documentation](https://tinygo.org/compiler-internals/differences-from-go/).
|
||||||
|
|||||||
@@ -1,410 +0,0 @@
|
|||||||
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 {
|
|
||||||
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.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).
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
}
|
|
||||||
+11
-17
@@ -11,19 +11,15 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errLiteralToPointer = errors.New("interp: trying to convert literal value to pointer")
|
// 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
|
// unsupportedInstructionError returns a new "unsupported instruction" error for
|
||||||
// by running the affected function at runtime instead of compile time.
|
// the given instruction. It includes source location information, when
|
||||||
var (
|
// available.
|
||||||
errExpectedPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)")
|
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Error {
|
||||||
errUnsupportedInst = errors.New("interp: unsupported instruction")
|
return e.errorAt(inst, errors.New("interp: unsupported instruction"))
|
||||||
errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)")
|
|
||||||
errMapAlreadyCreated = errors.New("interp: map already created")
|
|
||||||
)
|
|
||||||
|
|
||||||
func isRecoverableError(err error) bool {
|
|
||||||
return err == errExpectedPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrorLine is one line in a traceback. The position may be missing.
|
// ErrorLine is one line in a traceback. The position may be missing.
|
||||||
@@ -50,13 +46,11 @@ func (e *Error) Error() string {
|
|||||||
// errorAt returns an error value for the currently interpreted package at the
|
// errorAt returns an error value for the currently interpreted package at the
|
||||||
// location of the instruction. The location information may not be complete as
|
// location of the instruction. The location information may not be complete as
|
||||||
// it depends on debug information in the IR.
|
// it depends on debug information in the IR.
|
||||||
func (r *runner) errorAt(inst instruction, err error) *Error {
|
func (e *evalPackage) errorAt(inst llvm.Value, err error) *Error {
|
||||||
pos := getPosition(inst.llvmInst)
|
|
||||||
return &Error{
|
return &Error{
|
||||||
ImportPath: r.pkgName,
|
ImportPath: e.packagePath,
|
||||||
Pos: pos,
|
Pos: getPosition(inst),
|
||||||
Err: err,
|
Err: err,
|
||||||
Traceback: []ErrorLine{{pos, inst.llvmInst}},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+680
@@ -0,0 +1,680 @@
|
|||||||
|
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 {
|
||||||
|
value = operand.Load()
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
ptr.Store(value.Value())
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
m.PutString(keyBuf, keyLen, valPtr)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
m.PutBinary(keyBuf, valPtr)
|
||||||
|
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 := getStringBytes(buf1Ptr, buf1Len.Value())
|
||||||
|
buf2 := getStringBytes(buf2Ptr, buf2Len.Value())
|
||||||
|
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++ {
|
||||||
|
var err error
|
||||||
|
// *dst = *src
|
||||||
|
dstArray.Store(srcArray.Load())
|
||||||
|
// 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 := getStringBytes(bufPtr, bufLen.Value())
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
-100
@@ -1,77 +1,59 @@
|
|||||||
// Package interp is a partial evaluator of code run at package init time. See
|
// Package interp interprets Go package initializers as much as possible. This
|
||||||
// the README in this package for details.
|
// avoid running them at runtime, improving code size and making other
|
||||||
|
// optimizations possible.
|
||||||
package interp
|
package interp
|
||||||
|
|
||||||
|
// This file provides the overarching Eval object with associated (utility)
|
||||||
|
// methods.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enable extra checks, which should be disabled by default.
|
type Eval struct {
|
||||||
// This may help track down bugs by adding a few more sanity checks.
|
Mod llvm.Module
|
||||||
const checks = true
|
TargetData llvm.TargetData
|
||||||
|
Debug bool
|
||||||
// runner contains all state related to one interp run.
|
builder llvm.Builder
|
||||||
type runner struct {
|
dirtyGlobals map[llvm.Value]struct{}
|
||||||
mod llvm.Module
|
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run evaluates runtime.initAll function as much as possible at compile time.
|
// evalPackage encapsulates the Eval type for just a single package. The Eval
|
||||||
// Set debug to true if it should print output while running.
|
// type keeps state across the whole program, the evalPackage type keeps extra
|
||||||
|
// state for the currently interpreted package.
|
||||||
|
type evalPackage struct {
|
||||||
|
*Eval
|
||||||
|
packagePath string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run evaluates the function with the given name and then eliminates all
|
||||||
|
// callers.
|
||||||
func Run(mod llvm.Module, debug bool) error {
|
func Run(mod llvm.Module, debug bool) error {
|
||||||
r := runner{
|
if debug {
|
||||||
mod: mod,
|
println("\ncompile-time evaluation:")
|
||||||
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)
|
|
||||||
|
|
||||||
initAll := mod.NamedFunction("runtime.initAll")
|
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)
|
||||||
bb := initAll.EntryBasicBlock()
|
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
|
// 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
|
// to. This is necessary because otherwise we might be removing the
|
||||||
// instruction (init call) that we are removing after successful
|
// instruction (init call) that we are removing after successful
|
||||||
// interpretation.
|
// interpretation.
|
||||||
r.builder.SetInsertPointBefore(bb.FirstInstruction())
|
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||||
dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
|
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||||
r.builder.SetInsertPointBefore(dummy)
|
e.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
|
var initCalls []llvm.Value
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
if inst == dummy {
|
if inst == dummy {
|
||||||
@@ -81,67 +63,99 @@ func Run(mod llvm.Module, debug bool) error {
|
|||||||
break // ret void
|
break // ret void
|
||||||
}
|
}
|
||||||
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
||||||
return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
|
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
|
||||||
}
|
}
|
||||||
initCalls = append(initCalls, inst)
|
initCalls = append(initCalls, inst)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run initializers for each package. Once the package initializer is
|
// Do this in a separate step to avoid corrupting the iterator above.
|
||||||
// finished, the call to the package initializer can be removed.
|
undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
|
||||||
for _, call := range initCalls {
|
for _, call := range initCalls {
|
||||||
initName := call.CalledValue().Name()
|
initName := call.CalledValue().Name()
|
||||||
if !strings.HasSuffix(initName, ".init") {
|
if !strings.HasSuffix(initName, ".init") {
|
||||||
return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
|
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
|
||||||
}
|
}
|
||||||
r.pkgName = initName[:len(initName)-len(".init")]
|
pkgName := initName[:len(initName)-5]
|
||||||
fn := call.CalledValue()
|
fn := call.CalledValue()
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, "call:", fn.Name())
|
|
||||||
}
|
|
||||||
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
|
||||||
if callErr != nil {
|
|
||||||
if isRecoverableError(callErr.Err) {
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, "not interpretring", r.pkgName, "because of error:", callErr.Err)
|
|
||||||
}
|
|
||||||
mem.revert()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return callErr
|
|
||||||
}
|
|
||||||
call.EraseFromParentAsInstruction()
|
call.EraseFromParentAsInstruction()
|
||||||
for index, obj := range mem.objects {
|
evalPkg := evalPackage{
|
||||||
r.objects[index] = obj
|
Eval: e,
|
||||||
|
packagePath: pkgName,
|
||||||
}
|
}
|
||||||
}
|
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
|
||||||
r.pkgName = ""
|
if err == errUnreachable {
|
||||||
|
break
|
||||||
// Update all global variables in the LLVM module.
|
|
||||||
mem := memoryView{r: &r}
|
|
||||||
for _, obj := range r.objects {
|
|
||||||
if obj.llvmGlobal.IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if obj.buffer == nil {
|
if err != nil {
|
||||||
continue
|
return err
|
||||||
}
|
}
|
||||||
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
|
||||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
|
||||||
panic("initializer type mismatch")
|
|
||||||
}
|
|
||||||
obj.llvmGlobal.SetInitializer(initializer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFunction returns the compiled version of the given LLVM function. It
|
// function interprets the given function. The params are the function params
|
||||||
// compiles the function if necessary and caches the result.
|
// and the indent is the string indentation to use when dumping all interpreted
|
||||||
func (r *runner) getFunction(llvmFn llvm.Value) *function {
|
// instructions.
|
||||||
if fn, ok := r.functionCache[llvmFn]; ok {
|
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, *Error) {
|
||||||
return fn
|
fr := frame{
|
||||||
|
evalPackage: e,
|
||||||
|
fn: fn,
|
||||||
|
locals: make(map[llvm.Value]Value),
|
||||||
|
}
|
||||||
|
for i, param := range fn.Params() {
|
||||||
|
fr.locals[param] = params[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
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.
|
||||||
}
|
}
|
||||||
fn := r.compileFunction(llvmFn)
|
|
||||||
r.functionCache[llvmFn] = fn
|
|
||||||
return fn
|
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-35
@@ -3,7 +3,6 @@ package interp
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -42,29 +41,9 @@ func runTest(t *testing.T, pathPrefix string) {
|
|||||||
// Perform the transform.
|
// Perform the transform.
|
||||||
err = Run(mod, false)
|
err = Run(mod, false)
|
||||||
if err != nil {
|
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)
|
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.
|
// Run some cleanup passes to get easy-to-read outputs.
|
||||||
pm := llvm.NewPassManager()
|
pm := llvm.NewPassManager()
|
||||||
defer pm.Dispose()
|
defer pm.Dispose()
|
||||||
@@ -87,8 +66,6 @@ func runTest(t *testing.T, pathPrefix string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
|
|
||||||
|
|
||||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||||
// equal. That means, only relevant lines are compared (excluding comments
|
// equal. That means, only relevant lines are compared (excluding comments
|
||||||
// etc.).
|
// etc.).
|
||||||
@@ -98,18 +75,8 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
|||||||
if len(lines1) != len(lines2) {
|
if len(lines1) != len(lines2) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for i, line1 := range lines1 {
|
for i, line := range lines1 {
|
||||||
line2 := lines2[i]
|
if line != lines2[i] {
|
||||||
match1 := alignRegexp.MatchString(line1)
|
|
||||||
match2 := alignRegexp.MatchString(line2)
|
|
||||||
if match1 != match2 {
|
|
||||||
// Only one of the lines has the align keyword. Remove it.
|
|
||||||
// This is a change to make the test work in both LLVM 10 and LLVM
|
|
||||||
// 11 (LLVM 11 appears to automatically add alignment everywhere).
|
|
||||||
line1 = alignRegexp.ReplaceAllString(line1, "")
|
|
||||||
line2 = alignRegexp.ReplaceAllString(line2, "")
|
|
||||||
}
|
|
||||||
if line1 != line2 {
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,917 +0,0 @@
|
|||||||
package interp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent string) (value, memoryView, *Error) {
|
|
||||||
mem := memoryView{r: r, parent: parentMem}
|
|
||||||
locals := make([]value, len(fn.locals))
|
|
||||||
r.callsExecuted++
|
|
||||||
|
|
||||||
if time.Since(r.start) > time.Minute {
|
|
||||||
// Running for more than a minute. This should never happen.
|
|
||||||
return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than a minute, timing out (executed calls: %d)", r.callsExecuted))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parameters are considered a kind of local values.
|
|
||||||
for i, param := range params {
|
|
||||||
locals[i] = param
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with the first basic block and the first instruction.
|
|
||||||
// Branch instructions may modify both bb and instIndex when branching.
|
|
||||||
bb := fn.blocks[0]
|
|
||||||
currentBB := 0
|
|
||||||
lastBB := -1 // last basic block is undefined, only defined after a branch
|
|
||||||
var operands []value
|
|
||||||
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
|
|
||||||
inst := bb.instructions[instIndex]
|
|
||||||
operands = operands[:0]
|
|
||||||
isRuntimeInst := false
|
|
||||||
if inst.opcode != llvm.PHI {
|
|
||||||
for _, v := range inst.operands {
|
|
||||||
if v, ok := v.(localValue); ok {
|
|
||||||
if localVal := locals[fn.locals[v.value]]; localVal == nil {
|
|
||||||
return nil, mem, r.errorAt(inst, errors.New("interp: local not defined"))
|
|
||||||
} else {
|
|
||||||
operands = append(operands, localVal)
|
|
||||||
if _, ok := localVal.(localValue); ok {
|
|
||||||
isRuntimeInst = true
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
operands = append(operands, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if isRuntimeInst {
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
switch inst.opcode {
|
|
||||||
case llvm.Ret:
|
|
||||||
if len(operands) != 0 {
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"ret", operands[0])
|
|
||||||
}
|
|
||||||
// Return instruction has a value to return.
|
|
||||||
return operands[0], mem, nil
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"ret")
|
|
||||||
}
|
|
||||||
// Return instruction doesn't return anything, it's just 'ret void'.
|
|
||||||
return nil, mem, nil
|
|
||||||
case llvm.Br:
|
|
||||||
switch len(operands) {
|
|
||||||
case 1:
|
|
||||||
// Unconditional branch: [nextBB]
|
|
||||||
lastBB = currentBB
|
|
||||||
currentBB = int(operands[0].(literalValue).value.(uint32))
|
|
||||||
bb = fn.blocks[currentBB]
|
|
||||||
instIndex = -1 // start at 0 the next cycle
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
// Conditional branch: [cond, thenBB, elseBB]
|
|
||||||
lastBB = currentBB
|
|
||||||
switch operands[0].Uint() {
|
|
||||||
case 1: // true -> thenBB
|
|
||||||
currentBB = int(operands[1].(literalValue).value.(uint32))
|
|
||||||
case 0: // false -> elseBB
|
|
||||||
currentBB = int(operands[2].(literalValue).value.(uint32))
|
|
||||||
default:
|
|
||||||
panic("bool should be 0 or 1")
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
|
||||||
}
|
|
||||||
bb = fn.blocks[currentBB]
|
|
||||||
instIndex = -1 // start at 0 the next cycle
|
|
||||||
default:
|
|
||||||
panic("unknown operands length")
|
|
||||||
}
|
|
||||||
break // continue with next block
|
|
||||||
case llvm.PHI:
|
|
||||||
var result value
|
|
||||||
for i := 0; i < len(inst.operands); i += 2 {
|
|
||||||
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
|
|
||||||
incoming := inst.operands[i+1]
|
|
||||||
if local, ok := incoming.(localValue); ok {
|
|
||||||
result = locals[fn.locals[local.value]]
|
|
||||||
} else {
|
|
||||||
result = incoming
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
|
|
||||||
}
|
|
||||||
if result == nil {
|
|
||||||
panic("could not find PHI input")
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = result
|
|
||||||
case llvm.Select:
|
|
||||||
// Select is much like a ternary operator: it picks a result from
|
|
||||||
// the second and third operand based on the boolean first operand.
|
|
||||||
var result value
|
|
||||||
switch operands[0].Uint() {
|
|
||||||
case 1:
|
|
||||||
result = operands[1]
|
|
||||||
case 0:
|
|
||||||
result = operands[2]
|
|
||||||
default:
|
|
||||||
panic("boolean must be 0 or 1")
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = result
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"select", operands, "->", result)
|
|
||||||
}
|
|
||||||
case llvm.Call:
|
|
||||||
// A call instruction can either be a regular call or a runtime intrinsic.
|
|
||||||
fnPtr, err := operands[0].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
callFn := r.getFunction(fnPtr.llvmValue(&mem))
|
|
||||||
switch {
|
|
||||||
case callFn.name == "runtime.trackPointer":
|
|
||||||
// Allocas and such are created as globals, so don't need a
|
|
||||||
// runtime.trackPointer.
|
|
||||||
// Unless the object is allocated at runtime for example, in
|
|
||||||
// which case this call won't even get to this point but will
|
|
||||||
// already be emitted in initAll.
|
|
||||||
continue
|
|
||||||
case callFn.name == "(reflect.Type).Elem" || strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
|
|
||||||
// These functions should be run at runtime. Specifically:
|
|
||||||
// * (reflect.Type).Elem is a special function. It should
|
|
||||||
// eventually be interpreted, but fall back to a runtime call
|
|
||||||
// for now.
|
|
||||||
// * Print and panic functions are best emitted directly without
|
|
||||||
// interpreting them, otherwise we get a ton of putchar (etc.)
|
|
||||||
// calls.
|
|
||||||
// * runtime.hashmapGet tries to access the map value directly.
|
|
||||||
// This is not possible as the map value is treated as a special
|
|
||||||
// kind of object in this package.
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
case callFn.name == "runtime.nanotime" && r.pkgName == "time":
|
|
||||||
// The time package contains a call to runtime.nanotime.
|
|
||||||
// This appears to be to work around a limitation in Windows
|
|
||||||
// Server 2008:
|
|
||||||
// > Monotonic times are reported as offsets from startNano.
|
|
||||||
// > We initialize startNano to runtimeNano() - 1 so that on systems where
|
|
||||||
// > monotonic time resolution is fairly low (e.g. Windows 2008
|
|
||||||
// > which appears to have a default resolution of 15ms),
|
|
||||||
// > we avoid ever reporting a monotonic time of 0.
|
|
||||||
// > (Callers may want to use 0 as "time not set".)
|
|
||||||
// Simply let runtime.nanotime return 0 in this case, which
|
|
||||||
// should be fine and avoids a call to runtime.nanotime. It
|
|
||||||
// means that monotonic time in the time package is counted from
|
|
||||||
// time.Time{}.Sub(1), which should be fine.
|
|
||||||
locals[inst.localIndex] = literalValue{uint64(0)}
|
|
||||||
case callFn.name == "runtime.alloc":
|
|
||||||
// Allocate heap memory. At compile time, this is instead done
|
|
||||||
// by creating a global variable.
|
|
||||||
|
|
||||||
// Get the requested memory size to be allocated.
|
|
||||||
size := operands[1].Uint()
|
|
||||||
|
|
||||||
// Create the object.
|
|
||||||
alloc := object{
|
|
||||||
globalName: r.pkgName + "$alloc",
|
|
||||||
buffer: newRawValue(uint32(size)),
|
|
||||||
size: uint32(size),
|
|
||||||
}
|
|
||||||
index := len(r.objects)
|
|
||||||
r.objects = append(r.objects, alloc)
|
|
||||||
|
|
||||||
// And create a pointer to this object, for working with it (so
|
|
||||||
// that stores to it copy it, etc).
|
|
||||||
ptr := newPointerValue(r, index, 0)
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = ptr
|
|
||||||
case callFn.name == "runtime.sliceCopy":
|
|
||||||
// sliceCopy implements the built-in copy function for slices.
|
|
||||||
// It is implemented here so that it can be used even if the
|
|
||||||
// runtime implementation is not available. Doing it this way
|
|
||||||
// may also be faster.
|
|
||||||
// Code:
|
|
||||||
// func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int {
|
|
||||||
// n := srcLen
|
|
||||||
// if n > dstLen {
|
|
||||||
// n = dstLen
|
|
||||||
// }
|
|
||||||
// memmove(dst, src, n*elemSize)
|
|
||||||
// return int(n)
|
|
||||||
// }
|
|
||||||
dstLen := operands[3].Uint()
|
|
||||||
srcLen := operands[4].Uint()
|
|
||||||
elemSize := operands[5].Uint()
|
|
||||||
n := srcLen
|
|
||||||
if n > dstLen {
|
|
||||||
n = dstLen
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n)
|
|
||||||
}
|
|
||||||
if n != 0 {
|
|
||||||
// Only try to copy bytes when there are any bytes to copy.
|
|
||||||
// This is not just an optimization. If one of the slices
|
|
||||||
// (or both) are nil, the asPointer method call will fail
|
|
||||||
// even though copying a nil slice is allowed.
|
|
||||||
dst, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
src, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
nBytes := uint32(n * elemSize)
|
|
||||||
dstObj := mem.getWritable(dst.index())
|
|
||||||
dstBuf := dstObj.buffer.asRawValue(r)
|
|
||||||
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
|
||||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
|
||||||
dstObj.buffer = dstBuf
|
|
||||||
mem.put(dst.index(), dstObj)
|
|
||||||
}
|
|
||||||
switch inst.llvmInst.Type().IntTypeWidth() {
|
|
||||||
case 16:
|
|
||||||
locals[inst.localIndex] = literalValue{uint16(n)}
|
|
||||||
case 32:
|
|
||||||
locals[inst.localIndex] = literalValue{uint32(n)}
|
|
||||||
case 64:
|
|
||||||
locals[inst.localIndex] = literalValue{uint64(n)}
|
|
||||||
default:
|
|
||||||
panic("unknown integer type width")
|
|
||||||
}
|
|
||||||
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0i8.p0i8.") || strings.HasPrefix(callFn.name, "llvm.memmove.p0i8.p0i8."):
|
|
||||||
// Copy a block of memory from one pointer to another.
|
|
||||||
dst, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
src, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
nBytes := uint32(operands[3].Uint())
|
|
||||||
dstObj := mem.getWritable(dst.index())
|
|
||||||
dstBuf := dstObj.buffer.asRawValue(r)
|
|
||||||
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
|
||||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
|
||||||
dstObj.buffer = dstBuf
|
|
||||||
mem.put(dst.index(), dstObj)
|
|
||||||
case callFn.name == "runtime.typeAssert":
|
|
||||||
// This function must be implemented manually as it is normally
|
|
||||||
// implemented by the interface lowering pass.
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
|
|
||||||
}
|
|
||||||
typeInInterfacePtr, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
assertedType, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
result := assertedType.asRawValue(r).equal(actualType.asRawValue(r))
|
|
||||||
if result {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
||||||
} else {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
||||||
}
|
|
||||||
case callFn.name == "runtime.interfaceImplements":
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load various values for the interface implements check below.
|
|
||||||
typeInInterfacePtr, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
|
||||||
interfaceMethodSetPtr, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
|
|
||||||
|
|
||||||
// Make a set of all the methods on the concrete type, for
|
|
||||||
// easier checking in the next step.
|
|
||||||
concreteTypeMethods := 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()
|
|
||||||
concreteTypeMethods[name] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether all interface methods are also in the list
|
|
||||||
// of defined methods calculated above. This is the interface
|
|
||||||
// assert itself.
|
|
||||||
assertOk := uint8(1) // i1 true
|
|
||||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
|
||||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
|
||||||
if _, ok := concreteTypeMethods[name]; !ok {
|
|
||||||
// There is a method on the interface that is not
|
|
||||||
// implemented by the type. The assertion will fail.
|
|
||||||
assertOk = 0 // i1 false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If assertOk is still 1, the assertion succeeded.
|
|
||||||
locals[inst.localIndex] = literalValue{assertOk}
|
|
||||||
case callFn.name == "runtime.hashmapMake":
|
|
||||||
// Create a new map.
|
|
||||||
hashmapPointerType := inst.llvmInst.Type()
|
|
||||||
keySize := uint32(operands[1].Uint())
|
|
||||||
valueSize := uint32(operands[2].Uint())
|
|
||||||
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
|
|
||||||
alloc := object{
|
|
||||||
llvmType: hashmapPointerType,
|
|
||||||
globalName: r.pkgName + "$map",
|
|
||||||
buffer: m,
|
|
||||||
size: m.len(r),
|
|
||||||
}
|
|
||||||
index := len(r.objects)
|
|
||||||
r.objects = append(r.objects, alloc)
|
|
||||||
|
|
||||||
// Create a pointer to this map. Maps are reference types, so
|
|
||||||
// are implemented as pointers.
|
|
||||||
ptr := newPointerValue(r, index, 0)
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = ptr
|
|
||||||
case callFn.name == "runtime.hashmapBinarySet":
|
|
||||||
// Do a mapassign operation with a binary key (that is, without
|
|
||||||
// a string key).
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
|
|
||||||
}
|
|
||||||
mapPtr, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
|
|
||||||
keyPtr, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
valuePtr, err := operands[3].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
err = m.putBinary(&mem, keyPtr, valuePtr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
case callFn.name == "runtime.hashmapStringSet":
|
|
||||||
// Do a mapassign operation with a string key.
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
|
|
||||||
}
|
|
||||||
mapPtr, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
|
|
||||||
stringPtr, err := operands[2].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
stringLen := operands[3].Uint()
|
|
||||||
valuePtr, err := operands[4].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if len(callFn.blocks) == 0 {
|
|
||||||
// Call to a function declaration without a definition
|
|
||||||
// available.
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Call a function with a definition available. Run it as usual,
|
|
||||||
// possibly trying to recover from it if it failed to execute.
|
|
||||||
if r.debug {
|
|
||||||
argStrings := make([]string, len(operands)-1)
|
|
||||||
for i := range argStrings {
|
|
||||||
argStrings[i] = operands[i+1].String()
|
|
||||||
}
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"call:", callFn.name+"("+strings.Join(argStrings, ", ")+")")
|
|
||||||
}
|
|
||||||
retval, callMem, callErr := r.run(callFn, operands[1:], &mem, indent+" ")
|
|
||||||
if callErr != nil {
|
|
||||||
if isRecoverableError(callErr.Err) {
|
|
||||||
// This error can be recovered by doing the call at
|
|
||||||
// runtime instead of at compile time. But we need to
|
|
||||||
// revert any changes made by the call first.
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"!! revert because of error:", callErr.Err)
|
|
||||||
}
|
|
||||||
callMem.revert()
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Add to the traceback, so that error handling code can see
|
|
||||||
// how this function got called.
|
|
||||||
callErr.Traceback = append(callErr.Traceback, ErrorLine{
|
|
||||||
Pos: getPosition(inst.llvmInst),
|
|
||||||
Inst: inst.llvmInst,
|
|
||||||
})
|
|
||||||
return nil, mem, callErr
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = retval
|
|
||||||
mem.extend(callMem)
|
|
||||||
}
|
|
||||||
case llvm.Load:
|
|
||||||
// Load instruction, loading some data from the topmost memory view.
|
|
||||||
ptr, err := operands[0].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
size := operands[1].(literalValue).value.(uint64)
|
|
||||||
if mem.hasExternalStore(ptr) {
|
|
||||||
// If there could be an external store (for example, because a
|
|
||||||
// pointer to the object was passed to a function that could not
|
|
||||||
// be interpreted at compile time) then the load must be done at
|
|
||||||
// runtime.
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result := mem.load(ptr, uint32(size))
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = result
|
|
||||||
case llvm.Store:
|
|
||||||
// Store instruction. Create a new object in the memory view and
|
|
||||||
// store to that, to make it possible to roll back this store.
|
|
||||||
ptr, err := operands[1].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
if mem.hasExternalLoadOrStore(ptr) {
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val := operands[0]
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
|
|
||||||
}
|
|
||||||
mem.store(val, ptr)
|
|
||||||
case llvm.Alloca:
|
|
||||||
// Alloca normally allocates some stack memory. In the interpreter,
|
|
||||||
// it allocates a global instead.
|
|
||||||
// This can likely be optimized, as all it really needs is an alloca
|
|
||||||
// in the initAll function and creating a global is wasteful for
|
|
||||||
// this purpose.
|
|
||||||
|
|
||||||
// Create the new object.
|
|
||||||
size := operands[0].(literalValue).value.(uint64)
|
|
||||||
alloca := object{
|
|
||||||
llvmType: inst.llvmInst.Type(),
|
|
||||||
globalName: r.pkgName + "$alloca",
|
|
||||||
buffer: newRawValue(uint32(size)),
|
|
||||||
size: uint32(size),
|
|
||||||
}
|
|
||||||
index := len(r.objects)
|
|
||||||
r.objects = append(r.objects, alloca)
|
|
||||||
|
|
||||||
// Create a pointer to this object (an alloca produces a pointer).
|
|
||||||
ptr := newPointerValue(r, index, 0)
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"alloca:", operands, "->", ptr)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = ptr
|
|
||||||
case llvm.GetElementPtr:
|
|
||||||
// GetElementPtr does pointer arithmetic, changing the offset of the
|
|
||||||
// pointer into the underlying object.
|
|
||||||
var offset uint64
|
|
||||||
var gepOperands []uint64
|
|
||||||
for i := 2; i < len(operands); i += 2 {
|
|
||||||
index := operands[i].Uint()
|
|
||||||
elementSize := operands[i+1].Uint()
|
|
||||||
if int64(elementSize) < 0 {
|
|
||||||
// This is a struct field.
|
|
||||||
// The field number is encoded by flipping all the bits.
|
|
||||||
gepOperands = append(gepOperands, ^elementSize)
|
|
||||||
offset += index
|
|
||||||
} else {
|
|
||||||
// This is a normal GEP, probably an array index.
|
|
||||||
gepOperands = append(gepOperands, index)
|
|
||||||
offset += elementSize * index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ptr, err := operands[0].asPointer(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, r.errorAt(inst, err)
|
|
||||||
}
|
|
||||||
ptr = ptr.addOffset(uint32(offset))
|
|
||||||
locals[inst.localIndex] = ptr
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
|
|
||||||
}
|
|
||||||
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
|
|
||||||
// Various bitcast-like instructions that all keep the same bits
|
|
||||||
// while changing the LLVM type.
|
|
||||||
// Because interp doesn't preserve the type, these operations are
|
|
||||||
// identity operations.
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", operands[0])
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = operands[0]
|
|
||||||
case llvm.ExtractValue:
|
|
||||||
agg := operands[0].asRawValue(r)
|
|
||||||
offset := operands[1].(literalValue).value.(uint64)
|
|
||||||
size := operands[2].(literalValue).value.(uint64)
|
|
||||||
elt := rawValue{
|
|
||||||
buf: agg.buf[offset : offset+size],
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"extractvalue:", operands, "->", elt)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = elt
|
|
||||||
case llvm.InsertValue:
|
|
||||||
agg := operands[0].asRawValue(r)
|
|
||||||
elt := operands[1].asRawValue(r)
|
|
||||||
offset := int(operands[2].(literalValue).value.(uint64))
|
|
||||||
newagg := newRawValue(uint32(len(agg.buf)))
|
|
||||||
copy(newagg.buf, agg.buf)
|
|
||||||
copy(newagg.buf[offset:], elt.buf)
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"insertvalue:", operands, "->", newagg)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = newagg
|
|
||||||
case llvm.ICmp:
|
|
||||||
predicate := llvm.IntPredicate(operands[2].(literalValue).value.(uint8))
|
|
||||||
var result bool
|
|
||||||
lhs := operands[0]
|
|
||||||
rhs := operands[1]
|
|
||||||
switch predicate {
|
|
||||||
case llvm.IntEQ, llvm.IntNE:
|
|
||||||
lhsPointer, lhsErr := lhs.asPointer(r)
|
|
||||||
rhsPointer, rhsErr := rhs.asPointer(r)
|
|
||||||
if (lhsErr == nil) != (rhsErr == nil) {
|
|
||||||
// Fast path: only one is a pointer, so they can't be equal.
|
|
||||||
result = false
|
|
||||||
} else if lhsErr == nil {
|
|
||||||
// Both must be nil, so both are pointers.
|
|
||||||
// Compare them directly.
|
|
||||||
result = lhsPointer.equal(rhsPointer)
|
|
||||||
} else {
|
|
||||||
// Fall back to generic comparison.
|
|
||||||
result = lhs.asRawValue(r).equal(rhs.asRawValue(r))
|
|
||||||
}
|
|
||||||
if predicate == llvm.IntNE {
|
|
||||||
result = !result
|
|
||||||
}
|
|
||||||
case llvm.IntUGT:
|
|
||||||
result = lhs.Uint() > rhs.Uint()
|
|
||||||
case llvm.IntUGE:
|
|
||||||
result = lhs.Uint() >= rhs.Uint()
|
|
||||||
case llvm.IntULT:
|
|
||||||
result = lhs.Uint() < rhs.Uint()
|
|
||||||
case llvm.IntULE:
|
|
||||||
result = lhs.Uint() <= rhs.Uint()
|
|
||||||
case llvm.IntSGT:
|
|
||||||
result = lhs.Int() > rhs.Int()
|
|
||||||
case llvm.IntSGE:
|
|
||||||
result = lhs.Int() >= rhs.Int()
|
|
||||||
case llvm.IntSLT:
|
|
||||||
result = lhs.Int() < rhs.Int()
|
|
||||||
case llvm.IntSLE:
|
|
||||||
result = lhs.Int() <= rhs.Int()
|
|
||||||
default:
|
|
||||||
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported icmp"))
|
|
||||||
}
|
|
||||||
if result {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
||||||
} else {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"icmp:", operands[0], intPredicateString(predicate), operands[1], "->", result)
|
|
||||||
}
|
|
||||||
case llvm.FCmp:
|
|
||||||
predicate := llvm.FloatPredicate(operands[2].(literalValue).value.(uint8))
|
|
||||||
var result bool
|
|
||||||
var lhs, rhs float64
|
|
||||||
switch operands[0].len(r) {
|
|
||||||
case 8:
|
|
||||||
lhs = math.Float64frombits(operands[0].Uint())
|
|
||||||
rhs = math.Float64frombits(operands[1].Uint())
|
|
||||||
case 4:
|
|
||||||
lhs = float64(math.Float32frombits(uint32(operands[0].Uint())))
|
|
||||||
rhs = float64(math.Float32frombits(uint32(operands[1].Uint())))
|
|
||||||
default:
|
|
||||||
panic("unknown float type")
|
|
||||||
}
|
|
||||||
switch predicate {
|
|
||||||
case llvm.FloatOEQ:
|
|
||||||
result = lhs == rhs
|
|
||||||
case llvm.FloatUNE:
|
|
||||||
result = lhs != rhs
|
|
||||||
case llvm.FloatOGT:
|
|
||||||
result = lhs > rhs
|
|
||||||
case llvm.FloatOGE:
|
|
||||||
result = lhs >= rhs
|
|
||||||
case llvm.FloatOLT:
|
|
||||||
result = lhs < rhs
|
|
||||||
case llvm.FloatOLE:
|
|
||||||
result = lhs <= rhs
|
|
||||||
default:
|
|
||||||
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported fcmp"))
|
|
||||||
}
|
|
||||||
if result {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
|
||||||
} else {
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+"fcmp:", operands[0], predicate, operands[1], "->", result)
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
lhs := operands[0]
|
|
||||||
rhs := operands[1]
|
|
||||||
lhsPtr, err := lhs.asPointer(r)
|
|
||||||
if err == nil {
|
|
||||||
// The lhs is a pointer. This sometimes happens for particular
|
|
||||||
// pointer tricks.
|
|
||||||
switch inst.opcode {
|
|
||||||
case llvm.Add:
|
|
||||||
// This likely means this is part of a
|
|
||||||
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
|
||||||
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
|
|
||||||
locals[inst.localIndex] = lhsPtr
|
|
||||||
continue
|
|
||||||
case llvm.Xor:
|
|
||||||
if rhs.Uint() == 0 {
|
|
||||||
// Special workaround for strings.noescape, see
|
|
||||||
// src/strings/builder.go in the Go source tree. This is
|
|
||||||
// the identity operator, so we can return the input.
|
|
||||||
locals[inst.localIndex] = lhs
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// Catch-all for weird operations that should just be done
|
|
||||||
// at runtime.
|
|
||||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, mem, err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var result uint64
|
|
||||||
switch inst.opcode {
|
|
||||||
case llvm.Add:
|
|
||||||
result = lhs.Uint() + rhs.Uint()
|
|
||||||
case llvm.Sub:
|
|
||||||
result = lhs.Uint() - rhs.Uint()
|
|
||||||
case llvm.Mul:
|
|
||||||
result = lhs.Uint() * rhs.Uint()
|
|
||||||
case llvm.UDiv:
|
|
||||||
result = lhs.Uint() / rhs.Uint()
|
|
||||||
case llvm.SDiv:
|
|
||||||
result = uint64(lhs.Int() / rhs.Int())
|
|
||||||
case llvm.URem:
|
|
||||||
result = lhs.Uint() % rhs.Uint()
|
|
||||||
case llvm.SRem:
|
|
||||||
result = uint64(lhs.Int() % rhs.Int())
|
|
||||||
case llvm.Shl:
|
|
||||||
result = lhs.Uint() << rhs.Uint()
|
|
||||||
case llvm.LShr:
|
|
||||||
result = lhs.Uint() >> rhs.Uint()
|
|
||||||
case llvm.AShr:
|
|
||||||
result = uint64(lhs.Int() >> rhs.Uint())
|
|
||||||
case llvm.And:
|
|
||||||
result = lhs.Uint() & rhs.Uint()
|
|
||||||
case llvm.Or:
|
|
||||||
result = lhs.Uint() | rhs.Uint()
|
|
||||||
case llvm.Xor:
|
|
||||||
result = lhs.Uint() ^ rhs.Uint()
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
switch lhs.len(r) {
|
|
||||||
case 8:
|
|
||||||
locals[inst.localIndex] = literalValue{result}
|
|
||||||
case 4:
|
|
||||||
locals[inst.localIndex] = literalValue{uint32(result)}
|
|
||||||
case 2:
|
|
||||||
locals[inst.localIndex] = literalValue{uint16(result)}
|
|
||||||
case 1:
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(result)}
|
|
||||||
default:
|
|
||||||
panic("unknown integer size")
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
|
|
||||||
}
|
|
||||||
case llvm.SExt, llvm.ZExt, llvm.Trunc:
|
|
||||||
// Change the size of an integer to a larger or smaller bit width.
|
|
||||||
// We make use of the fact that the Uint() function already
|
|
||||||
// zero-extends the value and that Int() already sign-extends the
|
|
||||||
// value, so we only need to truncate it to the appropriate bit
|
|
||||||
// width. This means we can implement sext, zext and trunc in the
|
|
||||||
// same way, by first {zero,sign}extending all the way up to uint64
|
|
||||||
// and then truncating it as necessary.
|
|
||||||
var value uint64
|
|
||||||
if inst.opcode == llvm.SExt {
|
|
||||||
value = uint64(operands[0].Int())
|
|
||||||
} else {
|
|
||||||
value = operands[0].Uint()
|
|
||||||
}
|
|
||||||
bitwidth := operands[1].Uint()
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
|
||||||
}
|
|
||||||
switch bitwidth {
|
|
||||||
case 64:
|
|
||||||
locals[inst.localIndex] = literalValue{value}
|
|
||||||
case 32:
|
|
||||||
locals[inst.localIndex] = literalValue{uint32(value)}
|
|
||||||
case 16:
|
|
||||||
locals[inst.localIndex] = literalValue{uint16(value)}
|
|
||||||
case 8:
|
|
||||||
locals[inst.localIndex] = literalValue{uint8(value)}
|
|
||||||
default:
|
|
||||||
panic("unknown integer size in sext/zext/trunc")
|
|
||||||
}
|
|
||||||
case llvm.SIToFP, llvm.UIToFP:
|
|
||||||
var value float64
|
|
||||||
switch inst.opcode {
|
|
||||||
case llvm.SIToFP:
|
|
||||||
value = float64(operands[0].Int())
|
|
||||||
case llvm.UIToFP:
|
|
||||||
value = float64(operands[0].Uint())
|
|
||||||
}
|
|
||||||
bitwidth := operands[1].Uint()
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
|
||||||
}
|
|
||||||
switch bitwidth {
|
|
||||||
case 64:
|
|
||||||
locals[inst.localIndex] = literalValue{math.Float64bits(value)}
|
|
||||||
case 32:
|
|
||||||
locals[inst.localIndex] = literalValue{math.Float32bits(float32(value))}
|
|
||||||
default:
|
|
||||||
panic("unknown integer size in sitofp/uitofp")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+inst.String())
|
|
||||||
}
|
|
||||||
return nil, mem, r.errorAt(inst, errUnsupportedInst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
|
|
||||||
numOperands := inst.llvmInst.OperandsCount()
|
|
||||||
operands := make([]llvm.Value, numOperands)
|
|
||||||
for i := 0; i < numOperands; i++ {
|
|
||||||
operand := inst.llvmInst.Operand(i)
|
|
||||||
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
|
||||||
operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
|
|
||||||
}
|
|
||||||
operands[i] = operand
|
|
||||||
}
|
|
||||||
if r.debug {
|
|
||||||
fmt.Fprintln(os.Stderr, indent+inst.String())
|
|
||||||
}
|
|
||||||
var result llvm.Value
|
|
||||||
switch inst.opcode {
|
|
||||||
case llvm.Call:
|
|
||||||
llvmFn := operands[len(operands)-1]
|
|
||||||
args := operands[:len(operands)-1]
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg.Type().TypeKind() == llvm.PointerTypeKind {
|
|
||||||
mem.markExternalStore(arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result = r.builder.CreateCall(llvmFn, args, inst.name)
|
|
||||||
case llvm.Load:
|
|
||||||
mem.markExternalLoad(operands[0])
|
|
||||||
result = r.builder.CreateLoad(operands[0], inst.name)
|
|
||||||
if inst.llvmInst.IsVolatile() {
|
|
||||||
result.SetVolatile(true)
|
|
||||||
}
|
|
||||||
case llvm.Store:
|
|
||||||
mem.markExternalStore(operands[1])
|
|
||||||
result = r.builder.CreateStore(operands[0], operands[1])
|
|
||||||
if inst.llvmInst.IsVolatile() {
|
|
||||||
result.SetVolatile(true)
|
|
||||||
}
|
|
||||||
case llvm.BitCast:
|
|
||||||
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
|
|
||||||
case llvm.ExtractValue:
|
|
||||||
indices := inst.llvmInst.Indices()
|
|
||||||
if len(indices) != 1 {
|
|
||||||
panic("expected exactly one index")
|
|
||||||
}
|
|
||||||
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
|
|
||||||
case llvm.InsertValue:
|
|
||||||
indices := inst.llvmInst.Indices()
|
|
||||||
if len(indices) != 1 {
|
|
||||||
panic("expected exactly one index")
|
|
||||||
}
|
|
||||||
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
|
|
||||||
case llvm.Add:
|
|
||||||
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.Sub:
|
|
||||||
result = r.builder.CreateSub(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.Mul:
|
|
||||||
result = r.builder.CreateMul(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.UDiv:
|
|
||||||
result = r.builder.CreateUDiv(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.SDiv:
|
|
||||||
result = r.builder.CreateSDiv(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.URem:
|
|
||||||
result = r.builder.CreateURem(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.SRem:
|
|
||||||
result = r.builder.CreateSRem(operands[0], operands[1], inst.name)
|
|
||||||
case llvm.ZExt:
|
|
||||||
result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name)
|
|
||||||
default:
|
|
||||||
return r.errorAt(inst, errUnsupportedRuntimeInst)
|
|
||||||
}
|
|
||||||
locals[inst.localIndex] = localValue{result}
|
|
||||||
mem.instructions = append(mem.instructions, result)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func intPredicateString(predicate llvm.IntPredicate) string {
|
|
||||||
switch predicate {
|
|
||||||
case llvm.IntEQ:
|
|
||||||
return "eq"
|
|
||||||
case llvm.IntNE:
|
|
||||||
return "ne"
|
|
||||||
case llvm.IntUGT:
|
|
||||||
return "ugt"
|
|
||||||
case llvm.IntUGE:
|
|
||||||
return "uge"
|
|
||||||
case llvm.IntULT:
|
|
||||||
return "ult"
|
|
||||||
case llvm.IntULE:
|
|
||||||
return "ule"
|
|
||||||
case llvm.IntSGT:
|
|
||||||
return "sgt"
|
|
||||||
case llvm.IntSGE:
|
|
||||||
return "sge"
|
|
||||||
case llvm.IntSLT:
|
|
||||||
return "slt"
|
|
||||||
case llvm.IntSLE:
|
|
||||||
return "sle"
|
|
||||||
default:
|
|
||||||
return "cmp?"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-1430
File diff suppressed because it is too large
Load Diff
+253
@@ -0,0 +1,253 @@
|
|||||||
|
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 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.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{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
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
-29
@@ -4,10 +4,6 @@ target triple = "x86_64--linux"
|
|||||||
@main.v1 = internal global i64 0
|
@main.v1 = internal global i64 0
|
||||||
@main.nonConst1 = global [4 x i64] zeroinitializer
|
@main.nonConst1 = global [4 x i64] zeroinitializer
|
||||||
@main.nonConst2 = global i64 0
|
@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
|
|
||||||
|
|
||||||
declare void @runtime.printint64(i64) unnamed_addr
|
declare void @runtime.printint64(i64) unnamed_addr
|
||||||
|
|
||||||
@@ -51,20 +47,6 @@ entry:
|
|||||||
%value2 = load i64, i64* %gep2
|
%value2 = load i64, i64* %gep2
|
||||||
store i64 %value2, i64* @main.nonConst2
|
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
|
|
||||||
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,14 +58,3 @@ entry:
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare i64 @someValue()
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
Vendored
-17
@@ -3,10 +3,6 @@ target triple = "x86_64--linux"
|
|||||||
|
|
||||||
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
@main.nonConst1 = local_unnamed_addr global [4 x i64] zeroinitializer
|
||||||
@main.nonConst2 = local_unnamed_addr global i64 0
|
@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
|
|
||||||
|
|
||||||
declare void @runtime.printint64(i64) unnamed_addr
|
declare void @runtime.printint64(i64) unnamed_addr
|
||||||
|
|
||||||
@@ -20,11 +16,6 @@ entry:
|
|||||||
store i64 %value1, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
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)
|
%value2 = load i64, i64* getelementptr inbounds ([4 x i64], [4 x i64]* @main.nonConst1, i32 0, i32 0)
|
||||||
store i64 %value2, i64* @main.nonConst2
|
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
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,11 +27,3 @@ entry:
|
|||||||
}
|
}
|
||||||
|
|
||||||
declare i64 @someValue() local_unnamed_addr
|
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
|
|
||||||
}
|
|
||||||
|
|||||||
Vendored
+4
-2
@@ -48,7 +48,8 @@ entry:
|
|||||||
define internal void @main.testNonConstantBinarySet() {
|
define internal void @main.testNonConstantBinarySet() {
|
||||||
%hashmap.key = alloca i8
|
%hashmap.key = alloca i8
|
||||||
%hashmap.value = alloca i8
|
%hashmap.value = alloca i8
|
||||||
; Create hashmap from global.
|
; 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)
|
%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
|
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.binaryMap
|
||||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
|
%map = load %runtime.hashmap*, %runtime.hashmap** @main.binaryMap
|
||||||
@@ -63,7 +64,8 @@ define internal void @main.testNonConstantBinarySet() {
|
|||||||
; operations (with string keys).
|
; operations (with string keys).
|
||||||
define internal void @main.testNonConstantStringSet() {
|
define internal void @main.testNonConstantStringSet() {
|
||||||
%hashmap.value = alloca i8
|
%hashmap.value = alloca i8
|
||||||
; Create hashmap from global.
|
; 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)
|
%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
|
store %runtime.hashmap* %map.new, %runtime.hashmap** @main.stringMap
|
||||||
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
|
%map = load %runtime.hashmap*, %runtime.hashmap** @main.stringMap
|
||||||
|
|||||||
Vendored
+16
-8
@@ -2,19 +2,27 @@ target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
|||||||
target triple = "armv6m-none-eabi"
|
target triple = "armv6m-none-eabi"
|
||||||
|
|
||||||
%runtime.hashmap = type { %runtime.hashmap*, i8*, i32, i8, i8, i8 }
|
%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.m = local_unnamed_addr global %runtime.hashmap* @"main$map"
|
||||||
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.1"
|
@main.binaryMap = local_unnamed_addr global %runtime.hashmap* @"main$map.4"
|
||||||
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.3"
|
@main.stringMap = local_unnamed_addr global %runtime.hashmap* @"main$map.6"
|
||||||
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
@main.init.string = internal unnamed_addr constant [7 x i8] c"CONNECT"
|
||||||
@"main$map" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } }* @"main$mapbucket", i32 0, i32 0, i32 0), i32 1, i8 1, i8 8, i8 0 }
|
@"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$mapbucket" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] 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$map.1" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.2", i32 0, i32 0, i32 0), i32 1, i8 1, i8 1, i8 0 }
|
@"main$alloca.2" = internal global i8 1
|
||||||
@"main$mapbucket.2" = internal unnamed_addr global { [8 x i8], i8*, { i8, [7 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"\04\00\00\00\00\00\00\00", i8* null, { i8, [7 x i8] } { i8 1, [7 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
|
@"main$alloca.3" = internal global i8 2
|
||||||
@"main$map.3" = internal global %runtime.hashmap { %runtime.hashmap* null, i8* getelementptr inbounds ({ [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }, { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } }* @"main$mapbucket.4", i32 0, i32 0, i32 0), i32 1, i8 8, i8 1, i8 0 }
|
@"main$map.4" = internal unnamed_addr global %runtime.hashmap { %runtime.hashmap* null, i8* null, i32 0, i8 1, i8 1, i8 0 }
|
||||||
@"main$mapbucket.4" = internal unnamed_addr global { [8 x i8], i8*, { { [7 x i8]*, [4 x i8] }, [56 x i8] }, { i8, [7 x i8] } } { [8 x i8] c"x\00\00\00\00\00\00\00", i8* null, { { [7 x i8]*, [4 x i8] }, [56 x i8] } { { [7 x i8]*, [4 x i8] } { [7 x i8]* @main.init.string, [4 x i8] c"\07\00\00\00" }, [56 x i8] zeroinitializer }, { i8, [7 x i8] } { i8 2, [7 x i8] zeroinitializer } }
|
@"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 {
|
define void @runtime.initAll() unnamed_addr {
|
||||||
entry:
|
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
|
ret void
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+78
@@ -0,0 +1,78 @@
|
|||||||
|
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
+1
-4
@@ -1,8 +1,6 @@
|
|||||||
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
|
||||||
target triple = "x86_64--linux"
|
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.printuint8(i8) local_unnamed_addr
|
||||||
|
|
||||||
declare void @runtime.printint16(i16) local_unnamed_addr
|
declare void @runtime.printint16(i16) local_unnamed_addr
|
||||||
@@ -17,7 +15,6 @@ entry:
|
|||||||
call void @runtime.printuint8(i8 3)
|
call void @runtime.printuint8(i8 3)
|
||||||
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 5)
|
||||||
call void @runtime.printint16(i16 %int16SliceDst.val)
|
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|||||||
+121
@@ -0,0 +1,121 @@
|
|||||||
|
package interp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return a list of values (actually, instructions) where this value is used as
|
||||||
|
// an operand.
|
||||||
|
func getUses(value llvm.Value) []llvm.Value {
|
||||||
|
var uses []llvm.Value
|
||||||
|
use := value.FirstUse()
|
||||||
|
for !use.IsNil() {
|
||||||
|
uses = append(uses, use.User())
|
||||||
|
use = use.NextUse()
|
||||||
|
}
|
||||||
|
return uses
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStringBytes loads the byte slice of a Go string represented as a
|
||||||
|
// {ptr, len} pair.
|
||||||
|
func getStringBytes(strPtr Value, strLen llvm.Value) []byte {
|
||||||
|
if !strLen.IsConstant() {
|
||||||
|
panic("getStringBytes with a non-constant length")
|
||||||
|
}
|
||||||
|
buf := make([]byte, strLen.ZExtValue())
|
||||||
|
for i := range buf {
|
||||||
|
gep, err := strPtr.GetElementPtr([]uint32{uint32(i)})
|
||||||
|
if err != nil {
|
||||||
|
panic(err) // TODO
|
||||||
|
}
|
||||||
|
c := gep.Load()
|
||||||
|
buf[i] = byte(c.ZExtValue())
|
||||||
|
}
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLLVMIndices converts an []uint32 into an []llvm.Value, for use in
|
||||||
|
// llvm.ConstGEP.
|
||||||
|
func getLLVMIndices(int32Type llvm.Type, indices []uint32) []llvm.Value {
|
||||||
|
llvmIndices := make([]llvm.Value, len(indices))
|
||||||
|
for i, index := range indices {
|
||||||
|
llvmIndices[i] = llvm.ConstInt(int32Type, uint64(index), false)
|
||||||
|
}
|
||||||
|
return llvmIndices
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if this type is a scalar value (integer or floating point), false
|
||||||
|
// otherwise.
|
||||||
|
func isScalar(t llvm.Type) bool {
|
||||||
|
switch t.TypeKind() {
|
||||||
|
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPointerNil returns whether this is a nil pointer or not. The ok value
|
||||||
|
// indicates whether the result is certain: if it is false the result boolean is
|
||||||
|
// not valid.
|
||||||
|
func isPointerNil(v llvm.Value) (result bool, ok bool) {
|
||||||
|
if !v.IsAConstantExpr().IsNil() {
|
||||||
|
switch v.Opcode() {
|
||||||
|
case llvm.IntToPtr:
|
||||||
|
// Whether a constant inttoptr is nil is easy to
|
||||||
|
// determine.
|
||||||
|
result, ok = isZero(v.Operand(0))
|
||||||
|
if ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case llvm.BitCast, llvm.GetElementPtr:
|
||||||
|
// These const instructions are just a kind of wrappers for the
|
||||||
|
// underlying pointer.
|
||||||
|
return isPointerNil(v.Operand(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !v.IsAConstantPointerNull().IsNil() {
|
||||||
|
// A constant pointer null is always null, of course.
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
if !v.IsAGlobalValue().IsNil() {
|
||||||
|
// A global value is never null.
|
||||||
|
return false, true
|
||||||
|
}
|
||||||
|
return false, false // not valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// isZero returns whether the value in v is the integer zero, and whether that
|
||||||
|
// can be known right now.
|
||||||
|
func isZero(v llvm.Value) (result bool, ok bool) {
|
||||||
|
if !v.IsAConstantExpr().IsNil() {
|
||||||
|
switch v.Opcode() {
|
||||||
|
case llvm.PtrToInt:
|
||||||
|
return isPointerNil(v.Operand(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !v.IsAConstantInt().IsNil() {
|
||||||
|
val := v.ZExtValue()
|
||||||
|
return val == 0, true
|
||||||
|
}
|
||||||
|
return false, false // not valid
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap returns the underlying value, with GEPs removed. This can be useful to
|
||||||
|
// get the underlying global of a GEP pointer.
|
||||||
|
func unwrap(value llvm.Value) llvm.Value {
|
||||||
|
for {
|
||||||
|
if !value.IsAConstantExpr().IsNil() {
|
||||||
|
switch value.Opcode() {
|
||||||
|
case llvm.GetElementPtr:
|
||||||
|
value = value.Operand(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if !value.IsAGetElementPtrInst().IsNil() {
|
||||||
|
value = value.Operand(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
@@ -0,0 +1,419 @@
|
|||||||
|
package interp
|
||||||
|
|
||||||
|
// This file provides a litte bit of abstraction around LLVM values.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A Value is a LLVM value with some extra methods attached for easier
|
||||||
|
// interpretation.
|
||||||
|
type Value interface {
|
||||||
|
Value() llvm.Value // returns a LLVM value
|
||||||
|
Type() llvm.Type // equal to Value().Type()
|
||||||
|
IsConstant() bool // returns true if this value is a constant value
|
||||||
|
Load() llvm.Value // dereference a pointer
|
||||||
|
Store(llvm.Value) // store to a pointer
|
||||||
|
GetElementPtr([]uint32) (Value, error) // returns an interior pointer
|
||||||
|
String() string // string representation, for debugging
|
||||||
|
}
|
||||||
|
|
||||||
|
// A type that simply wraps a LLVM constant value.
|
||||||
|
type LocalValue struct {
|
||||||
|
Eval *Eval
|
||||||
|
Underlying llvm.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value implements Value by returning the constant value itself.
|
||||||
|
func (v *LocalValue) Value() llvm.Value {
|
||||||
|
return v.Underlying
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *LocalValue) Type() llvm.Type {
|
||||||
|
return v.Underlying.Type()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *LocalValue) IsConstant() bool {
|
||||||
|
if _, ok := v.Eval.dirtyGlobals[unwrap(v.Underlying)]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return v.Underlying.IsConstant()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load loads a constant value if this is a constant pointer.
|
||||||
|
func (v *LocalValue) Load() llvm.Value {
|
||||||
|
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||||
|
return v.Underlying.Initializer()
|
||||||
|
}
|
||||||
|
switch v.Underlying.Opcode() {
|
||||||
|
case llvm.GetElementPtr:
|
||||||
|
indices := v.getConstGEPIndices()
|
||||||
|
if indices[0] != 0 {
|
||||||
|
panic("invalid GEP")
|
||||||
|
}
|
||||||
|
global := v.Eval.getValue(v.Underlying.Operand(0))
|
||||||
|
agg := global.Load()
|
||||||
|
return llvm.ConstExtractValue(agg, indices[1:])
|
||||||
|
case llvm.BitCast:
|
||||||
|
panic("interp: load from a bitcast")
|
||||||
|
default:
|
||||||
|
panic("interp: load from a constant")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stores to the underlying value if the value type is a pointer type,
|
||||||
|
// otherwise it panics.
|
||||||
|
func (v *LocalValue) Store(value llvm.Value) {
|
||||||
|
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||||
|
if !value.IsConstant() {
|
||||||
|
v.MarkDirty()
|
||||||
|
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||||
|
} else {
|
||||||
|
v.Underlying.SetInitializer(value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !value.IsConstant() {
|
||||||
|
v.MarkDirty()
|
||||||
|
v.Eval.builder.CreateStore(value, v.Underlying)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch v.Underlying.Opcode() {
|
||||||
|
case llvm.GetElementPtr:
|
||||||
|
indices := v.getConstGEPIndices()
|
||||||
|
if indices[0] != 0 {
|
||||||
|
panic("invalid GEP")
|
||||||
|
}
|
||||||
|
global := &LocalValue{v.Eval, v.Underlying.Operand(0)}
|
||||||
|
agg := global.Load()
|
||||||
|
agg = llvm.ConstInsertValue(agg, value, indices[1:])
|
||||||
|
global.Store(agg)
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
panic("interp: store on a constant")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetElementPtr returns a GEP when the underlying value is of pointer type.
|
||||||
|
func (v *LocalValue) GetElementPtr(indices []uint32) (Value, error) {
|
||||||
|
if !v.Underlying.IsAGlobalVariable().IsNil() {
|
||||||
|
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||||
|
gep := llvm.ConstGEP(v.Underlying, getLLVMIndices(int32Type, indices))
|
||||||
|
return &LocalValue{v.Eval, gep}, nil
|
||||||
|
}
|
||||||
|
if !v.Underlying.IsAConstantExpr().IsNil() {
|
||||||
|
switch v.Underlying.Opcode() {
|
||||||
|
case llvm.GetElementPtr, llvm.IntToPtr, llvm.BitCast:
|
||||||
|
int32Type := v.Underlying.Type().Context().Int32Type()
|
||||||
|
llvmIndices := getLLVMIndices(int32Type, indices)
|
||||||
|
return &LocalValue{v.Eval, llvm.ConstGEP(v.Underlying, llvmIndices)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("interp: unknown GEP")
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripPointerCasts removes all const bitcasts from pointer values, if there
|
||||||
|
// are any.
|
||||||
|
func (v *LocalValue) stripPointerCasts() *LocalValue {
|
||||||
|
value := v.Underlying
|
||||||
|
for {
|
||||||
|
if !value.IsAConstantExpr().IsNil() {
|
||||||
|
switch value.Opcode() {
|
||||||
|
case llvm.BitCast:
|
||||||
|
value = value.Operand(0)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &LocalValue{
|
||||||
|
Eval: v.Eval,
|
||||||
|
Underlying: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *LocalValue) String() string {
|
||||||
|
isConstant := "false"
|
||||||
|
if v.IsConstant() {
|
||||||
|
isConstant = "true"
|
||||||
|
}
|
||||||
|
return "&LocalValue{Type: " + v.Type().String() + ", IsConstant: " + isConstant + "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConstGEPIndices returns indices of this constant GEP, if this is a GEP
|
||||||
|
// instruction. If it is not, the behavior is undefined.
|
||||||
|
func (v *LocalValue) getConstGEPIndices() []uint32 {
|
||||||
|
indices := make([]uint32, v.Underlying.OperandsCount()-1)
|
||||||
|
for i := range indices {
|
||||||
|
operand := v.Underlying.Operand(i + 1)
|
||||||
|
indices[i] = uint32(operand.ZExtValue())
|
||||||
|
}
|
||||||
|
return indices
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkDirty marks this global as dirty, meaning that every load from and store
|
||||||
|
// to this global (from now on) must be performed at runtime.
|
||||||
|
func (v *LocalValue) MarkDirty() {
|
||||||
|
underlying := unwrap(v.Underlying)
|
||||||
|
if underlying.IsAGlobalVariable().IsNil() {
|
||||||
|
panic("trying to mark a non-global as dirty")
|
||||||
|
}
|
||||||
|
if !v.IsConstant() {
|
||||||
|
return // already dirty
|
||||||
|
}
|
||||||
|
v.Eval.dirtyGlobals[underlying] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapValue implements a Go map which is created at compile time and stored as a
|
||||||
|
// global variable.
|
||||||
|
type MapValue struct {
|
||||||
|
Eval *Eval
|
||||||
|
PkgName string
|
||||||
|
Underlying llvm.Value
|
||||||
|
Keys []Value
|
||||||
|
Values []Value
|
||||||
|
KeySize int
|
||||||
|
ValueSize int
|
||||||
|
KeyType llvm.Type
|
||||||
|
ValueType llvm.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MapValue) newBucket() llvm.Value {
|
||||||
|
ctx := v.Eval.Mod.Context()
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
bucketType := ctx.StructType([]llvm.Type{
|
||||||
|
llvm.ArrayType(ctx.Int8Type(), 8), // tophash
|
||||||
|
i8ptrType, // next bucket
|
||||||
|
llvm.ArrayType(v.KeyType, 8), // key type
|
||||||
|
llvm.ArrayType(v.ValueType, 8), // value type
|
||||||
|
}, false)
|
||||||
|
bucketValue := llvm.ConstNull(bucketType)
|
||||||
|
bucket := llvm.AddGlobal(v.Eval.Mod, bucketType, v.PkgName+"$mapbucket")
|
||||||
|
bucket.SetInitializer(bucketValue)
|
||||||
|
bucket.SetLinkage(llvm.InternalLinkage)
|
||||||
|
bucket.SetUnnamedAddr(true)
|
||||||
|
return bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns a global variable which is a pointer to the actual hashmap.
|
||||||
|
func (v *MapValue) Value() llvm.Value {
|
||||||
|
if !v.Underlying.IsNil() {
|
||||||
|
return v.Underlying
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := v.Eval.Mod.Context()
|
||||||
|
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
|
||||||
|
var firstBucketGlobal llvm.Value
|
||||||
|
if len(v.Keys) == 0 {
|
||||||
|
// there are no buckets
|
||||||
|
firstBucketGlobal = llvm.ConstPointerNull(i8ptrType)
|
||||||
|
} else {
|
||||||
|
// create initial bucket
|
||||||
|
firstBucketGlobal = v.newBucket()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert each key/value pair in the hashmap.
|
||||||
|
bucketGlobal := firstBucketGlobal
|
||||||
|
for i, key := range v.Keys {
|
||||||
|
var keyBuf []byte
|
||||||
|
llvmKey := key.Value()
|
||||||
|
llvmValue := v.Values[i].Value()
|
||||||
|
if key.Type().TypeKind() == llvm.StructTypeKind && key.Type().StructName() == "runtime._string" {
|
||||||
|
keyPtr := llvm.ConstExtractValue(llvmKey, []uint32{0})
|
||||||
|
keyLen := llvm.ConstExtractValue(llvmKey, []uint32{1})
|
||||||
|
keyPtrVal := v.Eval.getValue(keyPtr)
|
||||||
|
keyBuf = getStringBytes(keyPtrVal, keyLen)
|
||||||
|
} else if key.Type().TypeKind() == llvm.IntegerTypeKind {
|
||||||
|
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
|
||||||
|
n := key.Value().ZExtValue()
|
||||||
|
for i := range keyBuf {
|
||||||
|
keyBuf[i] = byte(n)
|
||||||
|
n >>= 8
|
||||||
|
}
|
||||||
|
} else if key.Type().TypeKind() == llvm.ArrayTypeKind &&
|
||||||
|
key.Type().ElementType().TypeKind() == llvm.IntegerTypeKind &&
|
||||||
|
key.Type().ElementType().IntTypeWidth() == 8 {
|
||||||
|
keyBuf = make([]byte, v.Eval.TargetData.TypeAllocSize(key.Type()))
|
||||||
|
for i := range keyBuf {
|
||||||
|
keyBuf[i] = byte(llvm.ConstExtractValue(llvmKey, []uint32{uint32(i)}).ZExtValue())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("interp: map key type not implemented: " + key.Type().String())
|
||||||
|
}
|
||||||
|
hash := v.hash(keyBuf)
|
||||||
|
|
||||||
|
if i%8 == 0 && i != 0 {
|
||||||
|
// Bucket is full, create a new one.
|
||||||
|
newBucketGlobal := v.newBucket()
|
||||||
|
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
|
||||||
|
newBucketPtr := llvm.ConstInBoundsGEP(newBucketGlobal, []llvm.Value{zero})
|
||||||
|
newBucketPtrCast := llvm.ConstBitCast(newBucketPtr, i8ptrType)
|
||||||
|
// insert pointer into old bucket
|
||||||
|
bucket := bucketGlobal.Initializer()
|
||||||
|
bucket = llvm.ConstInsertValue(bucket, newBucketPtrCast, []uint32{1})
|
||||||
|
bucketGlobal.SetInitializer(bucket)
|
||||||
|
// switch to next bucket
|
||||||
|
bucketGlobal = newBucketGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
tophashValue := llvm.ConstInt(ctx.Int8Type(), uint64(v.topHash(hash)), false)
|
||||||
|
bucket := bucketGlobal.Initializer()
|
||||||
|
bucket = llvm.ConstInsertValue(bucket, tophashValue, []uint32{0, uint32(i % 8)})
|
||||||
|
bucket = llvm.ConstInsertValue(bucket, llvmKey, []uint32{2, uint32(i % 8)})
|
||||||
|
bucket = llvm.ConstInsertValue(bucket, llvmValue, []uint32{3, uint32(i % 8)})
|
||||||
|
bucketGlobal.SetInitializer(bucket)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hashmap itself.
|
||||||
|
zero := llvm.ConstInt(ctx.Int32Type(), 0, false)
|
||||||
|
bucketPtr := llvm.ConstInBoundsGEP(firstBucketGlobal, []llvm.Value{zero})
|
||||||
|
hashmapType := v.Type()
|
||||||
|
hashmap := llvm.ConstNamedStruct(hashmapType, []llvm.Value{
|
||||||
|
llvm.ConstPointerNull(llvm.PointerType(hashmapType, 0)), // next
|
||||||
|
llvm.ConstBitCast(bucketPtr, i8ptrType), // buckets
|
||||||
|
llvm.ConstInt(hashmapType.StructElementTypes()[2], uint64(len(v.Keys)), false), // count
|
||||||
|
llvm.ConstInt(ctx.Int8Type(), uint64(v.KeySize), false), // keySize
|
||||||
|
llvm.ConstInt(ctx.Int8Type(), uint64(v.ValueSize), false), // valueSize
|
||||||
|
llvm.ConstInt(ctx.Int8Type(), 0, false), // bucketBits
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a pointer to this hashmap.
|
||||||
|
hashmapPtr := llvm.AddGlobal(v.Eval.Mod, hashmap.Type(), v.PkgName+"$map")
|
||||||
|
hashmapPtr.SetInitializer(hashmap)
|
||||||
|
hashmapPtr.SetLinkage(llvm.InternalLinkage)
|
||||||
|
hashmapPtr.SetUnnamedAddr(true)
|
||||||
|
v.Underlying = llvm.ConstInBoundsGEP(hashmapPtr, []llvm.Value{zero})
|
||||||
|
return v.Underlying
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns type runtime.hashmap, which is the actual hashmap type.
|
||||||
|
func (v *MapValue) Type() llvm.Type {
|
||||||
|
return v.Eval.Mod.GetTypeByName("runtime.hashmap")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MapValue) IsConstant() bool {
|
||||||
|
return true // TODO: dirty maps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load panics: maps are of reference type so cannot be dereferenced.
|
||||||
|
func (v *MapValue) Load() llvm.Value {
|
||||||
|
panic("interp: load from a map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store panics: maps are of reference type so cannot be stored to.
|
||||||
|
func (v *MapValue) Store(value llvm.Value) {
|
||||||
|
panic("interp: store on a map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetElementPtr panics: maps are of reference type so their (interior)
|
||||||
|
// addresses cannot be calculated.
|
||||||
|
func (v *MapValue) GetElementPtr(indices []uint32) (Value, error) {
|
||||||
|
return nil, errors.New("interp: GEP on a map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutString does a map assign operation, assuming that the map is of type
|
||||||
|
// map[string]T.
|
||||||
|
func (v *MapValue) PutString(keyBuf, keyLen, valPtr *LocalValue) {
|
||||||
|
if !v.Underlying.IsNil() {
|
||||||
|
panic("map already created")
|
||||||
|
}
|
||||||
|
|
||||||
|
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||||
|
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||||
|
}
|
||||||
|
value := valPtr.Load()
|
||||||
|
if v.ValueType.IsNil() {
|
||||||
|
v.ValueType = value.Type()
|
||||||
|
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||||
|
panic("interp: map store value type has the wrong size")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if value.Type() != v.ValueType {
|
||||||
|
panic("interp: map store value type is inconsistent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyType := v.Eval.Mod.GetTypeByName("runtime._string")
|
||||||
|
v.KeyType = keyType
|
||||||
|
key := llvm.ConstNull(keyType)
|
||||||
|
key = llvm.ConstInsertValue(key, keyBuf.Value(), []uint32{0})
|
||||||
|
key = llvm.ConstInsertValue(key, keyLen.Value(), []uint32{1})
|
||||||
|
|
||||||
|
// TODO: avoid duplicate keys
|
||||||
|
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
|
||||||
|
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutBinary does a map assign operation.
|
||||||
|
func (v *MapValue) PutBinary(keyPtr, valPtr *LocalValue) {
|
||||||
|
if !v.Underlying.IsNil() {
|
||||||
|
panic("map already created")
|
||||||
|
}
|
||||||
|
|
||||||
|
if valPtr.Underlying.Opcode() == llvm.BitCast {
|
||||||
|
valPtr = &LocalValue{v.Eval, valPtr.Underlying.Operand(0)}
|
||||||
|
}
|
||||||
|
value := valPtr.Load()
|
||||||
|
if v.ValueType.IsNil() {
|
||||||
|
v.ValueType = value.Type()
|
||||||
|
if int(v.Eval.TargetData.TypeAllocSize(v.ValueType)) != v.ValueSize {
|
||||||
|
panic("interp: map store value type has the wrong size")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if value.Type() != v.ValueType {
|
||||||
|
panic("interp: map store value type is inconsistent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !keyPtr.Underlying.IsAConstantExpr().IsNil() {
|
||||||
|
if keyPtr.Underlying.Opcode() == llvm.BitCast {
|
||||||
|
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||||
|
} else if keyPtr.Underlying.Opcode() == llvm.GetElementPtr {
|
||||||
|
keyPtr = &LocalValue{v.Eval, keyPtr.Underlying.Operand(0)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key := keyPtr.Load()
|
||||||
|
if v.KeyType.IsNil() {
|
||||||
|
v.KeyType = key.Type()
|
||||||
|
if int(v.Eval.TargetData.TypeAllocSize(v.KeyType)) != v.KeySize {
|
||||||
|
panic("interp: map store key type has the wrong size")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if key.Type() != v.KeyType {
|
||||||
|
panic("interp: map store key type is inconsistent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: avoid duplicate keys
|
||||||
|
v.Keys = append(v.Keys, &LocalValue{v.Eval, key})
|
||||||
|
v.Values = append(v.Values, &LocalValue{v.Eval, value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get FNV-1a hash of this string.
|
||||||
|
//
|
||||||
|
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1a_hash
|
||||||
|
func (v *MapValue) hash(data []byte) uint32 {
|
||||||
|
var result uint32 = 2166136261 // FNV offset basis
|
||||||
|
for _, c := range data {
|
||||||
|
result ^= uint32(c)
|
||||||
|
result *= 16777619 // FNV prime
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the topmost 8 bits of the hash, without using a special value (like 0).
|
||||||
|
func (v *MapValue) topHash(hash uint32) uint8 {
|
||||||
|
tophash := uint8(hash >> 24)
|
||||||
|
if tophash < 1 {
|
||||||
|
// 0 means empty slot, so make it bigger.
|
||||||
|
tophash += 1
|
||||||
|
}
|
||||||
|
return tophash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *MapValue) String() string {
|
||||||
|
return "&MapValue{KeySize: " + strconv.Itoa(v.KeySize) + ", ValueSize: " + strconv.Itoa(v.ValueSize) + "}"
|
||||||
|
}
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
package ir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/types"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
|
"golang.org/x/tools/go/ssa"
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file provides a wrapper around go/ssa values and adds extra
|
||||||
|
// functionality to them.
|
||||||
|
|
||||||
|
// View on all functions, types, and globals in a program, with analysis
|
||||||
|
// results.
|
||||||
|
type Program struct {
|
||||||
|
Program *ssa.Program
|
||||||
|
LoaderProgram *loader.Program
|
||||||
|
mainPkg *ssa.Package
|
||||||
|
Functions []*Function
|
||||||
|
functionMap map[*ssa.Function]*Function
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function or method.
|
||||||
|
type Function struct {
|
||||||
|
*ssa.Function
|
||||||
|
LLVMFn llvm.Value
|
||||||
|
module string // go:wasm-module
|
||||||
|
linkName string // go:linkname, go:export
|
||||||
|
exported bool // go:export
|
||||||
|
nobounds bool // go:nobounds
|
||||||
|
flag bool // used by dead code elimination
|
||||||
|
inline InlineType // go:inline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface type that is at some point used in a type assert (to check whether
|
||||||
|
// it implements another interface).
|
||||||
|
type Interface struct {
|
||||||
|
Num int
|
||||||
|
Type *types.Interface
|
||||||
|
}
|
||||||
|
|
||||||
|
type InlineType int
|
||||||
|
|
||||||
|
// How much to inline.
|
||||||
|
const (
|
||||||
|
// Default behavior. The compiler decides for itself whether any given
|
||||||
|
// function will be inlined. Whether any function is inlined depends on the
|
||||||
|
// optimization level.
|
||||||
|
InlineDefault InlineType = iota
|
||||||
|
|
||||||
|
// Inline hint, just like the C inline keyword (signalled using
|
||||||
|
// //go:inline). The compiler will be more likely to inline this function,
|
||||||
|
// but it is not a guarantee.
|
||||||
|
InlineHint
|
||||||
|
|
||||||
|
// Don't inline, just like the GCC noinline attribute. Signalled using
|
||||||
|
// //go:noinline.
|
||||||
|
InlineNone
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create and initialize a new *Program from a *ssa.Program.
|
||||||
|
func NewProgram(lprogram *loader.Program) *Program {
|
||||||
|
program := lprogram.LoadSSA()
|
||||||
|
program.Build()
|
||||||
|
|
||||||
|
// Find the main package, which is a bit difficult when running a .go file
|
||||||
|
// directly.
|
||||||
|
mainPkg := program.ImportedPackage(lprogram.MainPkg.PkgPath)
|
||||||
|
if mainPkg == nil {
|
||||||
|
panic("could not find main package")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a list of packages in import order.
|
||||||
|
packageList := []*ssa.Package{}
|
||||||
|
packageSet := map[string]struct{}{}
|
||||||
|
worklist := []string{"runtime", lprogram.MainPkg.PkgPath}
|
||||||
|
for len(worklist) != 0 {
|
||||||
|
pkgPath := worklist[0]
|
||||||
|
pkg := program.ImportedPackage(pkgPath)
|
||||||
|
if _, ok := packageSet[pkgPath]; ok {
|
||||||
|
// Package already in the final package list.
|
||||||
|
worklist = worklist[1:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
unsatisfiedImports := make([]string, 0)
|
||||||
|
imports := pkg.Pkg.Imports()
|
||||||
|
for _, pkg := range imports {
|
||||||
|
if _, ok := packageSet[pkg.Path()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
unsatisfiedImports = append(unsatisfiedImports, pkg.Path())
|
||||||
|
}
|
||||||
|
if len(unsatisfiedImports) == 0 {
|
||||||
|
// All dependencies of this package are satisfied, so add this
|
||||||
|
// package to the list.
|
||||||
|
packageList = append(packageList, pkg)
|
||||||
|
packageSet[pkgPath] = struct{}{}
|
||||||
|
worklist = worklist[1:]
|
||||||
|
} else {
|
||||||
|
// Prepend all dependencies to the worklist and reconsider this
|
||||||
|
// package (by not removing it from the worklist). At that point, it
|
||||||
|
// must be possible to add it to packageList.
|
||||||
|
worklist = append(unsatisfiedImports, worklist...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &Program{
|
||||||
|
Program: program,
|
||||||
|
LoaderProgram: lprogram,
|
||||||
|
mainPkg: mainPkg,
|
||||||
|
functionMap: make(map[*ssa.Function]*Function),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range packageList {
|
||||||
|
p.AddPackage(pkg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a package to this Program. All packages need to be added first before any
|
||||||
|
// analysis is done for correct results.
|
||||||
|
func (p *Program) AddPackage(pkg *ssa.Package) {
|
||||||
|
memberNames := make([]string, 0)
|
||||||
|
for name := range pkg.Members {
|
||||||
|
memberNames = append(memberNames, name)
|
||||||
|
}
|
||||||
|
sort.Strings(memberNames)
|
||||||
|
|
||||||
|
for _, name := range memberNames {
|
||||||
|
member := pkg.Members[name]
|
||||||
|
switch member := member.(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
p.addFunction(member)
|
||||||
|
case *ssa.Type:
|
||||||
|
methods := getAllMethods(pkg.Prog, member.Type())
|
||||||
|
if !types.IsInterface(member.Type()) {
|
||||||
|
// named type
|
||||||
|
for _, method := range methods {
|
||||||
|
p.addFunction(pkg.Prog.MethodValue(method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case *ssa.Global:
|
||||||
|
// Ignore. Globals are not handled here.
|
||||||
|
case *ssa.NamedConst:
|
||||||
|
// Ignore: these are already resolved.
|
||||||
|
default:
|
||||||
|
panic("unknown member type: " + member.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) addFunction(ssaFn *ssa.Function) {
|
||||||
|
if _, ok := p.functionMap[ssaFn]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := &Function{Function: ssaFn}
|
||||||
|
f.parsePragmas()
|
||||||
|
p.Functions = append(p.Functions, f)
|
||||||
|
p.functionMap[ssaFn] = f
|
||||||
|
|
||||||
|
for _, anon := range ssaFn.AnonFuncs {
|
||||||
|
p.addFunction(anon)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) GetFunction(ssaFn *ssa.Function) *Function {
|
||||||
|
return p.functionMap[ssaFn]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Program) MainPkg() *ssa.Package {
|
||||||
|
return p.mainPkg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse compiler directives in the preceding comments.
|
||||||
|
func (f *Function) parsePragmas() {
|
||||||
|
if f.Syntax() == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
f.linkName = parts[1]
|
||||||
|
f.exported = true
|
||||||
|
case "//go:wasm-module":
|
||||||
|
// Alternative comment for setting the import module.
|
||||||
|
if len(parts) != 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.module = parts[1]
|
||||||
|
case "//go:inline":
|
||||||
|
f.inline = InlineHint
|
||||||
|
case "//go:noinline":
|
||||||
|
f.inline = InlineNone
|
||||||
|
case "//go: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) {
|
||||||
|
f.linkName = parts[2]
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
f.nobounds = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Function) IsNoBounds() bool {
|
||||||
|
return f.nobounds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true iff this function is externally visible.
|
||||||
|
func (f *Function) IsExported() bool {
|
||||||
|
return f.exported || f.CName() != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the inline directive of this function.
|
||||||
|
func (f *Function) Inline() InlineType {
|
||||||
|
return f.inline
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the module name if not the default.
|
||||||
|
func (f *Function) Module() string {
|
||||||
|
return f.module
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the link name for this function.
|
||||||
|
func (f *Function) LinkName() string {
|
||||||
|
if f.linkName != "" {
|
||||||
|
return f.linkName
|
||||||
|
}
|
||||||
|
if f.Signature.Recv() != nil {
|
||||||
|
// Method on a defined type (which may be a pointer).
|
||||||
|
return f.RelString(nil)
|
||||||
|
} else {
|
||||||
|
// Bare function.
|
||||||
|
if name := f.CName(); name != "" {
|
||||||
|
// Name CGo functions directly.
|
||||||
|
return name
|
||||||
|
} else {
|
||||||
|
return f.RelString(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the name of the C function if this is a CGo wrapper. Otherwise, return
|
||||||
|
// a zero-length string.
|
||||||
|
func (f *Function) CName() string {
|
||||||
|
name := f.Name()
|
||||||
|
if strings.HasPrefix(name, "_Cfunc_") {
|
||||||
|
// emitted by `go tool cgo`
|
||||||
|
return name[len("_Cfunc_"):]
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(name, "C.") {
|
||||||
|
// created by ../loader/cgo.go
|
||||||
|
return name[2:]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
package ir
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ssa"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file implements several optimization passes (analysis + transform) to
|
||||||
|
// optimize code in SSA form before it is compiled to LLVM IR. It is based on
|
||||||
|
// the IR defined in ir.go.
|
||||||
|
|
||||||
|
// Make 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple pass that removes dead code. This pass makes later analysis passes
|
||||||
|
// more useful.
|
||||||
|
func (p *Program) SimpleDCE() error {
|
||||||
|
// Unmark all functions.
|
||||||
|
for _, f := range p.Functions {
|
||||||
|
f.flag = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial set of live functions. Include main.main, *.init and runtime.*
|
||||||
|
// functions.
|
||||||
|
main, ok := p.mainPkg.Members["main"].(*ssa.Function)
|
||||||
|
if !ok {
|
||||||
|
if p.mainPkg.Members["main"] == nil {
|
||||||
|
return errors.New("function main is undeclared in the main package")
|
||||||
|
} else {
|
||||||
|
return errors.New("cannot declare main - must be func")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtimePkg := p.Program.ImportedPackage("runtime")
|
||||||
|
mathPkg := p.Program.ImportedPackage("math")
|
||||||
|
taskPkg := p.Program.ImportedPackage("internal/task")
|
||||||
|
p.GetFunction(main).flag = true
|
||||||
|
worklist := []*ssa.Function{main}
|
||||||
|
for _, f := range p.Functions {
|
||||||
|
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
||||||
|
if f.flag {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.flag = true
|
||||||
|
worklist = append(worklist, f.Function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all called functions recursively.
|
||||||
|
for len(worklist) != 0 {
|
||||||
|
f := worklist[len(worklist)-1]
|
||||||
|
worklist = worklist[:len(worklist)-1]
|
||||||
|
for _, block := range f.Blocks {
|
||||||
|
for _, instr := range block.Instrs {
|
||||||
|
if instr, ok := instr.(*ssa.MakeInterface); ok {
|
||||||
|
for _, sel := range getAllMethods(p.Program, instr.X.Type()) {
|
||||||
|
fn := p.Program.MethodValue(sel)
|
||||||
|
callee := p.GetFunction(fn)
|
||||||
|
if callee == nil {
|
||||||
|
// TODO: why is this necessary?
|
||||||
|
p.addFunction(fn)
|
||||||
|
callee = p.GetFunction(fn)
|
||||||
|
}
|
||||||
|
if !callee.flag {
|
||||||
|
callee.flag = true
|
||||||
|
worklist = append(worklist, callee.Function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, operand := range instr.Operands(nil) {
|
||||||
|
if operand == nil || *operand == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch operand := (*operand).(type) {
|
||||||
|
case *ssa.Function:
|
||||||
|
f := p.GetFunction(operand)
|
||||||
|
if f == nil {
|
||||||
|
// FIXME HACK: this function should have been
|
||||||
|
// discovered already. It is not for bound methods.
|
||||||
|
p.addFunction(operand)
|
||||||
|
f = p.GetFunction(operand)
|
||||||
|
}
|
||||||
|
if !f.flag {
|
||||||
|
f.flag = true
|
||||||
|
worklist = append(worklist, operand)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unmarked functions.
|
||||||
|
livefunctions := []*Function{}
|
||||||
|
for _, f := range p.Functions {
|
||||||
|
if f.flag {
|
||||||
|
livefunctions = append(livefunctions, f)
|
||||||
|
} else {
|
||||||
|
delete(p.functionMap, f.Function)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Functions = livefunctions
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+1
-1
Submodule lib/cmsis-svd updated: d9b58694ce...15b462f152
+1
-1
Submodule lib/nrfx updated: d779b49fc5...3ab39a9d45
Submodule lib/stm32-svd deleted from c6b5be976f
+1
-1
Submodule lib/wasi-libc updated: 215adc8ac9...a280fead2a
@@ -1,7 +1,5 @@
|
|||||||
package loader
|
package loader
|
||||||
|
|
||||||
import "go/scanner"
|
|
||||||
|
|
||||||
// Errors contains a list of parser errors or a list of typechecker errors for
|
// Errors contains a list of parser errors or a list of typechecker errors for
|
||||||
// the given package.
|
// the given package.
|
||||||
type Errors struct {
|
type Errors struct {
|
||||||
@@ -12,14 +10,3 @@ type Errors struct {
|
|||||||
func (e Errors) Error() string {
|
func (e Errors) Error() string {
|
||||||
return "could not compile: " + e.Errs[0].Error()
|
return "could not compile: " + e.Errs[0].Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error is a regular error but with an added import stack. This is especially
|
|
||||||
// useful for debugging import cycle errors.
|
|
||||||
type Error struct {
|
|
||||||
ImportStack []string
|
|
||||||
Err scanner.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e Error) Error() string {
|
|
||||||
return e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|||||||
+18
-74
@@ -7,22 +7,19 @@ import (
|
|||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"strconv"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
var gorootCreateMutex sync.Mutex
|
|
||||||
|
|
||||||
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
|
// GetCachedGoroot creates a new GOROOT by merging both the standard GOROOT and
|
||||||
// the GOROOT from TinyGo using lots of symbolic links.
|
// the GOROOT from TinyGo using lots of symbolic links.
|
||||||
func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
||||||
@@ -35,45 +32,30 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
|||||||
return "", errors.New("could not determine TINYGOROOT")
|
return "", errors.New("could not determine TINYGOROOT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needsSyscallPackage := false
|
||||||
|
for _, tag := range config.BuildTags() {
|
||||||
|
if tag == "baremetal" || tag == "darwin" {
|
||||||
|
needsSyscallPackage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine the location of the cached GOROOT.
|
// Determine the location of the cached GOROOT.
|
||||||
version, err := goenv.GorootVersionString(goroot)
|
version, err := goenv.GorootVersionString(goroot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
// This hash is really a cache key, that contains (hopefully) enough
|
gorootsHash := sha512.Sum512_256([]byte(goroot + "\x00" + tinygoroot))
|
||||||
// information to make collisions unlikely during development.
|
|
||||||
// By including the Go version and TinyGo version, cache collisions should
|
|
||||||
// not happen outside of development.
|
|
||||||
hash := sha512.New512_256()
|
|
||||||
fmt.Fprintln(hash, goroot)
|
|
||||||
fmt.Fprintln(hash, version)
|
|
||||||
fmt.Fprintln(hash, goenv.Version)
|
|
||||||
fmt.Fprintln(hash, tinygoroot)
|
|
||||||
gorootsHash := hash.Sum(nil)
|
|
||||||
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
|
gorootsHashHex := hex.EncodeToString(gorootsHash[:])
|
||||||
cachedgorootName := "goroot-" + version + "-" + gorootsHashHex
|
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), "goroot-"+version+"-"+gorootsHashHex)
|
||||||
cachedgoroot := filepath.Join(goenv.Get("GOCACHE"), cachedgorootName)
|
if needsSyscallPackage {
|
||||||
if needsSyscallPackage(config.BuildTags()) {
|
|
||||||
cachedgoroot += "-syscall"
|
cachedgoroot += "-syscall"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not try to create the cached GOROOT in parallel, that's only a waste
|
|
||||||
// of I/O bandwidth and thus speed. Instead, use a mutex to make sure only
|
|
||||||
// one goroutine does it at a time.
|
|
||||||
// This is not a way to ensure atomicity (a different TinyGo invocation
|
|
||||||
// could be creating the same directory), but instead a way to avoid
|
|
||||||
// creating it many times in parallel when running tests in parallel.
|
|
||||||
gorootCreateMutex.Lock()
|
|
||||||
defer gorootCreateMutex.Unlock()
|
|
||||||
|
|
||||||
if _, err := os.Stat(cachedgoroot); err == nil {
|
if _, err := os.Stat(cachedgoroot); err == nil {
|
||||||
return cachedgoroot, nil
|
return cachedgoroot, nil
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(goenv.Get("GOCACHE"), 0777)
|
tmpgoroot := cachedgoroot + ".tmp" + strconv.Itoa(rand.Int())
|
||||||
if err != nil {
|
err = os.MkdirAll(tmpgoroot, 0777)
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
tmpgoroot, err := ioutil.TempDir(goenv.Get("GOCACHE"), cachedgorootName+".tmp")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -88,7 +70,7 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage(config.BuildTags())))
|
err = mergeDirectory(goroot, tinygoroot, tmpgoroot, "", pathsToOverride(needsSyscallPackage))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -100,15 +82,6 @@ func GetCachedGoroot(config *compileopts.Config) (string, error) {
|
|||||||
// deleted by the defer above.
|
// deleted by the defer above.
|
||||||
return cachedgoroot, nil
|
return cachedgoroot, nil
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "windows" && os.IsPermission(err) {
|
|
||||||
// On Windows, a rename with a destination directory that already
|
|
||||||
// exists does not result in an IsExist error, but rather in an
|
|
||||||
// access denied error. To be sure, check for this case by checking
|
|
||||||
// whether the target directory exists.
|
|
||||||
if _, err := os.Stat(cachedgoroot); err == nil {
|
|
||||||
return cachedgoroot, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return cachedgoroot, nil
|
return cachedgoroot, nil
|
||||||
@@ -185,17 +158,6 @@ func mergeDirectory(goroot, tinygoroot, tmpgoroot, importPath string, overrides
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// needsSyscallPackage returns whether the syscall package should be overriden
|
|
||||||
// with the TinyGo version. This is the case on some targets.
|
|
||||||
func needsSyscallPackage(buildTags []string) bool {
|
|
||||||
for _, tag := range buildTags {
|
|
||||||
if tag == "baremetal" || tag == "darwin" || tag == "nintendoswitch" || tag == "wasi" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// The boolean indicates whether to merge the subdirs. True means merge, false
|
// The boolean indicates whether to merge the subdirs. True means merge, false
|
||||||
// means use the TinyGo version.
|
// means use the TinyGo version.
|
||||||
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
||||||
@@ -204,7 +166,6 @@ func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
|||||||
"device/": false,
|
"device/": false,
|
||||||
"examples/": false,
|
"examples/": false,
|
||||||
"internal/": true,
|
"internal/": true,
|
||||||
"internal/bytealg/": false,
|
|
||||||
"internal/reflectlite/": false,
|
"internal/reflectlite/": false,
|
||||||
"internal/task/": false,
|
"internal/task/": false,
|
||||||
"machine/": false,
|
"machine/": false,
|
||||||
@@ -212,7 +173,7 @@ func pathsToOverride(needsSyscallPackage bool) map[string]bool {
|
|||||||
"reflect/": false,
|
"reflect/": false,
|
||||||
"runtime/": false,
|
"runtime/": false,
|
||||||
"sync/": true,
|
"sync/": true,
|
||||||
"testing/": true,
|
"testing/": false,
|
||||||
}
|
}
|
||||||
if needsSyscallPackage {
|
if needsSyscallPackage {
|
||||||
paths["syscall/"] = true // include syscall/js
|
paths["syscall/"] = true // include syscall/js
|
||||||
@@ -252,27 +213,10 @@ func symlink(oldname, newname string) error {
|
|||||||
return symlinkErr
|
return symlinkErr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Try making a hard link.
|
// Make a hard link.
|
||||||
err := os.Link(oldname, newname)
|
err := os.Link(oldname, newname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Making a hardlink failed. Try copying the file as a last
|
return symlinkErr
|
||||||
// fallback.
|
|
||||||
inf, err := os.Open(oldname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer inf.Close()
|
|
||||||
outf, err := os.Create(newname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outf.Close()
|
|
||||||
_, err = io.Copy(outf, inf)
|
|
||||||
if err != nil {
|
|
||||||
os.Remove(newname)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// File was copied.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil // success
|
return nil // success
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package loader
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
)
|
|
||||||
|
|
||||||
// List returns a ready-to-run *exec.Cmd for running the `go list` command with
|
|
||||||
// the configuration used for TinyGo.
|
|
||||||
func List(config *compileopts.Config, extraArgs, pkgs []string) (*exec.Cmd, error) {
|
|
||||||
goroot, err := GetCachedGoroot(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args := append([]string{"list"}, extraArgs...)
|
|
||||||
if len(config.BuildTags()) != 0 {
|
|
||||||
args = append(args, "-tags", strings.Join(config.BuildTags(), " "))
|
|
||||||
}
|
|
||||||
args = append(args, pkgs...)
|
|
||||||
cgoEnabled := "0"
|
|
||||||
if config.CgoEnabled() {
|
|
||||||
cgoEnabled = "1"
|
|
||||||
}
|
|
||||||
cmd := exec.Command("go", args...)
|
|
||||||
cmd.Env = append(os.Environ(), "GOROOT="+goroot, "GOOS="+config.GOOS(), "GOARCH="+config.GOARCH(), "CGO_ENABLED="+cgoEnabled)
|
|
||||||
return cmd, nil
|
|
||||||
}
|
|
||||||
+249
-262
@@ -2,244 +2,159 @@ package loader
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"text/template"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/cgo"
|
"github.com/tinygo-org/tinygo/cgo"
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program holds all packages and some metadata about the program as a whole.
|
// Program holds all packages and some metadata about the program as a whole.
|
||||||
type Program struct {
|
type Program struct {
|
||||||
config *compileopts.Config
|
Build *build.Context
|
||||||
clangHeaders string
|
Tests bool
|
||||||
typeChecker types.Config
|
Packages map[string]*Package
|
||||||
goroot string // synthetic GOROOT
|
MainPkg *Package
|
||||||
workingDir string
|
sorted []*Package
|
||||||
|
fset *token.FileSet
|
||||||
Packages map[string]*Package
|
TypeChecker types.Config
|
||||||
sorted []*Package
|
Dir string // current working directory (for error reporting)
|
||||||
fset *token.FileSet
|
TINYGOROOT string // root of the TinyGo installation or root of the source code
|
||||||
|
CFlags []string
|
||||||
// Information obtained during parsing.
|
ClangHeaders string
|
||||||
LDFlags []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PackageJSON is a subset of the JSON struct returned from `go list`.
|
|
||||||
type PackageJSON struct {
|
|
||||||
Dir string
|
|
||||||
ImportPath string
|
|
||||||
Name string
|
|
||||||
ForTest string
|
|
||||||
|
|
||||||
// Source files
|
|
||||||
GoFiles []string
|
|
||||||
CgoFiles []string
|
|
||||||
CFiles []string
|
|
||||||
|
|
||||||
// Dependency information
|
|
||||||
Imports []string
|
|
||||||
ImportMap map[string]string
|
|
||||||
|
|
||||||
// Error information
|
|
||||||
Error *struct {
|
|
||||||
ImportStack []string
|
|
||||||
Pos string
|
|
||||||
Err string
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package holds a loaded package, its imports, and its parsed files.
|
// Package holds a loaded package, its imports, and its parsed files.
|
||||||
type Package struct {
|
type Package struct {
|
||||||
PackageJSON
|
*Program
|
||||||
|
*packages.Package
|
||||||
program *Program
|
Files []*ast.File
|
||||||
Files []*ast.File
|
Pkg *types.Package
|
||||||
Pkg *types.Package
|
types.Info
|
||||||
info types.Info
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads the given package with all dependencies (including the runtime
|
// Load loads the given package with all dependencies (including the runtime
|
||||||
// package). Call .Parse() afterwards to parse all Go files (including CGo
|
// package). Call .Parse() afterwards to parse all Go files (including CGo
|
||||||
// processing, if necessary).
|
// processing, if necessary).
|
||||||
func Load(config *compileopts.Config, inputPkgs []string, clangHeaders string, typeChecker types.Config) (*Program, error) {
|
func (p *Program) Load(importPath string) error {
|
||||||
goroot, err := GetCachedGoroot(config)
|
if p.Packages == nil {
|
||||||
if err != nil {
|
p.Packages = make(map[string]*Package)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
wd, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := &Program{
|
|
||||||
config: config,
|
|
||||||
clangHeaders: clangHeaders,
|
|
||||||
typeChecker: typeChecker,
|
|
||||||
goroot: goroot,
|
|
||||||
workingDir: wd,
|
|
||||||
Packages: make(map[string]*Package),
|
|
||||||
fset: token.NewFileSet(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// List the dependencies of this package, in raw JSON format.
|
err := p.loadPackage(importPath)
|
||||||
extraArgs := []string{"-json", "-deps"}
|
|
||||||
if config.TestConfig.CompileTestBinary {
|
|
||||||
extraArgs = append(extraArgs, "-test")
|
|
||||||
}
|
|
||||||
cmd, err := List(config, extraArgs, inputPkgs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
buf := &bytes.Buffer{}
|
p.MainPkg = p.sorted[len(p.sorted)-1]
|
||||||
cmd.Stdout = buf
|
if _, ok := p.Packages["runtime"]; !ok {
|
||||||
cmd.Stderr = os.Stderr
|
// The runtime package wasn't loaded. Although `go list -deps` seems to
|
||||||
err = cmd.Run()
|
// return the full dependency list, there is no way to get those
|
||||||
if err != nil {
|
// packages from the go/packages package. Therefore load the runtime
|
||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
// manually and add it to the list of to-be-compiled packages
|
||||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
// (duplicates are already filtered).
|
||||||
os.Exit(status.ExitStatus())
|
return p.loadPackage("runtime")
|
||||||
}
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("failed to run `go list`: %s", err)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
// Parse the returned json from `go list`.
|
|
||||||
decoder := json.NewDecoder(buf)
|
|
||||||
for {
|
|
||||||
pkg := &Package{
|
|
||||||
program: p,
|
|
||||||
info: types.Info{
|
|
||||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
|
||||||
Defs: make(map[*ast.Ident]types.Object),
|
|
||||||
Uses: make(map[*ast.Ident]types.Object),
|
|
||||||
Implicits: make(map[ast.Node]types.Object),
|
|
||||||
Scopes: make(map[ast.Node]*types.Scope),
|
|
||||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := decoder.Decode(&pkg.PackageJSON)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if pkg.Error != nil {
|
|
||||||
// There was an error while importing (for example, a circular
|
|
||||||
// dependency).
|
|
||||||
pos := token.Position{}
|
|
||||||
fields := strings.Split(pkg.Error.Pos, ":")
|
|
||||||
if len(fields) >= 2 {
|
|
||||||
// There is some file/line/column information.
|
|
||||||
if n, err := strconv.Atoi(fields[len(fields)-2]); err == nil {
|
|
||||||
// Format: filename.go:line:colum
|
|
||||||
pos.Filename = strings.Join(fields[:len(fields)-2], ":")
|
|
||||||
pos.Line = n
|
|
||||||
pos.Column, _ = strconv.Atoi(fields[len(fields)-1])
|
|
||||||
} else {
|
|
||||||
// Format: filename.go:line
|
|
||||||
pos.Filename = strings.Join(fields[:len(fields)-1], ":")
|
|
||||||
pos.Line, _ = strconv.Atoi(fields[len(fields)-1])
|
|
||||||
}
|
|
||||||
pos.Filename = p.getOriginalPath(pos.Filename)
|
|
||||||
}
|
|
||||||
err := scanner.Error{
|
|
||||||
Pos: pos,
|
|
||||||
Msg: pkg.Error.Err,
|
|
||||||
}
|
|
||||||
if len(pkg.Error.ImportStack) != 0 {
|
|
||||||
return nil, Error{
|
|
||||||
ImportStack: pkg.Error.ImportStack,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if config.TestConfig.CompileTestBinary {
|
|
||||||
// When creating a test binary, `go list` will list two or three
|
|
||||||
// packages used for testing the package. The first is the original
|
|
||||||
// package as if it were built normally, the second is the same
|
|
||||||
// package but with the *_test.go files included. A possible third
|
|
||||||
// may be included for _test packages (such as math_test), used to
|
|
||||||
// test the external API with no access to internal functions.
|
|
||||||
// All packages that are necessary for testing (including the to be
|
|
||||||
// tested package with *_test.go files, but excluding the original
|
|
||||||
// unmodified package) have a suffix added to the import path, for
|
|
||||||
// example the math package has import path "math [math.test]" and
|
|
||||||
// test dependencies such as fmt will have an import path of the
|
|
||||||
// form "fmt [math.test]".
|
|
||||||
// The code below removes this suffix, and if this results in a
|
|
||||||
// duplicate (which happens with the to-be-tested package without
|
|
||||||
// *.test.go files) the previous package is removed from the list of
|
|
||||||
// packages included in this build.
|
|
||||||
// This is necessary because the change in import paths results in
|
|
||||||
// breakage to //go:linkname. Additionally, the duplicated package
|
|
||||||
// slows down the build and so is best removed.
|
|
||||||
if pkg.ForTest != "" && strings.HasSuffix(pkg.ImportPath, " ["+pkg.ForTest+".test]") {
|
|
||||||
newImportPath := pkg.ImportPath[:len(pkg.ImportPath)-len(" ["+pkg.ForTest+".test]")]
|
|
||||||
if _, ok := p.Packages[newImportPath]; ok {
|
|
||||||
// Delete the previous package (that this package overrides).
|
|
||||||
delete(p.Packages, newImportPath)
|
|
||||||
for i, pkg := range p.sorted {
|
|
||||||
if pkg.ImportPath == newImportPath {
|
|
||||||
p.sorted = append(p.sorted[:i], p.sorted[i+1:]...) // remove element from slice
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pkg.ImportPath = newImportPath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.sorted = append(p.sorted, pkg)
|
|
||||||
p.Packages[pkg.ImportPath] = pkg
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOriginalPath looks whether this path is in the generated GOROOT and if so,
|
func (p *Program) loadPackage(importPath string) error {
|
||||||
// replaces the path with the original path (in GOROOT or TINYGOROOT). Otherwise
|
cgoEnabled := "0"
|
||||||
// the input path is returned.
|
if p.Build.CgoEnabled {
|
||||||
func (p *Program) getOriginalPath(path string) string {
|
cgoEnabled = "1"
|
||||||
originalPath := path
|
}
|
||||||
if strings.HasPrefix(path, p.goroot+string(filepath.Separator)) {
|
pkgs, err := packages.Load(&packages.Config{
|
||||||
// If this file is part of the synthetic GOROOT, try to infer the
|
Mode: packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps,
|
||||||
// original path.
|
Env: append(os.Environ(), "GOROOT="+p.Build.GOROOT, "GOOS="+p.Build.GOOS, "GOARCH="+p.Build.GOARCH, "CGO_ENABLED="+cgoEnabled),
|
||||||
relpath := path[len(filepath.Join(p.goroot, "src"))+1:]
|
BuildFlags: []string{"-tags", strings.Join(p.Build.BuildTags, " ")},
|
||||||
realgorootPath := filepath.Join(goenv.Get("GOROOT"), "src", relpath)
|
Tests: p.Tests,
|
||||||
if _, err := os.Stat(realgorootPath); err == nil {
|
}, importPath)
|
||||||
originalPath = realgorootPath
|
if err != nil {
|
||||||
}
|
return err
|
||||||
maybeInTinyGoRoot := false
|
}
|
||||||
for prefix := range pathsToOverride(needsSyscallPackage(p.config.BuildTags())) {
|
var pkg *packages.Package
|
||||||
if !strings.HasPrefix(relpath, prefix) {
|
if p.Tests {
|
||||||
continue
|
// We need the second package. Quoting from the docs:
|
||||||
}
|
// > For example, when using the go command, loading "fmt" with Tests=true
|
||||||
maybeInTinyGoRoot = true
|
// > returns four packages, with IDs "fmt" (the standard package),
|
||||||
}
|
// > "fmt [fmt.test]" (the package as compiled for the test),
|
||||||
if maybeInTinyGoRoot {
|
// > "fmt_test" (the test functions from source files in package fmt_test),
|
||||||
tinygoPath := filepath.Join(goenv.Get("TINYGOROOT"), "src", relpath)
|
// > and "fmt.test" (the test binary).
|
||||||
if _, err := os.Stat(tinygoPath); err == nil {
|
pkg = pkgs[1]
|
||||||
originalPath = tinygoPath
|
} else {
|
||||||
}
|
if len(pkgs) != 1 {
|
||||||
}
|
return fmt.Errorf("expected exactly one package while importing %s, got %d", importPath, len(pkgs))
|
||||||
|
}
|
||||||
|
pkg = pkgs[0]
|
||||||
|
}
|
||||||
|
var importError *Errors
|
||||||
|
var addPackages func(pkg *packages.Package)
|
||||||
|
addPackages = func(pkg *packages.Package) {
|
||||||
|
if _, ok := p.Packages[pkg.PkgPath]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pkg2 := p.newPackage(pkg)
|
||||||
|
p.Packages[pkg.PkgPath] = pkg2
|
||||||
|
if len(pkg.Errors) != 0 {
|
||||||
|
if importError != nil {
|
||||||
|
// There was another error reported already. Do not report
|
||||||
|
// errors from multiple packages at once.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
importError = &Errors{
|
||||||
|
Pkg: pkg2,
|
||||||
|
}
|
||||||
|
for _, err := range pkg.Errors {
|
||||||
|
importError.Errs = append(importError.Errs, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list of imports (sorted alphabetically).
|
||||||
|
names := make([]string, 0, len(pkg.Imports))
|
||||||
|
for name := range pkg.Imports {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
// Add all the imports.
|
||||||
|
for _, name := range names {
|
||||||
|
addPackages(pkg.Imports[name])
|
||||||
|
}
|
||||||
|
|
||||||
|
p.sorted = append(p.sorted, pkg2)
|
||||||
|
}
|
||||||
|
addPackages(pkg)
|
||||||
|
if importError != nil {
|
||||||
|
return importError
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPackage instantiates a new *Package object with initialized members.
|
||||||
|
func (p *Program) newPackage(pkg *packages.Package) *Package {
|
||||||
|
return &Package{
|
||||||
|
Program: p,
|
||||||
|
Package: pkg,
|
||||||
|
Info: types.Info{
|
||||||
|
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||||
|
Defs: make(map[*ast.Ident]types.Object),
|
||||||
|
Uses: make(map[*ast.Ident]types.Object),
|
||||||
|
Implicits: make(map[ast.Node]types.Object),
|
||||||
|
Scopes: make(map[ast.Node]*types.Scope),
|
||||||
|
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return originalPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sorted returns a list of all packages, sorted in a way that no packages come
|
// Sorted returns a list of all packages, sorted in a way that no packages come
|
||||||
@@ -248,12 +163,6 @@ func (p *Program) Sorted() []*Package {
|
|||||||
return p.sorted
|
return p.sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
// MainPkg returns the last package in the Sorted() slice. This is the main
|
|
||||||
// package of the program.
|
|
||||||
func (p *Program) MainPkg() *Package {
|
|
||||||
return p.sorted[len(p.sorted)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses all packages and typechecks them.
|
// Parse parses all packages and typechecks them.
|
||||||
//
|
//
|
||||||
// The returned error may be an Errors error, which contains a list of errors.
|
// The returned error may be an Errors error, which contains a list of errors.
|
||||||
@@ -261,16 +170,22 @@ func (p *Program) MainPkg() *Package {
|
|||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Program) Parse() error {
|
func (p *Program) Parse() error {
|
||||||
// Parse all packages.
|
// Parse all packages.
|
||||||
// TODO: do this in parallel.
|
for _, pkg := range p.Sorted() {
|
||||||
for _, pkg := range p.sorted {
|
|
||||||
err := pkg.Parse()
|
err := pkg.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.Tests {
|
||||||
|
err := p.swapTestMain()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Typecheck all packages.
|
// Typecheck all packages.
|
||||||
for _, pkg := range p.sorted {
|
for _, pkg := range p.Sorted() {
|
||||||
err := pkg.Check()
|
err := pkg.Check()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -280,6 +195,82 @@ func (p *Program) Parse() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Program) swapTestMain() error {
|
||||||
|
var tests []string
|
||||||
|
|
||||||
|
isTestFunc := func(f *ast.FuncDecl) bool {
|
||||||
|
// TODO: improve signature check
|
||||||
|
if strings.HasPrefix(f.Name.Name, "Test") && f.Name.Name != "TestMain" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, f := range p.MainPkg.Files {
|
||||||
|
for i, d := range f.Decls {
|
||||||
|
switch v := d.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if isTestFunc(v) {
|
||||||
|
tests = append(tests, v.Name.Name)
|
||||||
|
}
|
||||||
|
if v.Name.Name == "main" {
|
||||||
|
// Remove main
|
||||||
|
if len(f.Decls) == 1 {
|
||||||
|
f.Decls = make([]ast.Decl, 0)
|
||||||
|
} else {
|
||||||
|
f.Decls[i] = f.Decls[len(f.Decls)-1]
|
||||||
|
f.Decls = f.Decls[:len(f.Decls)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Check if they defined a TestMain and call it instead of testing.TestMain
|
||||||
|
const mainBody = `package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main () {
|
||||||
|
m := &testing.M{
|
||||||
|
Tests: []testing.TestToCall{
|
||||||
|
{{range .TestFunctions}}
|
||||||
|
{Name: "{{.}}", Func: {{.}}},
|
||||||
|
{{end}}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testing.TestMain(m)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
tmpl := template.Must(template.New("testmain").Parse(mainBody))
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
tmplData := struct {
|
||||||
|
TestFunctions []string
|
||||||
|
}{
|
||||||
|
TestFunctions: tests,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := tmpl.Execute(&b, tmplData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
path := filepath.Join(p.MainPkg.Dir, "$testmain.go")
|
||||||
|
|
||||||
|
if p.fset == nil {
|
||||||
|
p.fset = token.NewFileSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
newMain, err := parser.ParseFile(p.fset, path, b.Bytes(), parser.AllErrors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.MainPkg.Files = append(p.MainPkg.Files, newMain)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseFile is a wrapper around parser.ParseFile.
|
// parseFile is a wrapper around parser.ParseFile.
|
||||||
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
||||||
if p.fset == nil {
|
if p.fset == nil {
|
||||||
@@ -291,7 +282,21 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rd.Close()
|
defer rd.Close()
|
||||||
return parser.ParseFile(p.fset, p.getOriginalPath(path), rd, mode)
|
diagnosticPath := path
|
||||||
|
if strings.HasPrefix(path, p.Build.GOROOT+string(filepath.Separator)) {
|
||||||
|
// If this file is part of the synthetic GOROOT, try to infer the
|
||||||
|
// original path.
|
||||||
|
relpath := path[len(p.Build.GOROOT)+1:]
|
||||||
|
tinygoPath := filepath.Join(p.TINYGOROOT, relpath)
|
||||||
|
if _, err := os.Stat(tinygoPath); err == nil {
|
||||||
|
diagnosticPath = tinygoPath
|
||||||
|
}
|
||||||
|
realgorootPath := filepath.Join(goenv.Get("GOROOT"), relpath)
|
||||||
|
if _, err := os.Stat(realgorootPath); err == nil {
|
||||||
|
diagnosticPath = realgorootPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parser.ParseFile(p.fset, diagnosticPath, rd, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses and typechecks this package.
|
// Parse parses and typechecks this package.
|
||||||
@@ -299,13 +304,14 @@ func (p *Program) parseFile(path string, mode parser.Mode) (*ast.File, error) {
|
|||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Package) Parse() error {
|
func (p *Package) Parse() error {
|
||||||
if len(p.Files) != 0 {
|
if len(p.Files) != 0 {
|
||||||
return nil // nothing to do (?)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the AST.
|
// Load the AST.
|
||||||
if p.ImportPath == "unsafe" {
|
// TODO: do this in parallel.
|
||||||
// Special case for the unsafe package, which is defined internally by
|
if p.PkgPath == "unsafe" {
|
||||||
// the types package.
|
// Special case for the unsafe package. Don't even bother loading
|
||||||
|
// the files.
|
||||||
p.Pkg = types.Unsafe
|
p.Pkg = types.Unsafe
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -325,11 +331,11 @@ func (p *Package) Parse() error {
|
|||||||
// Idempotent.
|
// Idempotent.
|
||||||
func (p *Package) Check() error {
|
func (p *Package) Check() error {
|
||||||
if p.Pkg != nil {
|
if p.Pkg != nil {
|
||||||
return nil // already typechecked
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeErrors []error
|
var typeErrors []error
|
||||||
checker := p.program.typeChecker // make a copy, because it will be modified
|
checker := p.TypeChecker
|
||||||
checker.Error = func(err error) {
|
checker.Error = func(err error) {
|
||||||
typeErrors = append(typeErrors, err)
|
typeErrors = append(typeErrors, err)
|
||||||
}
|
}
|
||||||
@@ -337,16 +343,7 @@ func (p *Package) Check() error {
|
|||||||
// Do typechecking of the package.
|
// Do typechecking of the package.
|
||||||
checker.Importer = p
|
checker.Importer = p
|
||||||
|
|
||||||
packageName := p.ImportPath
|
typesPkg, err := checker.Check(p.PkgPath, p.fset, p.Files, &p.Info)
|
||||||
if p.Name == "main" {
|
|
||||||
// The main package normally has a different import path, such as
|
|
||||||
// "command-line-arguments" or "./testdata/cgo". Therefore, use the name
|
|
||||||
// "main" in such a case: this package isn't imported from anywhere.
|
|
||||||
// This is safe as it isn't possible to import a package with the name
|
|
||||||
// "main".
|
|
||||||
packageName = "main"
|
|
||||||
}
|
|
||||||
typesPkg, err := checker.Check(packageName, p.program.fset, p.Files, &p.info)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err, ok := err.(Errors); ok {
|
if err, ok := err.(Errors); ok {
|
||||||
return err
|
return err
|
||||||
@@ -359,46 +356,39 @@ func (p *Package) Check() error {
|
|||||||
|
|
||||||
// parseFiles parses the loaded list of files and returns this list.
|
// parseFiles parses the loaded list of files and returns this list.
|
||||||
func (p *Package) parseFiles() ([]*ast.File, error) {
|
func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||||
|
// TODO: do this concurrently.
|
||||||
var files []*ast.File
|
var files []*ast.File
|
||||||
var fileErrs []error
|
var fileErrs []error
|
||||||
|
|
||||||
// Parse all files (incuding CgoFiles).
|
var cgoFiles []*ast.File
|
||||||
parseFile := func(file string) {
|
for _, file := range p.GoFiles {
|
||||||
if !filepath.IsAbs(file) {
|
f, err := p.parseFile(file, parser.ParseComments)
|
||||||
file = filepath.Join(p.Dir, file)
|
|
||||||
}
|
|
||||||
f, err := p.program.parseFile(file, parser.ParseComments)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fileErrs = append(fileErrs, err)
|
fileErrs = append(fileErrs, err)
|
||||||
return
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, importSpec := range f.Imports {
|
||||||
|
if importSpec.Path.Value == `"C"` {
|
||||||
|
cgoFiles = append(cgoFiles, f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
files = append(files, f)
|
files = append(files, f)
|
||||||
}
|
}
|
||||||
for _, file := range p.GoFiles {
|
if len(cgoFiles) != 0 {
|
||||||
parseFile(file)
|
cflags := append(p.CFlags, "-I"+filepath.Dir(p.GoFiles[0]))
|
||||||
}
|
if p.ClangHeaders != "" {
|
||||||
for _, file := range p.CgoFiles {
|
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.ClangHeaders)
|
||||||
parseFile(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do CGo processing.
|
|
||||||
if len(p.CgoFiles) != 0 {
|
|
||||||
var cflags []string
|
|
||||||
cflags = append(cflags, p.program.config.CFlags()...)
|
|
||||||
cflags = append(cflags, "-I"+p.Dir)
|
|
||||||
if p.program.clangHeaders != "" {
|
|
||||||
cflags = append(cflags, "-Xclang", "-internal-isystem", "-Xclang", p.program.clangHeaders)
|
|
||||||
}
|
}
|
||||||
generated, ldflags, errs := cgo.Process(files, p.program.workingDir, p.program.fset, cflags)
|
generated, errs := cgo.Process(files, p.Program.Dir, p.fset, cflags)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
fileErrs = append(fileErrs, errs...)
|
fileErrs = append(fileErrs, errs...)
|
||||||
}
|
}
|
||||||
files = append(files, generated)
|
files = append(files, generated)
|
||||||
p.program.LDFlags = append(p.program.LDFlags, ldflags...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only return an error after CGo processing, so that errors in parsing and
|
|
||||||
// CGo can be reported together.
|
|
||||||
if len(fileErrs) != 0 {
|
if len(fileErrs) != 0 {
|
||||||
return nil, Errors{p, fileErrs}
|
return nil, Errors{p, fileErrs}
|
||||||
}
|
}
|
||||||
@@ -412,11 +402,8 @@ func (p *Package) Import(to string) (*types.Package, error) {
|
|||||||
if to == "unsafe" {
|
if to == "unsafe" {
|
||||||
return types.Unsafe, nil
|
return types.Unsafe, nil
|
||||||
}
|
}
|
||||||
if newTo, ok := p.ImportMap[to]; ok && !strings.HasSuffix(newTo, ".test]") {
|
if _, ok := p.Imports[to]; ok {
|
||||||
to = newTo
|
return p.Packages[p.Imports[to].PkgPath].Pkg, nil
|
||||||
}
|
|
||||||
if imported, ok := p.program.Packages[to]; ok {
|
|
||||||
return imported.Pkg, nil
|
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("package not imported: " + to)
|
return nil, errors.New("package not imported: " + to)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-10
@@ -10,17 +10,9 @@ import (
|
|||||||
func (p *Program) LoadSSA() *ssa.Program {
|
func (p *Program) LoadSSA() *ssa.Program {
|
||||||
prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
|
prog := ssa.NewProgram(p.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
|
||||||
|
|
||||||
for _, pkg := range p.sorted {
|
for _, pkg := range p.Sorted() {
|
||||||
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.info, true)
|
prog.CreatePackage(pkg.Pkg, pkg.Files, &pkg.Info, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return prog
|
return prog
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadSSA constructs the SSA form of this package.
|
|
||||||
//
|
|
||||||
// The program must already be parsed and type-checked with the .Parse() method.
|
|
||||||
func (p *Package) LoadSSA() *ssa.Package {
|
|
||||||
prog := ssa.NewProgram(p.program.fset, ssa.SanityCheckFunctions|ssa.BareInits|ssa.GlobalDebug)
|
|
||||||
return prog.CreatePackage(p.Pkg, p.Files, &p.info, true)
|
|
||||||
}
|
|
||||||
|
|||||||
+8
-39
@@ -6,7 +6,6 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@@ -26,8 +25,6 @@ import (
|
|||||||
|
|
||||||
const TESTDATA = "testdata"
|
const TESTDATA = "testdata"
|
||||||
|
|
||||||
var testTarget = flag.String("target", "", "override test target")
|
|
||||||
|
|
||||||
func TestCompiler(t *testing.T) {
|
func TestCompiler(t *testing.T) {
|
||||||
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -47,20 +44,9 @@ func TestCompiler(t *testing.T) {
|
|||||||
|
|
||||||
sort.Strings(matches)
|
sort.Strings(matches)
|
||||||
|
|
||||||
if *testTarget != "" {
|
|
||||||
// This makes it possible to run one specific test (instead of all),
|
|
||||||
// which is especially useful to quickly check whether some changes
|
|
||||||
// affect a particular target architecture.
|
|
||||||
runPlatTests(*testTarget, matches, t)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if runtime.GOOS != "windows" {
|
if runtime.GOOS != "windows" {
|
||||||
t.Run("Host", func(t *testing.T) {
|
t.Run("Host", func(t *testing.T) {
|
||||||
runPlatTests("", matches, t)
|
runPlatTests("", matches, t)
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
runTest("testdata/libc/env.go", "", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +67,6 @@ func TestCompiler(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
t.Run("X86Linux", func(t *testing.T) {
|
|
||||||
runPlatTests("i386--linux-gnu", matches, t)
|
|
||||||
})
|
|
||||||
t.Run("ARMLinux", func(t *testing.T) {
|
t.Run("ARMLinux", func(t *testing.T) {
|
||||||
runPlatTests("arm--linux-gnueabihf", matches, t)
|
runPlatTests("arm--linux-gnueabihf", matches, t)
|
||||||
})
|
})
|
||||||
@@ -104,11 +87,6 @@ func TestCompiler(t *testing.T) {
|
|||||||
runPlatTests("wasm", matches, t)
|
runPlatTests("wasm", matches, t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("WASI", func(t *testing.T) {
|
|
||||||
runPlatTests("wasi", matches, t)
|
|
||||||
runTest("testdata/libc/env.go", "wasi", t, []string{"ENV1=VALUE1", "ENV2=VALUE2"}...)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,8 +95,10 @@ func runPlatTests(target string, matches []string, t *testing.T) {
|
|||||||
|
|
||||||
for _, path := range matches {
|
for _, path := range matches {
|
||||||
path := path // redefine to avoid race condition
|
path := path // redefine to avoid race condition
|
||||||
|
|
||||||
t.Run(filepath.Base(path), func(t *testing.T) {
|
t.Run(filepath.Base(path), func(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
runTest(path, target, t)
|
runTest(path, target, t)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -136,7 +116,7 @@ func runBuild(src, out string, opts *compileopts.Options) error {
|
|||||||
return Build(src, out, opts)
|
return Build(src, out, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runTest(path, target string, t *testing.T, environmentVars ...string) {
|
func runTest(path, target string, t *testing.T) {
|
||||||
// Get the expected output for this test.
|
// Get the expected output for this test.
|
||||||
txtpath := path[:len(path)-3] + ".txt"
|
txtpath := path[:len(path)-3] + ".txt"
|
||||||
if path[len(path)-1] == os.PathSeparator {
|
if path[len(path)-1] == os.PathSeparator {
|
||||||
@@ -166,11 +146,10 @@ func runTest(path, target string, t *testing.T, environmentVars ...string) {
|
|||||||
PrintIR: false,
|
PrintIR: false,
|
||||||
DumpSSA: false,
|
DumpSSA: false,
|
||||||
VerifyIR: true,
|
VerifyIR: true,
|
||||||
Debug: true,
|
Debug: false,
|
||||||
PrintSizes: "",
|
PrintSizes: "",
|
||||||
WasmAbi: "",
|
WasmAbi: "js",
|
||||||
}
|
}
|
||||||
|
|
||||||
binary := filepath.Join(tmpdir, "test")
|
binary := filepath.Join(tmpdir, "test")
|
||||||
err = runBuild("./"+path, binary, config)
|
err = runBuild("./"+path, binary, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -185,26 +164,16 @@ func runTest(path, target string, t *testing.T, environmentVars ...string) {
|
|||||||
ranTooLong := false
|
ranTooLong := false
|
||||||
if target == "" {
|
if target == "" {
|
||||||
cmd = exec.Command(binary)
|
cmd = exec.Command(binary)
|
||||||
cmd.Env = append(cmd.Env, environmentVars...)
|
|
||||||
} else {
|
} else {
|
||||||
spec, err := compileopts.LoadTarget(target)
|
spec, err := compileopts.LoadTarget(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to load target spec:", err)
|
t.Fatal("failed to load target spec:", err)
|
||||||
}
|
}
|
||||||
if len(spec.Emulator) == 0 {
|
if len(spec.Emulator) == 0 {
|
||||||
cmd = exec.Command(binary)
|
t.Fatal("no emulator available for target:", target)
|
||||||
} else {
|
|
||||||
args := append(spec.Emulator[1:], binary)
|
|
||||||
cmd = exec.Command(spec.Emulator[0], args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(spec.Emulator) != 0 && spec.Emulator[0] == "wasmtime" {
|
|
||||||
for _, v := range environmentVars {
|
|
||||||
cmd.Args = append(cmd.Args, "--env", v)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd.Env = append(cmd.Env, environmentVars...)
|
|
||||||
}
|
}
|
||||||
|
args := append(spec.Emulator[1:], binary)
|
||||||
|
cmd = exec.Command(spec.Emulator[0], args...)
|
||||||
}
|
}
|
||||||
stdout := &bytes.Buffer{}
|
stdout := &bytes.Buffer{}
|
||||||
cmd.Stdout = stdout
|
cmd.Stdout = stdout
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user