mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-18 11:33:59 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71fa131ef9 |
+135
-306
@@ -6,6 +6,31 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Pull submodules"
|
name: "Pull submodules"
|
||||||
command: git submodule update --init
|
command: git submodule update --init
|
||||||
|
apt-dependencies:
|
||||||
|
parameters:
|
||||||
|
llvm:
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install apt dependencies"
|
||||||
|
command: |
|
||||||
|
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install \
|
||||||
|
python3 \
|
||||||
|
llvm<<parameters.llvm>>-dev \
|
||||||
|
clang<<parameters.llvm>> \
|
||||||
|
libclang<<parameters.llvm>>-dev \
|
||||||
|
lld<<parameters.llvm>> \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
libc6-dev-armel-cross \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-user \
|
||||||
|
gcc-avr \
|
||||||
|
avr-libc
|
||||||
install-node:
|
install-node:
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
@@ -15,99 +40,50 @@ 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:
|
dep:
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- run:
|
||||||
name: "Install Chrome"
|
name: "Install Go dependencies"
|
||||||
command: |
|
command: |
|
||||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
sudo apt install ./google-chrome-stable_current_amd64.deb
|
dep ensure --vendor-only
|
||||||
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-cmake:
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: "Install CMake"
|
|
||||||
command: |
|
|
||||||
wget https://github.com/Kitware/CMake/releases/download/v3.21.4/cmake-3.21.4-linux-x86_64.tar.gz
|
|
||||||
sudo tar --strip-components=1 -C /usr/local -xf cmake-3.21.4-linux-x86_64.tar.gz
|
|
||||||
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-v2
|
- llvm-source-8-v2
|
||||||
- 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-v2
|
key: llvm-source-8-v2
|
||||||
paths:
|
paths:
|
||||||
- llvm-project/clang/lib/Headers
|
- llvm
|
||||||
- llvm-project/clang/include
|
smoketest:
|
||||||
- llvm-project/lld/include
|
|
||||||
- llvm-project/llvm/include
|
|
||||||
hack-ninja-jobs:
|
|
||||||
steps:
|
steps:
|
||||||
- run:
|
- smoketest-no-avr
|
||||||
name: "Hack Ninja to use less jobs"
|
- run: tinygo build -size short -o test.elf -target=arduino examples/blinky1
|
||||||
command: |
|
- run: tinygo build -size short -o test.elf -target=digispark examples/blinky1
|
||||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
smoketest-no-avr:
|
||||||
chmod +x /go/bin/ninja
|
|
||||||
build-binaryen-linux:
|
|
||||||
steps:
|
steps:
|
||||||
- restore_cache:
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky1
|
||||||
keys:
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/blinky2
|
||||||
- binaryen-linux-v1
|
- run: tinygo build -o blinky2 examples/blinky2 # TODO: re-enable -size flag with MachO support
|
||||||
- run:
|
- run: tinygo build -size short -o test.elf -target=pca10040 examples/test
|
||||||
name: "Build Binaryen"
|
- run: tinygo build -size short -o test.elf -target=microbit examples/echo
|
||||||
command: |
|
- run: tinygo build -size short -o test.elf -target=nrf52840-mdk examples/blinky1
|
||||||
make binaryen
|
- run: tinygo build -size short -o test.elf -target=pca10031 examples/blinky1
|
||||||
- save_cache:
|
- run: tinygo build -size short -o test.elf -target=bluepill examples/blinky1
|
||||||
key: binaryen-linux-v1
|
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky1
|
||||||
paths:
|
- run: tinygo build -size short -o test.elf -target=reelboard examples/blinky2
|
||||||
- build/wasm-opt
|
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky1
|
||||||
build-binaryen-linux-stretch:
|
- run: tinygo build -size short -o test.elf -target=pca10056 examples/blinky2
|
||||||
steps:
|
- run: tinygo build -size short -o test.elf -target=itsybitsy-m0 examples/blinky1
|
||||||
- restore_cache:
|
- run: tinygo build -size short -o test.elf -target=circuitplay-express examples/blinky1
|
||||||
keys:
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky1
|
||||||
- binaryen-linux-stretch-v1
|
- run: tinygo build -size short -o test.elf -target=stm32f4disco examples/blinky2
|
||||||
- run:
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
name: "Build Binaryen"
|
- run: tinygo build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
command: |
|
|
||||||
CC=$PWD/llvm-build/bin/clang make binaryen
|
|
||||||
- save_cache:
|
|
||||||
key: binaryen-linux-stretch-v1
|
|
||||||
paths:
|
|
||||||
- build/wasm-opt
|
|
||||||
build-wasi-libc:
|
|
||||||
steps:
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- wasi-libc-sysroot-v4
|
|
||||||
- run:
|
|
||||||
name: "Build wasi-libc"
|
|
||||||
command: make wasi-libc
|
|
||||||
- save_cache:
|
|
||||||
key: wasi-libc-sysroot-v4
|
|
||||||
paths:
|
|
||||||
- lib/wasi-libc/sysroot
|
|
||||||
test-linux:
|
test-linux:
|
||||||
parameters:
|
parameters:
|
||||||
llvm:
|
llvm:
|
||||||
@@ -115,117 +91,25 @@ commands:
|
|||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
- submodules
|
- submodules
|
||||||
- run:
|
- apt-dependencies:
|
||||||
name: "Install apt dependencies"
|
llvm: <<parameters.llvm>>
|
||||||
command: |
|
|
||||||
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
|
||||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends \
|
|
||||||
llvm-<<parameters.llvm>>-dev \
|
|
||||||
clang-<<parameters.llvm>> \
|
|
||||||
libclang-<<parameters.llvm>>-dev \
|
|
||||||
lld-<<parameters.llvm>> \
|
|
||||||
gcc-avr \
|
|
||||||
avr-libc \
|
|
||||||
cmake \
|
|
||||||
ninja-build
|
|
||||||
- hack-ninja-jobs
|
|
||||||
- build-binaryen-linux
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
|
||||||
- llvm-source-linux
|
|
||||||
- run: go install -tags=llvm<<parameters.llvm>> .
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- wasi-libc-sysroot-systemclang-v3
|
|
||||||
- run: make wasi-libc
|
|
||||||
- save_cache:
|
|
||||||
key: wasi-libc-sysroot-systemclang-v3
|
|
||||||
paths:
|
|
||||||
- lib/wasi-libc/sysroot
|
|
||||||
- run: make gen-device -j4
|
|
||||||
- run: make smoketest XTENSA=0
|
|
||||||
- save_cache:
|
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/go-build
|
|
||||||
- /go/pkg/mod
|
|
||||||
- run: make fmt-check
|
|
||||||
assert-test-linux:
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
- submodules
|
|
||||||
- run:
|
|
||||||
name: "Install apt dependencies"
|
|
||||||
command: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends \
|
|
||||||
qemu-system-arm \
|
|
||||||
qemu-system-riscv32 \
|
|
||||||
qemu-user \
|
|
||||||
gcc-avr \
|
|
||||||
avr-libc \
|
|
||||||
ninja-build \
|
|
||||||
python3
|
|
||||||
- install-node
|
- install-node
|
||||||
- install-wasmtime
|
|
||||||
- install-cmake
|
|
||||||
- hack-ninja-jobs
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- dep
|
||||||
keys:
|
- run: go install .
|
||||||
- llvm-build-11-linux-v4-assert
|
- run: go test -v
|
||||||
- run:
|
- run: make gen-device -j4
|
||||||
name: "Build LLVM"
|
- smoketest
|
||||||
command: |
|
|
||||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
|
||||||
then
|
|
||||||
# fetch LLVM source (may only have headers right now)
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# build!
|
|
||||||
make ASSERT=1 llvm-build
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
fi
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-linux-v4-assert
|
key: go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
|
||||||
llvm-build
|
|
||||||
- build-binaryen-linux
|
|
||||||
- build-wasi-libc
|
|
||||||
- run:
|
|
||||||
name: "Test TinyGo"
|
|
||||||
command: make ASSERT=1 test
|
|
||||||
no_output_timeout: 20m
|
|
||||||
environment:
|
|
||||||
# Note: -p=2 limits parallelism to two jobs at a time, which is
|
|
||||||
# necessary to keep memory consumption down and avoid OOM (for a
|
|
||||||
# 2CPU/4GB executor).
|
|
||||||
GOFLAGS: -p=2
|
|
||||||
- run:
|
|
||||||
name: "Build TinyGo"
|
|
||||||
command: |
|
|
||||||
make ASSERT=1
|
|
||||||
echo 'export PATH=$(pwd)/build:$PATH' >> $BASH_ENV
|
|
||||||
- run: make tinygo-test
|
|
||||||
- save_cache:
|
|
||||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
- /go/pkg/mod
|
- ~/.cache/tinygo
|
||||||
- install-xtensa-toolchain:
|
- run: make fmt-check
|
||||||
variant: "linux-amd64"
|
|
||||||
- run: make gen-device -j4
|
|
||||||
- run: make smoketest
|
|
||||||
- install-chrome
|
|
||||||
- run: make wasmtest
|
|
||||||
build-linux:
|
build-linux:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -233,86 +117,76 @@ 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 --no-install-recommends \
|
python3 \
|
||||||
libgnutls30 libssl1.0.2 \
|
gcc-arm-linux-gnueabihf \
|
||||||
ninja-build \
|
binutils-arm-none-eabi \
|
||||||
python3
|
libc6-dev-armel-cross \
|
||||||
- install-cmake
|
gcc-aarch64-linux-gnu \
|
||||||
- hack-ninja-jobs
|
libc6-dev-arm64-cross \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-user \
|
||||||
|
gcc-avr \
|
||||||
|
avr-libc
|
||||||
|
- install-node
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- go-cache-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
- go-cache-v2-{{ checksum "go.mod" }}
|
- go-cache-{{ checksum "Gopkg.lock" }}
|
||||||
- llvm-source-linux
|
- llvm-source-linux
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-11-linux-v4-noassert
|
- llvm-build-8-linux-v4
|
||||||
- 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
|
||||||
# fetch LLVM source (may only have headers right now)
|
# install dependencies
|
||||||
rm -rf llvm-project
|
sudo apt-get install cmake clang ninja-build
|
||||||
make llvm-source
|
# make build faster
|
||||||
|
export CC=clang
|
||||||
|
export CXX=clang++
|
||||||
|
# hack ninja to use less jobs
|
||||||
|
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||||
|
chmod +x /go/bin/ninja
|
||||||
# build!
|
# build!
|
||||||
make llvm-build
|
make llvm-build
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-linux-v4-noassert
|
key: llvm-build-8-linux-v4
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- build-binaryen-linux-stretch
|
|
||||||
- build-wasi-libc
|
|
||||||
- run:
|
- run:
|
||||||
name: "Install fpm"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
sudo apt-get install ruby ruby-dev
|
ln -s $PWD/llvm-build/bin/clang-8 /go/bin/clang-8
|
||||||
sudo gem install --no-document fpm
|
ln -s $PWD/llvm-build/bin/ld.lld /go/bin/ld.lld-8
|
||||||
|
ln -s $PWD/llvm-build/bin/wasm-ld /go/bin/wasm-ld-8
|
||||||
|
- dep
|
||||||
|
- run:
|
||||||
|
name: "Test TinyGo"
|
||||||
|
command: make test
|
||||||
- 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
|
|
||||||
- persist_to_workspace:
|
|
||||||
root: /tmp
|
|
||||||
paths:
|
|
||||||
- tinygo.linux-amd64.tar.gz
|
|
||||||
- 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-{{ checksum "Gopkg.lock" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
paths:
|
paths:
|
||||||
- ~/.cache/go-build
|
- ~/.cache/go-build
|
||||||
- /go/pkg/mod
|
- ~/.cache/tinygo
|
||||||
test-linux-build:
|
|
||||||
# Now run the smoke tests for the generated binary.
|
|
||||||
steps:
|
|
||||||
- attach_workspace:
|
|
||||||
at: /tmp/workspace
|
|
||||||
- checkout
|
|
||||||
- run:
|
|
||||||
name: "Install apt dependencies"
|
|
||||||
command: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends \
|
|
||||||
gcc-avr \
|
|
||||||
avr-libc
|
|
||||||
- install-xtensa-toolchain:
|
|
||||||
variant: "linux-amd64"
|
|
||||||
- run:
|
- run:
|
||||||
name: "Extract release tarball"
|
name: "Extract release tarball"
|
||||||
command: |
|
command: |
|
||||||
mkdir -p ~/lib
|
mkdir -p ~/lib
|
||||||
tar -C ~/lib -xf /tmp/workspace/tinygo.linux-amd64.tar.gz
|
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||||
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
|
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||||
tinygo version
|
tinygo version
|
||||||
- run: make smoketest
|
- smoketest
|
||||||
build-macos:
|
build-macos:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- checkout
|
||||||
@@ -320,76 +194,44 @@ commands:
|
|||||||
- run:
|
- run:
|
||||||
name: "Install dependencies"
|
name: "Install dependencies"
|
||||||
command: |
|
command: |
|
||||||
curl https://dl.google.com/go/go1.17.darwin-amd64.tar.gz -o go1.17.darwin-amd64.tar.gz
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install go dep qemu
|
||||||
sudo tar -C /usr/local -xzf go1.17.darwin-amd64.tar.gz
|
|
||||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
|
||||||
- install-xtensa-toolchain:
|
|
||||||
variant: "macos"
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
- llvm-source-8-macos-v2
|
||||||
- go-cache-macos-v3-{{ checksum "go.mod" }}
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- llvm-source-11-macos-v3
|
|
||||||
- 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-v3
|
key: llvm-source-8-macos-v2
|
||||||
paths:
|
paths:
|
||||||
- llvm-project/clang/lib/Headers
|
- llvm
|
||||||
- llvm-project/clang/include
|
|
||||||
- llvm-project/lld/include
|
|
||||||
- llvm-project/llvm/include
|
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
keys:
|
keys:
|
||||||
- llvm-build-11-macos-v5
|
- llvm-build-8-macos-v3
|
||||||
- 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
|
||||||
# fetch LLVM source (may only have headers right now)
|
# install dependencies
|
||||||
rm -rf llvm-project
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||||
make llvm-source
|
|
||||||
# build!
|
# build!
|
||||||
make llvm-build
|
make llvm-build
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
fi
|
fi
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: llvm-build-11-macos-v5
|
key: llvm-build-8-macos-v3
|
||||||
paths:
|
paths:
|
||||||
llvm-build
|
llvm-build
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- binaryen-macos-v1
|
|
||||||
- run:
|
- run:
|
||||||
name: "Build Binaryen"
|
name: "Create LLVM symlinks"
|
||||||
command: |
|
command: |
|
||||||
if [ ! -f build/wasm-opt ]
|
ln -s $PWD/llvm-build/bin/clang-8 /usr/local/bin/clang-8
|
||||||
then
|
|
||||||
make binaryen
|
|
||||||
fi
|
|
||||||
- save_cache:
|
|
||||||
key: binaryen-macos-v1
|
|
||||||
paths:
|
|
||||||
- build/wasm-opt
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- wasi-libc-sysroot-macos-v4
|
|
||||||
- run:
|
- run:
|
||||||
name: "Build wasi-libc"
|
name: "Install Go dependencies"
|
||||||
command: make wasi-libc
|
command: dep ensure --vendor-only
|
||||||
- save_cache:
|
|
||||||
key: wasi-libc-sysroot-macos-v4
|
|
||||||
paths:
|
|
||||||
- lib/wasi-libc/sysroot
|
|
||||||
- run:
|
- run:
|
||||||
name: "Test TinyGo"
|
name: "Test TinyGo"
|
||||||
command: make test GOTESTFLAGS="-v -short"
|
command: make test
|
||||||
no_output_timeout: 20m
|
|
||||||
- run:
|
- run:
|
||||||
name: "Build TinyGo release"
|
name: "Build TinyGo release"
|
||||||
command: |
|
command: |
|
||||||
@@ -404,57 +246,44 @@ 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: make smoketest AVR=0
|
- smoketest-no-avr
|
||||||
- save_cache:
|
|
||||||
key: go-cache-macos-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
|
||||||
paths:
|
|
||||||
- ~/.cache/go-build
|
|
||||||
- /go/pkg/mod
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-llvm11-go115:
|
test-llvm8-go111:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.15-buster
|
- image: circleci/golang:1.11
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- test-linux:
|
- test-linux:
|
||||||
llvm: "11"
|
llvm: "-8"
|
||||||
test-llvm11-go116:
|
test-llvm8-go112:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.16-buster
|
- image: circleci/golang:1.12
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- test-linux:
|
- test-linux:
|
||||||
llvm: "11"
|
llvm: "-8"
|
||||||
assert-test-linux:
|
|
||||||
docker:
|
|
||||||
- image: circleci/golang:1.17-buster
|
|
||||||
steps:
|
|
||||||
- assert-test-linux
|
|
||||||
build-linux:
|
build-linux:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/golang:1.17-stretch
|
- image: circleci/golang:1.12
|
||||||
|
working_directory: /go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- build-linux
|
- build-linux
|
||||||
test-linux-build:
|
|
||||||
docker:
|
|
||||||
- image: cimg/go:1.17
|
|
||||||
steps:
|
|
||||||
- test-linux-build
|
|
||||||
build-macos:
|
build-macos:
|
||||||
macos:
|
macos:
|
||||||
xcode: "11.1.0" # macOS 10.14
|
xcode: "10.1.0"
|
||||||
|
working_directory: ~/go/src/github.com/tinygo-org/tinygo
|
||||||
steps:
|
steps:
|
||||||
- build-macos
|
- build-macos
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
workflows:
|
workflows:
|
||||||
test-all:
|
test-all:
|
||||||
jobs:
|
jobs:
|
||||||
- test-llvm11-go115
|
- test-llvm8-go111
|
||||||
- test-llvm11-go116
|
- test-llvm8-go112
|
||||||
- build-linux
|
|
||||||
- test-linux-build:
|
|
||||||
requires:
|
|
||||||
- build-linux
|
- build-linux
|
||||||
- build-macos
|
- build-macos
|
||||||
- assert-test-linux
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
build/
|
|
||||||
llvm-*/
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
name: CI for tinygo-dev docker container
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ dev, fix-docker-llvm-build ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
push_to_registry:
|
|
||||||
name: Push Docker image to GHCR/Docker Hub
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- name: Docker meta
|
|
||||||
id: meta
|
|
||||||
uses: docker/metadata-action@v3
|
|
||||||
with:
|
|
||||||
images: |
|
|
||||||
tinygo/tinygo-dev
|
|
||||||
ghcr.io/${{ github.repository }}/tinygo-dev
|
|
||||||
tags: |
|
|
||||||
type=sha,format=long
|
|
||||||
type=raw,value=latest
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
- name: Log in to Github Container Registry
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
name: Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows:
|
|
||||||
runs-on: windows-2019
|
|
||||||
steps:
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: '1.17'
|
|
||||||
- name: Install Ninja
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
choco install ninja
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Cache LLVM source
|
|
||||||
uses: actions/cache@v2
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-11-windows-v1
|
|
||||||
path: |
|
|
||||||
llvm-project/clang/lib/Headers
|
|
||||||
llvm-project/clang/include
|
|
||||||
llvm-project/lld/include
|
|
||||||
llvm-project/llvm/include
|
|
||||||
- name: Download LLVM source
|
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
|
||||||
run: make llvm-source
|
|
||||||
- name: Cache LLVM build
|
|
||||||
uses: actions/cache@v2
|
|
||||||
id: cache-llvm-build
|
|
||||||
with:
|
|
||||||
key: llvm-build-11-windows-v2
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# build!
|
|
||||||
make llvm-build
|
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
- name: Cache wasi-libc sysroot
|
|
||||||
uses: actions/cache@v2
|
|
||||||
id: cache-wasi-libc
|
|
||||||
with:
|
|
||||||
key: wasi-libc-sysroot-v1
|
|
||||||
path: lib/wasi-libc/sysroot
|
|
||||||
- name: Build wasi-libc
|
|
||||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
|
||||||
run: make wasi-libc
|
|
||||||
- name: Cache Binaryen
|
|
||||||
uses: actions/cache@v2
|
|
||||||
id: cache-binaryen
|
|
||||||
with:
|
|
||||||
key: binaryen-v1
|
|
||||||
path: build/binaryen
|
|
||||||
- name: Build Binaryen
|
|
||||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
|
||||||
run: make binaryen
|
|
||||||
- name: Test TinyGo
|
|
||||||
shell: bash
|
|
||||||
run: make test GOTESTFLAGS="-v -short"
|
|
||||||
- name: Build TinyGo release tarball
|
|
||||||
run: make build/release -j4
|
|
||||||
- name: Make release artifact
|
|
||||||
shell: bash
|
|
||||||
working-directory: build/release
|
|
||||||
run: 7z -tzip a release.zip tinygo
|
|
||||||
- name: Publish release artifact
|
|
||||||
# Note: this release artifact is double-zipped, see:
|
|
||||||
# https://github.com/actions/upload-artifact/issues/39
|
|
||||||
# We can essentially pick one of these:
|
|
||||||
# - have a dobule-zipped artifact when downloaded from the UI
|
|
||||||
# - have a very slow artifact upload
|
|
||||||
# We're doing the former here, to keep artifact uploads fast.
|
|
||||||
uses: actions/upload-artifact@v2
|
|
||||||
with:
|
|
||||||
name: release-double-zipped
|
|
||||||
path: build/release/release.zip
|
|
||||||
- name: Smoke tests
|
|
||||||
shell: bash
|
|
||||||
run: make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
|
||||||
- name: Test stdlib packages
|
|
||||||
run: make tinygo-test
|
|
||||||
+4
-19
@@ -1,29 +1,14 @@
|
|||||||
|
build
|
||||||
docs/_build
|
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/*.s
|
|
||||||
src/device/sifive/*.go
|
|
||||||
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/sam/*.go
|
||||||
src/device/kendryte/*.s
|
src/device/sam/*.s
|
||||||
src/device/rp/*.go
|
|
||||||
src/device/rp/*.s
|
|
||||||
vendor
|
vendor
|
||||||
|
llvm
|
||||||
llvm-build
|
llvm-build
|
||||||
llvm-project
|
|
||||||
|
|
||||||
# Ignore files generated by smoketest
|
|
||||||
test.gba
|
|
||||||
test.hex
|
|
||||||
test.nro
|
|
||||||
test.wasm
|
|
||||||
wasm.wasm
|
|
||||||
+1
-19
@@ -9,26 +9,8 @@
|
|||||||
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
|
||||||
branch = release_80
|
branch = release_80
|
||||||
[submodule "lib/wasi-libc"]
|
|
||||||
path = lib/wasi-libc
|
|
||||||
url = https://github.com/CraneStation/wasi-libc
|
|
||||||
[submodule "lib/picolibc"]
|
|
||||||
path = lib/picolibc
|
|
||||||
url = https://github.com/keith-packard/picolibc.git
|
|
||||||
[submodule "lib/stm32-svd"]
|
|
||||||
path = lib/stm32-svd
|
|
||||||
url = https://github.com/tinygo-org/stm32-svd
|
|
||||||
[submodule "lib/musl"]
|
|
||||||
path = lib/musl
|
|
||||||
url = git://git.musl-libc.org/musl
|
|
||||||
[submodule "lib/binaryen"]
|
|
||||||
path = lib/binaryen
|
|
||||||
url = https://github.com/WebAssembly/binaryen.git
|
|
||||||
[submodule "lib/mingw-w64"]
|
|
||||||
path = lib/mingw-w64
|
|
||||||
url = https://github.com/mingw-w64/mingw-w64.git
|
|
||||||
|
|||||||
+6
-16
@@ -10,20 +10,13 @@ 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
|
||||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||||
|
|
||||||
* Go (1.15+)
|
* Go (1.11+)
|
||||||
|
* [dep](https://golang.github.io/dep/)
|
||||||
* Standard build tools (gcc/clang)
|
* Standard build tools (gcc/clang)
|
||||||
* git
|
* git
|
||||||
* CMake
|
* CMake
|
||||||
@@ -34,10 +27,11 @@ on a different system like Mac.
|
|||||||
|
|
||||||
## Download the source
|
## Download the source
|
||||||
|
|
||||||
The first step is to download the TinyGo sources (use `--recursive` if you clone
|
The first step is to download the TinyGo sources. Then, inside the directory,
|
||||||
the git repository). Then, inside the directory, download the LLVM source:
|
perform these steps:
|
||||||
|
|
||||||
make llvm-source
|
dep ensure -vendor-only # download Go dependencies
|
||||||
|
make llvm-source # download LLVM
|
||||||
|
|
||||||
You can also store LLVM outside of the TinyGo root directory by setting the
|
You can also store LLVM outside of the TinyGo root directory by setting the
|
||||||
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
|
||||||
@@ -87,10 +81,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):
|
||||||
|
|
||||||
|
|||||||
-1090
File diff suppressed because it is too large
Load Diff
@@ -1,76 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
|
||||||
contributors and maintainers pledge to make participation in our project and
|
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
|
||||||
level of experience, education, socio-economic status, nationality, personal
|
|
||||||
appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment
|
|
||||||
include:
|
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
||||||
advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic
|
|
||||||
address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable
|
|
||||||
behavior and are expected to take appropriate and fair corrective action in
|
|
||||||
response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
||||||
permanently any contributor for other behaviors that they deem inappropriate,
|
|
||||||
threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all project spaces, and it also applies when
|
|
||||||
an individual is representing the project or its community in public spaces.
|
|
||||||
Examples of representing a project or community include using an official
|
|
||||||
project e-mail address, posting via an official social media account, or acting
|
|
||||||
as an appointed representative at an online or offline event. Representation of
|
|
||||||
a project may be further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported by contacting the project team at [conduct@tinygo.org](mailto:conduct@tinygo.org). All
|
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
||||||
Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
|
||||||
members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see
|
|
||||||
https://www.contributor-covenant.org/faq
|
|
||||||
+1
-5
@@ -16,23 +16,19 @@ Please open a Github issue with your problem, and we will be happy to assist.
|
|||||||
|
|
||||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
|
||||||
|
|
||||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||||
|
|
||||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||||
|
|
||||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
|
||||||
|
|
||||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||||
|
|
||||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||||
|
|
||||||
## How to use our Github repository
|
## 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,18 +0,0 @@
|
|||||||
# This is the official list of TinyGo authors for copyright purposes.
|
|
||||||
#
|
|
||||||
# This file is not actively maintained.
|
|
||||||
# To be included, send a change adding the individual or
|
|
||||||
# company who owns a contribution's copyright.
|
|
||||||
#
|
|
||||||
# Names should be added to this file as one of
|
|
||||||
# Organization's name
|
|
||||||
# Individual's name <submission email address>
|
|
||||||
# Individual's name <submission email address> <email2> <emailN>
|
|
||||||
#
|
|
||||||
# Please keep the list sorted.
|
|
||||||
|
|
||||||
Ayke van Laethem <aykevanlaethem@gmail.com>
|
|
||||||
Daniel Esteban <conejo@conejo.me>
|
|
||||||
Loon, LLC.
|
|
||||||
Ron Evans <ron@hybridgroup.com>
|
|
||||||
Nia Weiss <niaow1234@gmail.com>
|
|
||||||
+44
-38
@@ -1,52 +1,52 @@
|
|||||||
# TinyGo base stage installs the most recent Go 1.17.x, LLVM 11 and the TinyGo compiler itself.
|
# TinyGo base stage just installs LLVM 8 and the TinyGo compiler itself.
|
||||||
FROM golang:1.17 AS tinygo-base
|
FROM golang:latest 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/bullseye/ llvm-toolchain-bullseye-11 main" >> /etc/apt/sources.list && \
|
echo "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch-8 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-8-dev libclang-8-dev git
|
||||||
|
|
||||||
COPY . /tinygo
|
RUN wget -O- https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
|
||||||
|
|
||||||
|
COPY . /go/src/github.com/tinygo-org/tinygo
|
||||||
|
|
||||||
# remove submodules directories and re-init them to fix any hard-coded paths
|
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||||
# after copying the tinygo directory in the previous step.
|
# after copying the tinygo directory in the previous step.
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
rm -rf ./lib/* && \
|
rm -rf ./lib/* && \
|
||||||
git submodule sync && \
|
|
||||||
git submodule update --init --recursive --force
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
COPY ./lib/picolibc-stdio.c /tinygo/lib/picolibc-stdio.c
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
|
dep ensure --vendor-only && \
|
||||||
RUN cd /tinygo/ && \
|
go install /go/src/github.com/tinygo-org/tinygo/
|
||||||
go install /tinygo/
|
|
||||||
|
|
||||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||||
FROM tinygo-base AS tinygo-wasm
|
FROM tinygo-base AS tinygo-wasm
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/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/stretch/ llvm-toolchain-stretch-8 main" >> /etc/apt/sources.list && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y make clang-11 libllvm11 lld-11 cmake ninja-build && \
|
apt-get install -y libllvm8 lld-8
|
||||||
mkdir build && \
|
|
||||||
make wasi-libc binaryen
|
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
|
apt-get install -y apt-utils python3 make binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device-avr && \
|
make gen-device-avr && \
|
||||||
|
apt-get remove -y python3 make && \
|
||||||
apt-get autoremove -y && \
|
apt-get autoremove -y && \
|
||||||
apt-get clean
|
apt-get clean
|
||||||
|
|
||||||
@@ -54,27 +54,33 @@ RUN cd /tinygo/ && \
|
|||||||
FROM tinygo-base AS tinygo-arm
|
FROM tinygo-base AS tinygo-arm
|
||||||
|
|
||||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/src /go/src/github.com/tinygo-org/tinygo/src
|
||||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/targets /go/src/github.com/tinygo-org/tinygo/targets
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/tinygo/ && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y apt-utils make clang-11 && \
|
apt-get install -y apt-utils python3 make clang-8 && \
|
||||||
make gen-device-nrf && make gen-device-stm32
|
make gen-device-nrf && make gen-device-stm32 && \
|
||||||
|
apt-get remove -y python3 make && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
# 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.
|
||||||
FROM tinygo-wasm AS tinygo-all
|
FROM tinygo-wasm AS tinygo-all
|
||||||
|
|
||||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/Makefile /go/src/github.com/tinygo-org/tinygo/
|
||||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/tools /go/src/github.com/tinygo-org/tinygo/tools
|
||||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
COPY --from=tinygo-base /go/src/github.com/tinygo-org/tinygo/lib /go/src/github.com/tinygo-org/tinygo/lib
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /go/src/github.com/tinygo-org/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 python3 make clang-8 binutils-avr gcc-avr avr-libc && \
|
||||||
make gen-device
|
make gen-device && \
|
||||||
|
apt-get remove -y python3 make && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
CMD ["tinygo"]
|
CMD ["tinygo"]
|
||||||
|
|||||||
Generated
+51
@@ -0,0 +1,51 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:06519a2ec1d59040eaccec40206f9d0b59dc662db2a032f974d6d6b9a2bcb839"
|
||||||
|
name = "github.com/blakesmith/ar"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "8bd4349a67f2533b078dbc524689d15dba0f4659"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:00b45e06c7843541372fc17d982242bd6adfc2fc382b6f2e9ef9ce53d87a50b9"
|
||||||
|
name = "github.com/marcinbor85/gohex"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "7a43cd876e46e0f6ddc553f10f91731a78e6e949"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
digest = "1:ba70784a3deee74c0ca3c87bcac3c2f93d3b2d27d8f237b768c358b45ba47da8"
|
||||||
|
name = "golang.org/x/tools"
|
||||||
|
packages = [
|
||||||
|
"go/ast/astutil",
|
||||||
|
"go/ssa",
|
||||||
|
"go/types/typeutil",
|
||||||
|
]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "8dcc6e70cdefe9a82236b6e195e4f4e2108fcb9f"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "llvm8"
|
||||||
|
digest = "1:bf5539bdf6b3cc3ec1e45926db05d81180da11ce722fa1edcce3f0b4e1967da5"
|
||||||
|
name = "tinygo.org/x/go-llvm"
|
||||||
|
packages = ["."]
|
||||||
|
pruneopts = "UT"
|
||||||
|
revision = "7707ae5d1261a8929edea7336c8087ca8b520d8d"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
input-imports = [
|
||||||
|
"github.com/blakesmith/ar",
|
||||||
|
"github.com/marcinbor85/gohex",
|
||||||
|
"golang.org/x/tools/go/ast/astutil",
|
||||||
|
"golang.org/x/tools/go/ssa",
|
||||||
|
"tinygo.org/x/go-llvm",
|
||||||
|
]
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
[[constraint]]
|
||||||
|
branch = "llvm8"
|
||||||
|
name = "tinygo.org/x/go-llvm"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/tools"
|
||||||
|
|
||||||
|
[prune]
|
||||||
|
go-tests = true
|
||||||
|
unused-packages = true
|
||||||
@@ -1,10 +1,7 @@
|
|||||||
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
|
Copyright (c) 2018-2019 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-2019 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
|
||||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
modification, are permitted provided that the following conditions are
|
modification, are permitted provided that the following conditions are
|
||||||
|
|||||||
@@ -1,572 +1,110 @@
|
|||||||
|
|
||||||
# aliases
|
# aliases
|
||||||
all: tinygo
|
all: tinygo
|
||||||
|
tinygo: build/tinygo
|
||||||
|
|
||||||
|
.PHONY: all tinygo build/tinygo test llvm-build llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
|
||||||
|
|
||||||
# Default build and source directories, as created by `make llvm-build`.
|
# Default build and source directories, as created by `make llvm-build`.
|
||||||
LLVM_BUILDDIR ?= llvm-build
|
LLVM_BUILDDIR ?= llvm-build
|
||||||
LLVM_PROJECTDIR ?= llvm-project
|
CLANG_SRC ?= llvm/tools/clang
|
||||||
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
LLD_SRC ?= llvm/tools/lld
|
||||||
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
|
||||||
|
|
||||||
# Try to autodetect LLVM build tools.
|
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||||
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
|
|
||||||
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang))
|
|
||||||
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar))
|
|
||||||
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm))
|
|
||||||
|
|
||||||
# Go binary and GOROOT to select
|
UNAME_S := $(shell uname -s)
|
||||||
GO ?= go
|
ifeq ($(UNAME_S),Linux)
|
||||||
export GOROOT = $(shell $(GO) env GOROOT)
|
|
||||||
|
|
||||||
# Flags to pass to go test.
|
|
||||||
GOTESTFLAGS ?= -v
|
|
||||||
|
|
||||||
# md5sum binary
|
|
||||||
MD5SUM = md5sum
|
|
||||||
|
|
||||||
# tinygo binary for tests
|
|
||||||
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
|
||||||
|
|
||||||
# Use CCACHE for LLVM if possible
|
|
||||||
ifneq (, $(shell command -v ccache 2> /dev/null))
|
|
||||||
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Allow enabling LLVM assertions
|
|
||||||
ifeq (1, $(ASSERT))
|
|
||||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
|
|
||||||
else
|
|
||||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
|
||||||
endif
|
|
||||||
|
|
||||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
|
|
||||||
|
|
||||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsmanifest
|
|
||||||
|
|
||||||
ifeq ($(OS),Windows_NT)
|
|
||||||
EXE = .exe
|
|
||||||
START_GROUP = -Wl,--start-group
|
|
||||||
END_GROUP = -Wl,--end-group
|
|
||||||
|
|
||||||
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
|
||||||
# Without this flag, linking results in errors like these:
|
|
||||||
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
|
||||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
|
|
||||||
|
|
||||||
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
|
|
||||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
|
||||||
CGO_LDFLAGS_EXTRA += -lversion
|
|
||||||
|
|
||||||
BINARYEN_OPTION += -DCMAKE_EXE_LINKER_FLAGS='-static-libgcc -static-libstdc++'
|
|
||||||
|
|
||||||
LIBCLANG_NAME = libclang
|
|
||||||
|
|
||||||
else ifeq ($(shell uname -s),Darwin)
|
|
||||||
MD5SUM = md5
|
|
||||||
LIBCLANG_NAME = clang
|
|
||||||
else ifeq ($(shell uname -s),FreeBSD)
|
|
||||||
MD5SUM = md5
|
|
||||||
LIBCLANG_NAME = clang
|
|
||||||
START_GROUP = -Wl,--start-group
|
|
||||||
END_GROUP = -Wl,--end-group
|
|
||||||
else
|
|
||||||
LIBCLANG_NAME = clang
|
|
||||||
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) $(abspath $(LLVM_BUILDDIR))/lib/libclang.a -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions -lclangToolingRefactor $(END_GROUP) -lstdc++
|
||||||
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
|
||||||
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
|
||||||
|
|
||||||
# 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*)","")
|
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -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++11
|
||||||
CGO_CXXFLAGS=-std=c++14
|
CGO_LDFLAGS=-L$(LLVM_BUILDDIR)/lib $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS))
|
||||||
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
clean:
|
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/testing src/internal/reflectlite transform
|
FMT_PATHS = ./*.go compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall
|
||||||
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-nrf gen-device-sam gen-device-stm32
|
||||||
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
|
|
||||||
ifneq ($(STM32), 0)
|
|
||||||
gen-device: gen-device-stm32
|
|
||||||
endif
|
|
||||||
|
|
||||||
gen-device-avr:
|
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
|
./tools/gen-device-avr.py lib/avr/packs/atmega src/device/avr/
|
||||||
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
./tools/gen-device-avr.py lib/avr/packs/tiny src/device/avr/
|
||||||
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
go fmt ./src/device/avr
|
||||||
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
|
||||||
@GO111MODULE=off $(GO) fmt ./src/device/avr
|
|
||||||
|
|
||||||
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
gen-device-nrf:
|
||||||
$(GO) build -o $@ ./tools/gen-device-svd/
|
./tools/gen-device-svd.py lib/nrfx/mdk/ src/device/nrf/ --source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk
|
||||||
|
go fmt ./src/device/nrf
|
||||||
|
|
||||||
gen-device-esp: build/gen-device-svd
|
gen-device-sam:
|
||||||
./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/
|
./tools/gen-device-svd.py lib/cmsis-svd/data/Atmel/ src/device/sam/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
|
go fmt ./src/device/sam
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/esp
|
|
||||||
|
|
||||||
gen-device-nrf: build/gen-device-svd
|
gen-device-stm32:
|
||||||
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
./tools/gen-device-svd.py lib/cmsis-svd/data/STMicro/ src/device/stm32/ --source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
go fmt ./src/device/stm32
|
||||||
|
|
||||||
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
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
|
||||||
|
|
||||||
gen-device-sifive: build/gen-device-svd
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ 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
|
|
||||||
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
|
||||||
|
|
||||||
gen-device-rp: build/gen-device-svd
|
|
||||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
|
|
||||||
GO111MODULE=off $(GO) fmt ./src/device/rp
|
|
||||||
|
|
||||||
# Get LLVM sources.
|
# Get LLVM sources.
|
||||||
$(LLVM_PROJECTDIR)/llvm:
|
llvm/README.txt:
|
||||||
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
git clone -b release_80 https://github.com/llvm-mirror/llvm.git llvm
|
||||||
llvm-source: $(LLVM_PROJECTDIR)/llvm
|
llvm/tools/clang/README.txt:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/clang.git llvm/tools/clang
|
||||||
|
llvm/tools/lld/README.md:
|
||||||
|
git clone -b release_80 https://github.com/llvm-mirror/lld.git llvm/tools/lld
|
||||||
|
llvm-source: llvm/README.txt llvm/tools/clang/README.txt llvm/tools/lld/README.md
|
||||||
|
|
||||||
# Configure LLVM.
|
# Configure LLVM.
|
||||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
llvm-build/build.ninja: llvm-source
|
||||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
mkdir -p llvm-build; cd llvm-build; cmake -G Ninja ../llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_ASSERTIONS=OFF -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF
|
||||||
mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
|
|
||||||
|
|
||||||
# Build LLVM.
|
# Build LLVM.
|
||||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
llvm-build: llvm-build/build.ninja
|
||||||
cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS)
|
cd llvm-build; ninja
|
||||||
|
|
||||||
# Build Binaryen
|
|
||||||
.PHONY: binaryen
|
|
||||||
binaryen: build/wasm-opt
|
|
||||||
build/wasm-opt:
|
|
||||||
cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON $(BINARYEN_OPTION) && ninja
|
|
||||||
cp lib/binaryen/bin/wasm-opt build/wasm-opt
|
|
||||||
|
|
||||||
# Build wasi-libc sysroot
|
|
||||||
.PHONY: wasi-libc
|
|
||||||
wasi-libc: 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_CFLAGS="-O2 -g -DNDEBUG" WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
|
||||||
|
|
||||||
|
|
||||||
# Build the Go compiler.
|
# Build the Go compiler.
|
||||||
tinygo:
|
build/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-build/bin/llvm-config ]; then echo "Fetch and build LLVM first by running:\n make llvm-source\n make llvm-build"; exit 1; fi
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -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 -tags byollvm .
|
||||||
|
|
||||||
test: wasi-libc
|
test:
|
||||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" go test -v -tags byollvm .
|
||||||
|
|
||||||
TEST_PACKAGES = \
|
release: build/tinygo gen-device
|
||||||
compress/bzip2 \
|
|
||||||
container/heap \
|
|
||||||
container/list \
|
|
||||||
container/ring \
|
|
||||||
crypto/des \
|
|
||||||
crypto/dsa \
|
|
||||||
crypto/md5 \
|
|
||||||
crypto/rc4 \
|
|
||||||
crypto/sha1 \
|
|
||||||
crypto/sha256 \
|
|
||||||
crypto/sha512 \
|
|
||||||
encoding \
|
|
||||||
encoding/ascii85 \
|
|
||||||
encoding/base32 \
|
|
||||||
encoding/hex \
|
|
||||||
hash \
|
|
||||||
hash/adler32 \
|
|
||||||
hash/fnv \
|
|
||||||
hash/crc64 \
|
|
||||||
html \
|
|
||||||
index/suffixarray \
|
|
||||||
internal/itoa \
|
|
||||||
math \
|
|
||||||
math/cmplx \
|
|
||||||
net/mail \
|
|
||||||
reflect \
|
|
||||||
testing \
|
|
||||||
testing/iotest \
|
|
||||||
text/scanner \
|
|
||||||
unicode \
|
|
||||||
unicode/utf16 \
|
|
||||||
unicode/utf8 \
|
|
||||||
|
|
||||||
# Test known-working standard library packages.
|
|
||||||
# TODO: parallelize, and only show failing tests (no implied -v flag).
|
|
||||||
.PHONY: tinygo-test
|
|
||||||
tinygo-test:
|
|
||||||
$(TINYGO) test $(TEST_PACKAGES)
|
|
||||||
|
|
||||||
.PHONY: smoketest
|
|
||||||
smoketest:
|
|
||||||
$(TINYGO) version
|
|
||||||
# test all examples (except pwm)
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
# test simulated boards on play.tinygo.org
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=pca10040 examples/blinky2
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=pca10056 examples/blinky2
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
|
|
||||||
@$(MD5SUM) test.wasm
|
|
||||||
# test all targets/boards
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
|
||||||
@$(MD5SUM) test.gba
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
|
|
||||||
@$(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=feather-nrf52840-sense examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
# test pwm
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(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
|
|
||||||
ifneq ($(STM32), 0)
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
endif
|
|
||||||
ifneq ($(AVR), 0)
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
|
||||||
@$(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
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
|
||||||
@$(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
|
|
||||||
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
|
|
||||||
@$(MD5SUM) test.bin
|
|
||||||
endif
|
|
||||||
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
|
|
||||||
@$(MD5SUM) test.bin
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
|
|
||||||
$(TINYGO) build -size short -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 -size short -o test.hex -target=pca10040 -serial=none examples/echo
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
|
|
||||||
@$(MD5SUM) test.nro
|
|
||||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
|
|
||||||
@$(MD5SUM) test.hex
|
|
||||||
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
|
|
||||||
GOOS=windows GOARCH=amd64 $(TINYGO) build -o test.exe ./testdata/cgo
|
|
||||||
ifneq ($(OS),Windows_NT)
|
|
||||||
# TODO: this does not yet work on Windows. Somehow, unused functions are
|
|
||||||
# not garbage collected.
|
|
||||||
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
|
|
||||||
endif
|
|
||||||
|
|
||||||
|
|
||||||
wasmtest:
|
|
||||||
$(GO) test ./tests/wasm
|
|
||||||
|
|
||||||
build/release: tinygo gen-device wasi-libc binaryen
|
|
||||||
@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
|
||||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
|
||||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
|
||||||
@mkdir -p build/release/tinygo/lib/musl/arch
|
|
||||||
@mkdir -p build/release/tinygo/lib/musl/crt
|
|
||||||
@mkdir -p build/release/tinygo/lib/musl/src
|
|
||||||
@mkdir -p build/release/tinygo/lib/nrfx
|
@mkdir -p build/release/tinygo/lib/nrfx
|
||||||
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
|
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
|
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||||
@mkdir -p build/release/tinygo/lib/wasi-libc
|
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||||
@mkdir -p build/release/tinygo/pkg/armv6m-unknown-unknown-eabi
|
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7m-unknown-unknown-eabi
|
|
||||||
@mkdir -p build/release/tinygo/pkg/armv7em-unknown-unknown-eabi
|
|
||||||
@echo copying source files
|
@echo copying source files
|
||||||
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
|
@cp -p build/tinygo build/release/tinygo/bin
|
||||||
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
|
|
||||||
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||||
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
||||||
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
||||||
@cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch
|
|
||||||
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
|
|
||||||
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
|
|
||||||
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
|
|
||||||
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
|
|
||||||
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
|
|
||||||
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
|
|
||||||
@cp -rp lib/musl/include build/release/tinygo/lib/musl
|
|
||||||
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
|
|
||||||
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
|
||||||
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||||
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
|
|
||||||
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
|
|
||||||
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
|
|
||||||
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
|
|
||||||
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
|
|
||||||
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
|
|
||||||
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
|
|
||||||
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
|
|
||||||
@cp -rp src build/release/tinygo/src
|
@cp -rp src build/release/tinygo/src
|
||||||
@cp -rp targets build/release/tinygo/targets
|
@cp -rp targets build/release/tinygo/targets
|
||||||
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/compiler-rt compiler-rt
|
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
|
||||||
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/compiler-rt compiler-rt
|
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
|
||||||
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/compiler-rt compiler-rt
|
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
|
||||||
./build/tinygo build-library -target=armv6m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv6m-unknown-unknown-eabi/picolibc picolibc
|
|
||||||
./build/tinygo build-library -target=armv7m-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7m-unknown-unknown-eabi/picolibc picolibc
|
|
||||||
./build/tinygo build-library -target=armv7em-unknown-unknown-eabi -o build/release/tinygo/pkg/armv7em-unknown-unknown-eabi/picolibc 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
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# TinyGo - Go compiler for small places
|
# TinyGo - Go compiler for small places
|
||||||
|
|
||||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
|
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||||
|
|
||||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (WASM), and command-line tools.
|
||||||
|
|
||||||
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
led := machine.LED
|
led := machine.GPIO{machine.LED}
|
||||||
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
led.Configure(machine.GPIOConfig{Mode: machine.GPIO_OUTPUT})
|
||||||
for {
|
for {
|
||||||
led.Low()
|
led.Low()
|
||||||
time.Sleep(time.Millisecond * 1000)
|
time.Sleep(time.Millisecond * 1000)
|
||||||
@@ -43,79 +43,19 @@ 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 71 microcontroller boards are currently supported:
|
The following microcontroller boards are currently supported:
|
||||||
|
|
||||||
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
|
||||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||||
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
|
|
||||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
|
||||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
|
||||||
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
|
|
||||||
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
|
|
||||||
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
|
|
||||||
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
|
|
||||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
|
||||||
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
|
|
||||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
|
||||||
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
|
|
||||||
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
|
|
||||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
|
||||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
|
||||||
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
|
|
||||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
|
||||||
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
|
|
||||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
|
||||||
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
|
|
||||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
|
||||||
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
|
||||||
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
|
|
||||||
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
|
||||||
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
|
|
||||||
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
|
|
||||||
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
|
|
||||||
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
|
|
||||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||||
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
|
* [BBC:Microbit](https://microbit.org/)
|
||||||
* [BBC micro:bit](https://microbit.org/)
|
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||||
* [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)
|
|
||||||
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
|
|
||||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
|
||||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
|
||||||
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
|
|
||||||
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
|
|
||||||
* [Nintendo Switch](https://www.nintendo.com/switch/)
|
|
||||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
* [Nordic Semiconductor 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)
|
||||||
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
|
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||||
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
|
|
||||||
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
|
|
||||||
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
|
||||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
|
||||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
|
||||||
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
|
|
||||||
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
|
||||||
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
|
|
||||||
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
|
|
||||||
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
|
||||||
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
|
|
||||||
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
|
|
||||||
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
|
||||||
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
|
||||||
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
|
|
||||||
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
|
|
||||||
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
|
|
||||||
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
|
|
||||||
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
|
|
||||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
|
||||||
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
|
||||||
|
|
||||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||||
|
|
||||||
@@ -174,7 +114,3 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||||
|
|
||||||
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/11.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
|
||||||
|
|
||||||
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
|
|
||||||
|
|||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Statistics about code size in a program.
|
||||||
|
type ProgramSize struct {
|
||||||
|
Packages map[string]*PackageSize
|
||||||
|
Sum *PackageSize
|
||||||
|
Code uint64
|
||||||
|
Data uint64
|
||||||
|
BSS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the list of package names (ProgramSize.Packages) sorted
|
||||||
|
// alphabetically.
|
||||||
|
func (ps *ProgramSize) SortedPackageNames() []string {
|
||||||
|
names := make([]string, 0, len(ps.Packages))
|
||||||
|
for name := range ps.Packages {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// The size of a package, calculated from the linked object file.
|
||||||
|
type PackageSize struct {
|
||||||
|
Code uint64
|
||||||
|
ROData uint64
|
||||||
|
Data uint64
|
||||||
|
BSS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flash usage in regular microcontrollers.
|
||||||
|
func (ps *PackageSize) Flash() uint64 {
|
||||||
|
return ps.Code + ps.ROData + ps.Data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static RAM usage in regular microcontrollers.
|
||||||
|
func (ps *PackageSize) RAM() uint64 {
|
||||||
|
return ps.Data + ps.BSS
|
||||||
|
}
|
||||||
|
|
||||||
|
type symbolList []elf.Symbol
|
||||||
|
|
||||||
|
func (l symbolList) Len() int {
|
||||||
|
return len(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l symbolList) Less(i, j int) bool {
|
||||||
|
bind_i := elf.ST_BIND(l[i].Info)
|
||||||
|
bind_j := elf.ST_BIND(l[j].Info)
|
||||||
|
if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK {
|
||||||
|
// sort weak symbols after non-weak symbols
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return l[i].Value < l[j].Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l symbolList) Swap(i, j int) {
|
||||||
|
l[i], l[j] = l[j], l[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate program/data size breakdown of each package for a given ELF file.
|
||||||
|
func Sizes(path string) (*ProgramSize, error) {
|
||||||
|
file, err := elf.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
var sumCode uint64
|
||||||
|
var sumData uint64
|
||||||
|
var sumBSS uint64
|
||||||
|
for _, section := range file.Sections {
|
||||||
|
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if section.Type == elf.SHT_NOBITS {
|
||||||
|
sumBSS += section.Size
|
||||||
|
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
|
||||||
|
sumCode += section.Size
|
||||||
|
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||||
|
sumData += section.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allSymbols, err := file.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
symbols := make([]elf.Symbol, 0, len(allSymbols))
|
||||||
|
for _, symbol := range allSymbols {
|
||||||
|
symType := elf.ST_TYPE(symbol.Info)
|
||||||
|
if symbol.Size == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if symbol.Section >= elf.SectionIndex(len(file.Sections)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
section := file.Sections[symbol.Section]
|
||||||
|
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
symbols = append(symbols, symbol)
|
||||||
|
}
|
||||||
|
sort.Sort(symbolList(symbols))
|
||||||
|
|
||||||
|
sizes := map[string]*PackageSize{}
|
||||||
|
var lastSymbolValue uint64
|
||||||
|
for _, symbol := range symbols {
|
||||||
|
symType := elf.ST_TYPE(symbol.Info)
|
||||||
|
//bind := elf.ST_BIND(symbol.Info)
|
||||||
|
section := file.Sections[symbol.Section]
|
||||||
|
pkgName := "(bootstrap)"
|
||||||
|
symName := strings.TrimLeft(symbol.Name, "(*")
|
||||||
|
dot := strings.IndexByte(symName, '.')
|
||||||
|
if dot > 0 {
|
||||||
|
pkgName = symName[:dot]
|
||||||
|
}
|
||||||
|
pkgSize := sizes[pkgName]
|
||||||
|
if pkgSize == nil {
|
||||||
|
pkgSize = &PackageSize{}
|
||||||
|
sizes[pkgName] = pkgSize
|
||||||
|
}
|
||||||
|
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
|
||||||
|
if symType == elf.STT_FUNC {
|
||||||
|
pkgSize.Code += symbol.Size
|
||||||
|
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||||
|
if section.Type == elf.SHT_NOBITS {
|
||||||
|
pkgSize.BSS += symbol.Size
|
||||||
|
} else {
|
||||||
|
pkgSize.Data += symbol.Size
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pkgSize.ROData += symbol.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastSymbolValue = symbol.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
sum := &PackageSize{}
|
||||||
|
for _, pkg := range sizes {
|
||||||
|
sum.Code += pkg.Code
|
||||||
|
sum.ROData += pkg.ROData
|
||||||
|
sum.Data += pkg.Data
|
||||||
|
sum.BSS += pkg.BSS
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ProgramSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
+126
@@ -0,0 +1,126 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the cache directory, usually ~/.cache/tinygo
|
||||||
|
func cacheDir() string {
|
||||||
|
dir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
panic("could not find cache dir: " + err.Error())
|
||||||
|
}
|
||||||
|
return filepath.Join(dir, "tinygo")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||||
|
// for stale caches.
|
||||||
|
func cacheTimestamp(paths []string) (time.Time, error) {
|
||||||
|
var timestamp time.Time
|
||||||
|
for _, path := range paths {
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
if timestamp.IsZero() {
|
||||||
|
timestamp = st.ModTime()
|
||||||
|
} else if timestamp.Before(st.ModTime()) {
|
||||||
|
timestamp = st.ModTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timestamp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load a given file from the cache. Return "", nil if no cached file can
|
||||||
|
// be found (or the file is stale), return the absolute path if there is a cache
|
||||||
|
// and return an error on I/O errors.
|
||||||
|
//
|
||||||
|
// TODO: the configKey is currently ignored. It is supposed to be used as extra
|
||||||
|
// data for the cache key, like the compiler version and arguments.
|
||||||
|
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||||
|
dir := cacheDir()
|
||||||
|
cachepath := filepath.Join(dir, name)
|
||||||
|
cacheStat, err := os.Stat(cachepath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil // does not exist
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err // cannot stat cache file
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceTimestamp, err := cacheTimestamp(sourceFiles)
|
||||||
|
if err != nil {
|
||||||
|
return "", err // cannot stat source files
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheStat.ModTime().After(sourceTimestamp) {
|
||||||
|
return cachepath, nil
|
||||||
|
} else {
|
||||||
|
os.Remove(cachepath)
|
||||||
|
// stale cache
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the file located at tmppath in the cache with the given name. The
|
||||||
|
// tmppath may or may not be gone afterwards.
|
||||||
|
//
|
||||||
|
// Note: the configKey is ignored, see cacheLoad.
|
||||||
|
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
|
||||||
|
// get the last modified time
|
||||||
|
if len(sourceFiles) == 0 {
|
||||||
|
panic("cache: no source files")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check the config key
|
||||||
|
|
||||||
|
dir := cacheDir()
|
||||||
|
err := os.MkdirAll(dir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cachepath := filepath.Join(dir, name)
|
||||||
|
err = moveFile(tmppath, cachepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cachepath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||||
|
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||||
|
// the old file is removed.
|
||||||
|
func moveFile(src, dst string) error {
|
||||||
|
err := os.Rename(src, dst)
|
||||||
|
if err == nil {
|
||||||
|
// Success!
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Failed to move, probably a different filesystem.
|
||||||
|
// Do a copy + remove.
|
||||||
|
inf, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer inf.Close()
|
||||||
|
outpath := dst + ".tmp"
|
||||||
|
outf, err := os.Create(outpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(outf, inf)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(outpath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Rename(dst+".tmp", dst)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return outf.Close()
|
||||||
|
}
|
||||||
-206
@@ -1,206 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"debug/elf"
|
|
||||||
"debug/pe"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/blakesmith/ar"
|
|
||||||
)
|
|
||||||
|
|
||||||
// makeArchive creates an arcive for static linking from a list of object files
|
|
||||||
// given as a parameter. It is equivalent to the following command:
|
|
||||||
//
|
|
||||||
// ar -rcs <archivePath> <objs...>
|
|
||||||
func makeArchive(arfile *os.File, objs []string) error {
|
|
||||||
// Open the archive file.
|
|
||||||
arwriter := ar.NewWriter(arfile)
|
|
||||||
err := arwriter.WriteGlobalHeader()
|
|
||||||
if err != nil {
|
|
||||||
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open all object files and read the symbols for the symbol table.
|
|
||||||
symbolTable := []struct {
|
|
||||||
name string // symbol name
|
|
||||||
fileIndex int // index into objfiles
|
|
||||||
}{}
|
|
||||||
archiveOffsets := make([]int32, len(objs))
|
|
||||||
for i, objpath := range objs {
|
|
||||||
objfile, err := os.Open(objpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the symbols and add them to the symbol table.
|
|
||||||
if dbg, err := elf.NewFile(objfile); err == nil {
|
|
||||||
symbols, err := dbg.Symbols()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, symbol := range symbols {
|
|
||||||
bind := elf.ST_BIND(symbol.Info)
|
|
||||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
|
||||||
// Don't include local symbols (STB_LOCAL).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
|
|
||||||
// Not a function.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Include in archive.
|
|
||||||
symbolTable = append(symbolTable, struct {
|
|
||||||
name string
|
|
||||||
fileIndex int
|
|
||||||
}{symbol.Name, i})
|
|
||||||
}
|
|
||||||
} else if dbg, err := pe.NewFile(objfile); err == nil {
|
|
||||||
for _, symbol := range dbg.Symbols {
|
|
||||||
if symbol.StorageClass != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symbol.SectionNumber == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
symbolTable = append(symbolTable, struct {
|
|
||||||
name string
|
|
||||||
fileIndex int
|
|
||||||
}{symbol.Name, i})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close file, to avoid issues with too many open files (especially on
|
|
||||||
// MacOS X).
|
|
||||||
objfile.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the symbol table buffer.
|
|
||||||
// For some (sparse) details on the file format:
|
|
||||||
// https://en.wikipedia.org/wiki/Ar_(Unix)#System_V_(or_GNU)_variant
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
binary.Write(buf, binary.BigEndian, int32(len(symbolTable)))
|
|
||||||
for range symbolTable {
|
|
||||||
// This is a placeholder index, it will be updated after all files have
|
|
||||||
// been written to the archive (see the end of this function).
|
|
||||||
err = binary.Write(buf, binary.BigEndian, int32(0))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, sym := range symbolTable {
|
|
||||||
_, err := buf.Write([]byte(sym.name + "\x00"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for buf.Len()%2 != 0 {
|
|
||||||
// The symbol table must be aligned.
|
|
||||||
// This appears to be required by lld.
|
|
||||||
buf.WriteByte(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the symbol table.
|
|
||||||
err = arwriter.WriteHeader(&ar.Header{
|
|
||||||
Name: "/",
|
|
||||||
ModTime: time.Unix(0, 0),
|
|
||||||
Uid: 0,
|
|
||||||
Gid: 0,
|
|
||||||
Mode: 0,
|
|
||||||
Size: int64(buf.Len()),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of the start of the symbol table.
|
|
||||||
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write symbol table contents.
|
|
||||||
_, err = arfile.Write(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all object files to the archive.
|
|
||||||
var copyBuf bytes.Buffer
|
|
||||||
for i, objpath := range objs {
|
|
||||||
objfile, err := os.Open(objpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer objfile.Close()
|
|
||||||
|
|
||||||
// Store the start index, for when we'll update the symbol table with
|
|
||||||
// the correct file start indices.
|
|
||||||
offset, err := arfile.Seek(0, os.SEEK_CUR)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if int64(int32(offset)) != offset {
|
|
||||||
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
|
|
||||||
}
|
|
||||||
archiveOffsets[i] = int32(offset)
|
|
||||||
|
|
||||||
// Write the file header.
|
|
||||||
st, err := objfile.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = arwriter.WriteHeader(&ar.Header{
|
|
||||||
Name: filepath.Base(objfile.Name()),
|
|
||||||
ModTime: time.Unix(0, 0),
|
|
||||||
Uid: 0,
|
|
||||||
Gid: 0,
|
|
||||||
Mode: 0644,
|
|
||||||
Size: st.Size(),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the file contents into the archive.
|
|
||||||
// First load all contents into a buffer, then write it all in one go to
|
|
||||||
// the archive file. This is a bit complicated, but is necessary because
|
|
||||||
// io.Copy can't deal with files that are of an odd size.
|
|
||||||
copyBuf.Reset()
|
|
||||||
n, err := io.Copy(©Buf, objfile)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
|
||||||
}
|
|
||||||
if n != st.Size() {
|
|
||||||
return errors.New("file modified during ar creation: " + arfile.Name())
|
|
||||||
}
|
|
||||||
_, err = arwriter.Write(copyBuf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// File is not needed anymore.
|
|
||||||
objfile.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create symbol indices.
|
|
||||||
indicesBuf := &bytes.Buffer{}
|
|
||||||
for _, sym := range symbolTable {
|
|
||||||
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrite placeholder indices.
|
|
||||||
_, err = arfile.WriteAt(indicesBuf.Bytes(), symbolTableStart+4)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
-1183
File diff suppressed because it is too large
Load Diff
@@ -1,163 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test whether the Clang generated "target-cpu" and "target-features"
|
|
||||||
// attributes match the CPU and Features property in TinyGo target files.
|
|
||||||
func TestClangAttributes(t *testing.T) {
|
|
||||||
var targetNames = []string{
|
|
||||||
// Please keep this list sorted!
|
|
||||||
"atmega328p",
|
|
||||||
"atmega1280",
|
|
||||||
"atmega1284p",
|
|
||||||
"atmega2560",
|
|
||||||
"attiny85",
|
|
||||||
"cortex-m0",
|
|
||||||
"cortex-m0plus",
|
|
||||||
"cortex-m3",
|
|
||||||
//"cortex-m33", // TODO: broken in LLVM 11, fixed in https://reviews.llvm.org/D90305
|
|
||||||
"cortex-m4",
|
|
||||||
"cortex-m7",
|
|
||||||
"esp32c3",
|
|
||||||
"fe310",
|
|
||||||
"gameboy-advance",
|
|
||||||
"k210",
|
|
||||||
"nintendoswitch",
|
|
||||||
"riscv-qemu",
|
|
||||||
"wasi",
|
|
||||||
"wasm",
|
|
||||||
}
|
|
||||||
if hasBuiltinTools {
|
|
||||||
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
|
|
||||||
// which also implies it was built with Xtensa support.
|
|
||||||
targetNames = append(targetNames, "esp32", "esp8266")
|
|
||||||
}
|
|
||||||
for _, targetName := range targetNames {
|
|
||||||
targetName := targetName
|
|
||||||
t.Run(targetName, func(t *testing.T) {
|
|
||||||
testClangAttributes(t, &compileopts.Options{Target: targetName})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, options := range []*compileopts.Options{
|
|
||||||
{GOOS: "linux", GOARCH: "386"},
|
|
||||||
{GOOS: "linux", GOARCH: "amd64"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "5"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "6"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "7"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm64"},
|
|
||||||
{GOOS: "darwin", GOARCH: "amd64"},
|
|
||||||
{GOOS: "darwin", GOARCH: "arm64"},
|
|
||||||
{GOOS: "windows", GOARCH: "amd64"},
|
|
||||||
} {
|
|
||||||
t.Run("GOOS="+options.GOOS+",GOARCH="+options.GOARCH, func(t *testing.T) {
|
|
||||||
testClangAttributes(t, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
|
||||||
testDir := t.TempDir()
|
|
||||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
|
||||||
|
|
||||||
ctx := llvm.NewContext()
|
|
||||||
defer ctx.Dispose()
|
|
||||||
|
|
||||||
target, err := compileopts.LoadTarget(options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not load target: %s", err)
|
|
||||||
}
|
|
||||||
config := compileopts.Config{
|
|
||||||
Options: options,
|
|
||||||
Target: target,
|
|
||||||
ClangHeaders: clangHeaderPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a very simple C input file.
|
|
||||||
srcpath := filepath.Join(testDir, "test.c")
|
|
||||||
err = ioutil.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not write target file %s: %s", srcpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile this file using Clang.
|
|
||||||
outpath := filepath.Join(testDir, "test.bc")
|
|
||||||
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...)
|
|
||||||
if config.GOOS() == "darwin" {
|
|
||||||
// Silence some warnings that happen when testing GOOS=darwin on
|
|
||||||
// something other than MacOS.
|
|
||||||
flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot")
|
|
||||||
}
|
|
||||||
err = runCCompiler(flags...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile %s: %s", srcpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the resulting LLVM bitcode.
|
|
||||||
mod, err := ctx.ParseBitcodeFile(outpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not parse bitcode file %s: %s", outpath, err)
|
|
||||||
}
|
|
||||||
defer mod.Dispose()
|
|
||||||
|
|
||||||
// Check whether the LLVM target matches.
|
|
||||||
if mod.Target() != config.Triple() {
|
|
||||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the "target-cpu" and "target-features" string attribute of the add
|
|
||||||
// function.
|
|
||||||
add := mod.NamedFunction("add")
|
|
||||||
var cpu, features string
|
|
||||||
cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu")
|
|
||||||
featuresAttr := add.GetStringAttributeAtIndex(-1, "target-features")
|
|
||||||
if !cpuAttr.IsNil() {
|
|
||||||
cpu = cpuAttr.GetStringValue()
|
|
||||||
}
|
|
||||||
if !featuresAttr.IsNil() {
|
|
||||||
features = featuresAttr.GetStringValue()
|
|
||||||
}
|
|
||||||
if cpu != config.CPU() {
|
|
||||||
t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu)
|
|
||||||
}
|
|
||||||
if features != config.Features() {
|
|
||||||
if llvm.Version != "11.0.0" {
|
|
||||||
// This needs to be removed once we switch to LLVM 12.
|
|
||||||
// LLVM 11.0.0 uses a different "target-features" string than LLVM
|
|
||||||
// 11.1.0 for Thumb targets. The Xtensa fork is still based on LLVM
|
|
||||||
// 11.0.0, so we need to skip this check on that version.
|
|
||||||
t.Errorf("target has LLVM features %#v but Clang makes it %#v", config.Features(), features)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This TestMain is necessary because TinyGo may also be invoked to run certain
|
|
||||||
// LLVM tools in a separate process. Not capturing these invocations would lead
|
|
||||||
// to recursive tests.
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
if len(os.Args) >= 2 {
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "clang", "ld.lld", "wasm-ld":
|
|
||||||
// Invoke a specific tool.
|
|
||||||
err := RunTool(os.Args[1], os.Args[2:]...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run normal tests.
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
|
||||||
var genericBuiltins = []string{
|
|
||||||
"absvdi2.c",
|
|
||||||
"absvsi2.c",
|
|
||||||
"absvti2.c",
|
|
||||||
"adddf3.c",
|
|
||||||
"addsf3.c",
|
|
||||||
"addtf3.c",
|
|
||||||
"addvdi3.c",
|
|
||||||
"addvsi3.c",
|
|
||||||
"addvti3.c",
|
|
||||||
"apple_versioning.c",
|
|
||||||
"ashldi3.c",
|
|
||||||
"ashlti3.c",
|
|
||||||
"ashrdi3.c",
|
|
||||||
"ashrti3.c",
|
|
||||||
"bswapdi2.c",
|
|
||||||
"bswapsi2.c",
|
|
||||||
"clzdi2.c",
|
|
||||||
"clzsi2.c",
|
|
||||||
"clzti2.c",
|
|
||||||
"cmpdi2.c",
|
|
||||||
"cmpti2.c",
|
|
||||||
"comparedf2.c",
|
|
||||||
"comparesf2.c",
|
|
||||||
"ctzdi2.c",
|
|
||||||
"ctzsi2.c",
|
|
||||||
"ctzti2.c",
|
|
||||||
"divdc3.c",
|
|
||||||
"divdf3.c",
|
|
||||||
"divdi3.c",
|
|
||||||
"divmoddi4.c",
|
|
||||||
"divmodsi4.c",
|
|
||||||
"divsc3.c",
|
|
||||||
"divsf3.c",
|
|
||||||
"divsi3.c",
|
|
||||||
"divtc3.c",
|
|
||||||
"divti3.c",
|
|
||||||
"divtf3.c",
|
|
||||||
"extendsfdf2.c",
|
|
||||||
"extendhfsf2.c",
|
|
||||||
"ffsdi2.c",
|
|
||||||
"ffssi2.c",
|
|
||||||
"ffsti2.c",
|
|
||||||
"fixdfdi.c",
|
|
||||||
"fixdfsi.c",
|
|
||||||
"fixdfti.c",
|
|
||||||
"fixsfdi.c",
|
|
||||||
"fixsfsi.c",
|
|
||||||
"fixsfti.c",
|
|
||||||
"fixunsdfdi.c",
|
|
||||||
"fixunsdfsi.c",
|
|
||||||
"fixunsdfti.c",
|
|
||||||
"fixunssfdi.c",
|
|
||||||
"fixunssfsi.c",
|
|
||||||
"fixunssfti.c",
|
|
||||||
"floatdidf.c",
|
|
||||||
"floatdisf.c",
|
|
||||||
"floatsidf.c",
|
|
||||||
"floatsisf.c",
|
|
||||||
"floattidf.c",
|
|
||||||
"floattisf.c",
|
|
||||||
"floatundidf.c",
|
|
||||||
"floatundisf.c",
|
|
||||||
"floatunsidf.c",
|
|
||||||
"floatunsisf.c",
|
|
||||||
"floatuntidf.c",
|
|
||||||
"floatuntisf.c",
|
|
||||||
//"int_util.c",
|
|
||||||
"lshrdi3.c",
|
|
||||||
"lshrti3.c",
|
|
||||||
"moddi3.c",
|
|
||||||
"modsi3.c",
|
|
||||||
"modti3.c",
|
|
||||||
"muldc3.c",
|
|
||||||
"muldf3.c",
|
|
||||||
"muldi3.c",
|
|
||||||
"mulodi4.c",
|
|
||||||
"mulosi4.c",
|
|
||||||
"muloti4.c",
|
|
||||||
"mulsc3.c",
|
|
||||||
"mulsf3.c",
|
|
||||||
"multi3.c",
|
|
||||||
"multf3.c",
|
|
||||||
"mulvdi3.c",
|
|
||||||
"mulvsi3.c",
|
|
||||||
"mulvti3.c",
|
|
||||||
"negdf2.c",
|
|
||||||
"negdi2.c",
|
|
||||||
"negsf2.c",
|
|
||||||
"negti2.c",
|
|
||||||
"negvdi2.c",
|
|
||||||
"negvsi2.c",
|
|
||||||
"negvti2.c",
|
|
||||||
"os_version_check.c",
|
|
||||||
"paritydi2.c",
|
|
||||||
"paritysi2.c",
|
|
||||||
"parityti2.c",
|
|
||||||
"popcountdi2.c",
|
|
||||||
"popcountsi2.c",
|
|
||||||
"popcountti2.c",
|
|
||||||
"powidf2.c",
|
|
||||||
"powisf2.c",
|
|
||||||
"powitf2.c",
|
|
||||||
"subdf3.c",
|
|
||||||
"subsf3.c",
|
|
||||||
"subvdi3.c",
|
|
||||||
"subvsi3.c",
|
|
||||||
"subvti3.c",
|
|
||||||
"subtf3.c",
|
|
||||||
"trampoline_setup.c",
|
|
||||||
"truncdfhf2.c",
|
|
||||||
"truncdfsf2.c",
|
|
||||||
"truncsfhf2.c",
|
|
||||||
"ucmpdi2.c",
|
|
||||||
"ucmpti2.c",
|
|
||||||
"udivdi3.c",
|
|
||||||
"udivmoddi4.c",
|
|
||||||
"udivmodsi4.c",
|
|
||||||
"udivmodti4.c",
|
|
||||||
"udivsi3.c",
|
|
||||||
"udivti3.c",
|
|
||||||
"umoddi3.c",
|
|
||||||
"umodsi3.c",
|
|
||||||
"umodti3.c",
|
|
||||||
}
|
|
||||||
|
|
||||||
var aeabiBuiltins = []string{
|
|
||||||
"arm/aeabi_cdcmp.S",
|
|
||||||
"arm/aeabi_cdcmpeq_check_nan.c",
|
|
||||||
"arm/aeabi_cfcmp.S",
|
|
||||||
"arm/aeabi_cfcmpeq_check_nan.c",
|
|
||||||
"arm/aeabi_dcmp.S",
|
|
||||||
"arm/aeabi_div0.c",
|
|
||||||
"arm/aeabi_drsub.c",
|
|
||||||
"arm/aeabi_fcmp.S",
|
|
||||||
"arm/aeabi_frsub.c",
|
|
||||||
"arm/aeabi_idivmod.S",
|
|
||||||
"arm/aeabi_ldivmod.S",
|
|
||||||
"arm/aeabi_memcmp.S",
|
|
||||||
"arm/aeabi_memcpy.S",
|
|
||||||
"arm/aeabi_memmove.S",
|
|
||||||
"arm/aeabi_memset.S",
|
|
||||||
"arm/aeabi_uidivmod.S",
|
|
||||||
"arm/aeabi_uldivmod.S",
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompilerRT is a library with symbols required by programs compiled with LLVM.
|
|
||||||
// These symbols are for operations that cannot be emitted with a single
|
|
||||||
// instruction or a short sequence of instructions for that target.
|
|
||||||
//
|
|
||||||
// For more information, see: https://compiler-rt.llvm.org/
|
|
||||||
var CompilerRT = Library{
|
|
||||||
name: "compiler-rt",
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
|
|
||||||
},
|
|
||||||
sourceDir: "lib/compiler-rt/lib/builtins",
|
|
||||||
librarySources: func(target string) []string {
|
|
||||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
|
||||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
|
||||||
builtins = append(builtins, aeabiBuiltins...)
|
|
||||||
}
|
|
||||||
return builtins
|
|
||||||
},
|
|
||||||
}
|
|
||||||
-313
@@ -1,313 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
// This file implements a wrapper around the C compiler (Clang) which uses a
|
|
||||||
// build cache.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// compileAndCacheCFile compiles a C or assembly file using a build cache.
|
|
||||||
// Compiling the same file again (if nothing changed, including included header
|
|
||||||
// files) the output is loaded from the build cache instead.
|
|
||||||
//
|
|
||||||
// Its operation is a bit complex (more complex than Go package build caching)
|
|
||||||
// because the list of file dependencies is only known after the file is
|
|
||||||
// compiled. However, luckily compilers have a flag to write a list of file
|
|
||||||
// dependencies in Makefile syntax which can be used for caching.
|
|
||||||
//
|
|
||||||
// Because of this complexity, every file has in fact two cached build outputs:
|
|
||||||
// the file itself, and the list of dependencies. Its operation is as follows:
|
|
||||||
//
|
|
||||||
// depfile = hash(path, compiler, cflags, ...)
|
|
||||||
// if depfile exists:
|
|
||||||
// outfile = hash of all files and depfile name
|
|
||||||
// if outfile exists:
|
|
||||||
// # cache hit
|
|
||||||
// return outfile
|
|
||||||
// # cache miss
|
|
||||||
// tmpfile = compile file
|
|
||||||
// read dependencies (side effect of compile)
|
|
||||||
// write depfile
|
|
||||||
// outfile = hash of all files and depfile name
|
|
||||||
// rename tmpfile to outfile
|
|
||||||
//
|
|
||||||
// There are a few edge cases that are not handled:
|
|
||||||
// - If a file is added to an include path, that file may be included instead of
|
|
||||||
// some other file. This would be fixed by also including lookup failures in the
|
|
||||||
// dependencies file, but I'm not aware of a compiler which does that.
|
|
||||||
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
|
||||||
// details.
|
|
||||||
// - A header file may be changed to add/remove an include. This invalidates the
|
|
||||||
// depfile but without invalidating its name. For this reason, the depfile is
|
|
||||||
// written on each new compilation (even when it seems unnecessary). However, it
|
|
||||||
// could in rare cases lead to a stale file fetched from the cache.
|
|
||||||
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
|
|
||||||
// Hash input file.
|
|
||||||
fileHash, err := hashFile(abspath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create cache key for the dependencies file.
|
|
||||||
buf, err := json.Marshal(struct {
|
|
||||||
Path string
|
|
||||||
Hash string
|
|
||||||
Flags []string
|
|
||||||
LLVMVersion string
|
|
||||||
}{
|
|
||||||
Path: abspath,
|
|
||||||
Hash: fileHash,
|
|
||||||
Flags: cflags,
|
|
||||||
LLVMVersion: llvm.Version,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // shouldn't happen
|
|
||||||
}
|
|
||||||
depfileNameHashBuf := sha512.Sum512_224(buf)
|
|
||||||
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
|
|
||||||
|
|
||||||
// Load dependencies file, if possible.
|
|
||||||
depfileName := "dep-" + depfileNameHash + ".json"
|
|
||||||
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
|
||||||
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
|
|
||||||
var dependencies []string // sorted list of dependency paths
|
|
||||||
if err == nil {
|
|
||||||
// There is a dependency file, that's great!
|
|
||||||
// Parse it first.
|
|
||||||
err := json.Unmarshal(depfileBuf, &dependencies)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtain hashes of all the files listed as a dependency.
|
|
||||||
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
|
|
||||||
if err == nil {
|
|
||||||
if _, err := os.Stat(outpath); err == nil {
|
|
||||||
return outpath, nil
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if !os.IsNotExist(err) {
|
|
||||||
// expected either nil or IsNotExist
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
objTmpFile.Close()
|
|
||||||
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
depTmpFile.Close()
|
|
||||||
flags := append([]string{}, cflags...) // copy cflags
|
|
||||||
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
|
|
||||||
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
|
||||||
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
|
|
||||||
// If this is an assembly file (.s or .S, lowercase or uppercase), then
|
|
||||||
// we'll need to add -Qunused-arguments because many parameters are
|
|
||||||
// relevant to C, not assembly. And with -Werror, having meaningless
|
|
||||||
// flags (for the assembler) is a compiler error.
|
|
||||||
flags = append(flags, "-Qunused-arguments")
|
|
||||||
}
|
|
||||||
if printCommands != nil {
|
|
||||||
printCommands("clang", flags...)
|
|
||||||
}
|
|
||||||
err = runCCompiler(flags...)
|
|
||||||
if err != nil {
|
|
||||||
return "", &commandError{"failed to build", abspath, err}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create sorted and uniqued slice of dependencies.
|
|
||||||
dependencyPaths, err := readDepFile(depTmpFile.Name())
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
|
|
||||||
dependencySet := make(map[string]struct{}, len(dependencyPaths))
|
|
||||||
var dependencySlice []string
|
|
||||||
for _, path := range dependencyPaths {
|
|
||||||
if _, ok := dependencySet[path]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dependencySet[path] = struct{}{}
|
|
||||||
dependencySlice = append(dependencySlice, path)
|
|
||||||
}
|
|
||||||
sort.Strings(dependencySlice)
|
|
||||||
|
|
||||||
// Write dependencies file.
|
|
||||||
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // shouldn't happen
|
|
||||||
}
|
|
||||||
_, err = f.Write(buf)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = os.Rename(f.Name(), depfileCachePath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move temporary object file to final location.
|
|
||||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = os.Rename(objTmpFile.Name(), outpath)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return outpath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a cache path (a path in GOCACHE) to store the output of a compiler
|
|
||||||
// job. This path is based on the dep file name (which is a hash of metadata
|
|
||||||
// including compiler flags) and the hash of all input files in the paths slice.
|
|
||||||
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
|
|
||||||
// Hash all input files.
|
|
||||||
fileHashes := make(map[string]string, len(paths))
|
|
||||||
for _, path := range paths {
|
|
||||||
hash, err := hashFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
fileHashes[path] = hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate a cache key based on the above hashes.
|
|
||||||
buf, err := json.Marshal(struct {
|
|
||||||
DepfileHash string
|
|
||||||
FileHashes map[string]string
|
|
||||||
}{
|
|
||||||
DepfileHash: depfileNameHash,
|
|
||||||
FileHashes: fileHashes,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
panic(err) // shouldn't happen
|
|
||||||
}
|
|
||||||
outFileNameBuf := sha512.Sum512_224(buf)
|
|
||||||
cacheKey := hex.EncodeToString(outFileNameBuf[:])
|
|
||||||
|
|
||||||
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
|
|
||||||
return outpath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashFile hashes the given file path and returns the hash as a hex string.
|
|
||||||
func hashFile(path string) (string, error) {
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
fileHasher := sha512.New512_224()
|
|
||||||
_, err = io.Copy(fileHasher, f)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(fileHasher.Sum(nil)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
|
|
||||||
// file is assumed to have a single target named deps.
|
|
||||||
//
|
|
||||||
// There are roughly three make syntax variants:
|
|
||||||
// - BSD make, which doesn't support any escaping. This means that many special
|
|
||||||
// characters are not supported in file names.
|
|
||||||
// - GNU make, which supports escaping using a backslash but when it fails to
|
|
||||||
// find a file it tries to fall back with the literal path name (to match BSD
|
|
||||||
// make).
|
|
||||||
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
|
||||||
// any weird characters.
|
|
||||||
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
|
||||||
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
|
||||||
// is at least somewhat sane. This last format isn't perfect either: it does not
|
|
||||||
// correctly handle filenames with quote marks in them. Those are generally not
|
|
||||||
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
|
||||||
// it's the most sane of any of the formats so readDepFile will use that format.
|
|
||||||
func readDepFile(filename string) ([]string, error) {
|
|
||||||
buf, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(buf) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
return parseDepFile(string(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseDepFile(s string) ([]string, error) {
|
|
||||||
// This function makes no attempt at parsing anything other than Clang -MD
|
|
||||||
// -MV output.
|
|
||||||
|
|
||||||
// For Windows: replace CRLF with LF to make the logic below simpler.
|
|
||||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
|
||||||
|
|
||||||
// Collapse all lines ending in a backslash. These backslashes are really
|
|
||||||
// just a way to continue a line without making very long lines.
|
|
||||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
|
||||||
|
|
||||||
// Only use the first line, which is expected to begin with "deps:".
|
|
||||||
line := strings.SplitN(s, "\n", 2)[0]
|
|
||||||
if !strings.HasPrefix(line, "deps:") {
|
|
||||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
|
||||||
}
|
|
||||||
line = strings.TrimSpace(line[len("deps:"):])
|
|
||||||
|
|
||||||
var deps []string
|
|
||||||
for line != "" {
|
|
||||||
if line[0] == '"' {
|
|
||||||
// File path is quoted. Path ends with double quote.
|
|
||||||
// This does not handle double quotes in path names, which is a
|
|
||||||
// problem on non-Windows systems.
|
|
||||||
line = line[1:]
|
|
||||||
end := strings.IndexByte(line, '"')
|
|
||||||
if end < 0 {
|
|
||||||
return nil, errors.New("readDepFile: path is incorrectly quoted")
|
|
||||||
}
|
|
||||||
dep := line[:end]
|
|
||||||
line = strings.TrimSpace(line[end+1:])
|
|
||||||
deps = append(deps, dep)
|
|
||||||
} else {
|
|
||||||
// File path is not quoted. Path ends in space or EOL.
|
|
||||||
end := strings.IndexFunc(line, unicode.IsSpace)
|
|
||||||
if end < 0 {
|
|
||||||
// last dependency
|
|
||||||
deps = append(deps, line)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
dep := line[:end]
|
|
||||||
line = strings.TrimSpace(line[end:])
|
|
||||||
deps = append(deps, dep)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return deps, nil
|
|
||||||
}
|
|
||||||
@@ -1,513 +0,0 @@
|
|||||||
// +build byollvm
|
|
||||||
|
|
||||||
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
|
|
||||||
//
|
|
||||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
||||||
// See https://llvm.org/LICENSE.txt for license information.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This is the entry point to the clang -cc1as functionality, which implements
|
|
||||||
// the direct interface to the LLVM MC based assembler.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
#include "clang/Basic/Diagnostic.h"
|
|
||||||
#include "clang/Basic/DiagnosticOptions.h"
|
|
||||||
#include "clang/Driver/DriverDiagnostic.h"
|
|
||||||
#include "clang/Driver/Options.h"
|
|
||||||
#include "clang/Frontend/FrontendDiagnostic.h"
|
|
||||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
|
||||||
#include "clang/Frontend/Utils.h"
|
|
||||||
#include "llvm/ADT/STLExtras.h"
|
|
||||||
#include "llvm/ADT/StringSwitch.h"
|
|
||||||
#include "llvm/ADT/Triple.h"
|
|
||||||
#include "llvm/IR/DataLayout.h"
|
|
||||||
#include "llvm/MC/MCAsmBackend.h"
|
|
||||||
#include "llvm/MC/MCAsmInfo.h"
|
|
||||||
#include "llvm/MC/MCCodeEmitter.h"
|
|
||||||
#include "llvm/MC/MCContext.h"
|
|
||||||
#include "llvm/MC/MCInstrInfo.h"
|
|
||||||
#include "llvm/MC/MCObjectFileInfo.h"
|
|
||||||
#include "llvm/MC/MCObjectWriter.h"
|
|
||||||
#include "llvm/MC/MCParser/MCAsmParser.h"
|
|
||||||
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
|
|
||||||
#include "llvm/MC/MCRegisterInfo.h"
|
|
||||||
#include "llvm/MC/MCSectionMachO.h"
|
|
||||||
#include "llvm/MC/MCStreamer.h"
|
|
||||||
#include "llvm/MC/MCSubtargetInfo.h"
|
|
||||||
#include "llvm/MC/MCTargetOptions.h"
|
|
||||||
#include "llvm/Option/Arg.h"
|
|
||||||
#include "llvm/Option/ArgList.h"
|
|
||||||
#include "llvm/Option/OptTable.h"
|
|
||||||
#include "llvm/Support/CommandLine.h"
|
|
||||||
#include "llvm/Support/ErrorHandling.h"
|
|
||||||
#include "llvm/Support/FileSystem.h"
|
|
||||||
#include "llvm/Support/FormattedStream.h"
|
|
||||||
#include "llvm/Support/Host.h"
|
|
||||||
#include "llvm/Support/MemoryBuffer.h"
|
|
||||||
#include "llvm/Support/Path.h"
|
|
||||||
#include "llvm/Support/Process.h"
|
|
||||||
#include "llvm/Support/Signals.h"
|
|
||||||
#include "llvm/Support/SourceMgr.h"
|
|
||||||
#include "llvm/Support/TargetRegistry.h"
|
|
||||||
#include "llvm/Support/TargetSelect.h"
|
|
||||||
#include "llvm/Support/Timer.h"
|
|
||||||
#include "llvm/Support/raw_ostream.h"
|
|
||||||
#include <memory>
|
|
||||||
#include <system_error>
|
|
||||||
using namespace clang;
|
|
||||||
using namespace clang::driver;
|
|
||||||
using namespace clang::driver::options;
|
|
||||||
using namespace llvm;
|
|
||||||
using namespace llvm::opt;
|
|
||||||
|
|
||||||
#include "cc1as.h"
|
|
||||||
|
|
||||||
bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|
||||||
ArrayRef<const char *> Argv,
|
|
||||||
DiagnosticsEngine &Diags) {
|
|
||||||
bool Success = true;
|
|
||||||
|
|
||||||
// Parse the arguments.
|
|
||||||
const OptTable &OptTbl = getDriverOptTable();
|
|
||||||
|
|
||||||
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
|
|
||||||
unsigned MissingArgIndex, MissingArgCount;
|
|
||||||
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
|
||||||
IncludedFlagsBitmask);
|
|
||||||
|
|
||||||
// Check for missing argument error.
|
|
||||||
if (MissingArgCount) {
|
|
||||||
Diags.Report(diag::err_drv_missing_argument)
|
|
||||||
<< Args.getArgString(MissingArgIndex) << MissingArgCount;
|
|
||||||
Success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Issue errors on unknown arguments.
|
|
||||||
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
|
||||||
auto ArgString = A->getAsString(Args);
|
|
||||||
std::string Nearest;
|
|
||||||
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
|
||||||
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
|
||||||
else
|
|
||||||
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
|
||||||
<< ArgString << Nearest;
|
|
||||||
Success = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct the invocation.
|
|
||||||
|
|
||||||
// Target Options
|
|
||||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
|
||||||
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
|
||||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
|
||||||
|
|
||||||
// Use the default target triple if unspecified.
|
|
||||||
if (Opts.Triple.empty())
|
|
||||||
Opts.Triple = llvm::sys::getDefaultTargetTriple();
|
|
||||||
|
|
||||||
// Language Options
|
|
||||||
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
|
|
||||||
Opts.NoInitialTextSection = Args.hasArg(OPT_n);
|
|
||||||
Opts.SaveTemporaryLabels = Args.hasArg(OPT_msave_temp_labels);
|
|
||||||
// Any DebugInfoKind implies GenDwarfForAssembly.
|
|
||||||
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
|
||||||
|
|
||||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
|
|
||||||
OPT_compress_debug_sections_EQ)) {
|
|
||||||
if (A->getOption().getID() == OPT_compress_debug_sections) {
|
|
||||||
// TODO: be more clever about the compression type auto-detection
|
|
||||||
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
|
|
||||||
} else {
|
|
||||||
Opts.CompressDebugSections =
|
|
||||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
|
||||||
.Case("none", llvm::DebugCompressionType::None)
|
|
||||||
.Case("zlib", llvm::DebugCompressionType::Z)
|
|
||||||
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
|
||||||
.Default(llvm::DebugCompressionType::None);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
|
||||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
|
||||||
Opts.DwarfDebugFlags =
|
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
|
||||||
Opts.DwarfDebugProducer =
|
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
|
||||||
Opts.DebugCompilationDir =
|
|
||||||
std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir));
|
|
||||||
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
|
||||||
|
|
||||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
|
|
||||||
auto Split = StringRef(Arg).split('=');
|
|
||||||
Opts.DebugPrefixMap.insert(
|
|
||||||
{std::string(Split.first), std::string(Split.second)});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frontend Options
|
|
||||||
if (Args.hasArg(OPT_INPUT)) {
|
|
||||||
bool First = true;
|
|
||||||
for (const Arg *A : Args.filtered(OPT_INPUT)) {
|
|
||||||
if (First) {
|
|
||||||
Opts.InputFile = A->getValue();
|
|
||||||
First = false;
|
|
||||||
} else {
|
|
||||||
Diags.Report(diag::err_drv_unknown_argument) << A->getAsString(Args);
|
|
||||||
Success = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
|
||||||
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
|
|
||||||
Opts.SplitDwarfOutput =
|
|
||||||
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
|
|
||||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
|
||||||
StringRef Name = A->getValue();
|
|
||||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
|
||||||
.Case("asm", FT_Asm)
|
|
||||||
.Case("null", FT_Null)
|
|
||||||
.Case("obj", FT_Obj)
|
|
||||||
.Default(~0U);
|
|
||||||
if (OutputType == ~0U) {
|
|
||||||
Diags.Report(diag::err_drv_invalid_value) << A->getAsString(Args) << Name;
|
|
||||||
Success = false;
|
|
||||||
} else
|
|
||||||
Opts.OutputType = FileType(OutputType);
|
|
||||||
}
|
|
||||||
Opts.ShowHelp = Args.hasArg(OPT_help);
|
|
||||||
Opts.ShowVersion = Args.hasArg(OPT_version);
|
|
||||||
|
|
||||||
// Transliterate Options
|
|
||||||
Opts.OutputAsmVariant =
|
|
||||||
getLastArgIntValue(Args, OPT_output_asm_variant, 0, Diags);
|
|
||||||
Opts.ShowEncoding = Args.hasArg(OPT_show_encoding);
|
|
||||||
Opts.ShowInst = Args.hasArg(OPT_show_inst);
|
|
||||||
|
|
||||||
// Assemble Options
|
|
||||||
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
|
|
||||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
|
||||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
|
||||||
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
|
|
||||||
Opts.RelocationModel =
|
|
||||||
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
|
|
||||||
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
|
|
||||||
Opts.IncrementalLinkerCompatible =
|
|
||||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
|
||||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
|
||||||
|
|
||||||
// EmbedBitcode Option. If -fembed-bitcode is enabled, set the flag.
|
|
||||||
// EmbedBitcode behaves the same for all embed options for assembly files.
|
|
||||||
if (auto *A = Args.getLastArg(OPT_fembed_bitcode_EQ)) {
|
|
||||||
Opts.EmbedBitcode = llvm::StringSwitch<unsigned>(A->getValue())
|
|
||||||
.Case("all", 1)
|
|
||||||
.Case("bitcode", 1)
|
|
||||||
.Case("marker", 1)
|
|
||||||
.Default(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::unique_ptr<raw_fd_ostream>
|
|
||||||
getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
|
||||||
// Make sure that the Out file gets unlinked from the disk if we get a
|
|
||||||
// SIGINT.
|
|
||||||
if (Path != "-")
|
|
||||||
sys::RemoveFileOnSignal(Path);
|
|
||||||
|
|
||||||
std::error_code EC;
|
|
||||||
auto Out = std::make_unique<raw_fd_ostream>(
|
|
||||||
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
|
|
||||||
if (EC) {
|
|
||||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Out;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
|
||||||
// Get the target specific parser.
|
|
||||||
std::string Error;
|
|
||||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
|
||||||
if (!TheTarget)
|
|
||||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
|
||||||
|
|
||||||
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
|
||||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
|
|
||||||
|
|
||||||
if (std::error_code EC = Buffer.getError()) {
|
|
||||||
Error = EC.message();
|
|
||||||
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
SourceMgr SrcMgr;
|
|
||||||
|
|
||||||
// Tell SrcMgr about this buffer, which is what the parser will pick up.
|
|
||||||
unsigned BufferIndex = SrcMgr.AddNewSourceBuffer(std::move(*Buffer), SMLoc());
|
|
||||||
|
|
||||||
// Record the location of the include directories so that the lexer can find
|
|
||||||
// it later.
|
|
||||||
SrcMgr.setIncludeDirs(Opts.IncludePaths);
|
|
||||||
|
|
||||||
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
|
|
||||||
assert(MRI && "Unable to create target register info!");
|
|
||||||
|
|
||||||
MCTargetOptions MCOptions;
|
|
||||||
std::unique_ptr<MCAsmInfo> MAI(
|
|
||||||
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
|
|
||||||
assert(MAI && "Unable to create target asm info!");
|
|
||||||
|
|
||||||
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
|
|
||||||
// may be created with a combination of default and explicit settings.
|
|
||||||
MAI->setCompressDebugSections(Opts.CompressDebugSections);
|
|
||||||
|
|
||||||
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
|
|
||||||
|
|
||||||
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
|
|
||||||
if (Opts.OutputPath.empty())
|
|
||||||
Opts.OutputPath = "-";
|
|
||||||
std::unique_ptr<raw_fd_ostream> FDOS =
|
|
||||||
getOutputStream(Opts.OutputPath, Diags, IsBinary);
|
|
||||||
if (!FDOS)
|
|
||||||
return true;
|
|
||||||
std::unique_ptr<raw_fd_ostream> DwoOS;
|
|
||||||
if (!Opts.SplitDwarfOutput.empty())
|
|
||||||
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
|
||||||
|
|
||||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
|
||||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
|
||||||
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
|
||||||
|
|
||||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
|
|
||||||
|
|
||||||
bool PIC = false;
|
|
||||||
if (Opts.RelocationModel == "static") {
|
|
||||||
PIC = false;
|
|
||||||
} else if (Opts.RelocationModel == "pic") {
|
|
||||||
PIC = true;
|
|
||||||
} else {
|
|
||||||
assert(Opts.RelocationModel == "dynamic-no-pic" &&
|
|
||||||
"Invalid PIC model!");
|
|
||||||
PIC = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
|
|
||||||
if (Opts.SaveTemporaryLabels)
|
|
||||||
Ctx.setAllowTemporaryLabels(false);
|
|
||||||
if (Opts.GenDwarfForAssembly)
|
|
||||||
Ctx.setGenDwarfForAssembly(true);
|
|
||||||
if (!Opts.DwarfDebugFlags.empty())
|
|
||||||
Ctx.setDwarfDebugFlags(StringRef(Opts.DwarfDebugFlags));
|
|
||||||
if (!Opts.DwarfDebugProducer.empty())
|
|
||||||
Ctx.setDwarfDebugProducer(StringRef(Opts.DwarfDebugProducer));
|
|
||||||
if (!Opts.DebugCompilationDir.empty())
|
|
||||||
Ctx.setCompilationDir(Opts.DebugCompilationDir);
|
|
||||||
else {
|
|
||||||
// If no compilation dir is set, try to use the current directory.
|
|
||||||
SmallString<128> CWD;
|
|
||||||
if (!sys::fs::current_path(CWD))
|
|
||||||
Ctx.setCompilationDir(CWD);
|
|
||||||
}
|
|
||||||
if (!Opts.DebugPrefixMap.empty())
|
|
||||||
for (const auto &KV : Opts.DebugPrefixMap)
|
|
||||||
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
|
||||||
if (!Opts.MainFileName.empty())
|
|
||||||
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
|
||||||
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
|
||||||
if (Opts.GenDwarfForAssembly)
|
|
||||||
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
|
||||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
|
||||||
|
|
||||||
// Build up the feature string from the target feature list.
|
|
||||||
std::string FS = llvm::join(Opts.Features, ",");
|
|
||||||
|
|
||||||
std::unique_ptr<MCStreamer> Str;
|
|
||||||
|
|
||||||
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
|
||||||
std::unique_ptr<MCSubtargetInfo> STI(
|
|
||||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
|
||||||
|
|
||||||
raw_pwrite_stream *Out = FDOS.get();
|
|
||||||
std::unique_ptr<buffer_ostream> BOS;
|
|
||||||
|
|
||||||
MCOptions.MCNoWarn = Opts.NoWarn;
|
|
||||||
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
|
|
||||||
MCOptions.ABIName = Opts.TargetABI;
|
|
||||||
|
|
||||||
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
|
|
||||||
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
|
|
||||||
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
|
|
||||||
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
|
|
||||||
|
|
||||||
std::unique_ptr<MCCodeEmitter> CE;
|
|
||||||
if (Opts.ShowEncoding)
|
|
||||||
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
|
||||||
std::unique_ptr<MCAsmBackend> MAB(
|
|
||||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
|
||||||
|
|
||||||
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
|
|
||||||
Str.reset(TheTarget->createAsmStreamer(
|
|
||||||
Ctx, std::move(FOut), /*asmverbose*/ true,
|
|
||||||
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
|
|
||||||
Opts.ShowInst));
|
|
||||||
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
|
|
||||||
Str.reset(createNullStreamer(Ctx));
|
|
||||||
} else {
|
|
||||||
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
|
|
||||||
"Invalid file type!");
|
|
||||||
if (!FDOS->supportsSeeking()) {
|
|
||||||
BOS = std::make_unique<buffer_ostream>(*FDOS);
|
|
||||||
Out = BOS.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<MCCodeEmitter> CE(
|
|
||||||
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
|
||||||
std::unique_ptr<MCAsmBackend> MAB(
|
|
||||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
|
||||||
std::unique_ptr<MCObjectWriter> OW =
|
|
||||||
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
|
|
||||||
: MAB->createObjectWriter(*Out);
|
|
||||||
|
|
||||||
Triple T(Opts.Triple);
|
|
||||||
Str.reset(TheTarget->createMCObjectStreamer(
|
|
||||||
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
|
|
||||||
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
|
|
||||||
/*DWARFMustBeAtTheEnd*/ true));
|
|
||||||
Str.get()->InitSections(Opts.NoExecStack);
|
|
||||||
}
|
|
||||||
|
|
||||||
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
|
|
||||||
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
|
||||||
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
|
|
||||||
MCObjectFileInfo::IsMachO) {
|
|
||||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
|
||||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
|
||||||
Str.get()->SwitchSection(AsmLabel);
|
|
||||||
Str.get()->emitZeros(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assembly to object compilation should leverage assembly info.
|
|
||||||
Str->setUseAssemblerInfoForParsing(true);
|
|
||||||
|
|
||||||
bool Failed = false;
|
|
||||||
|
|
||||||
std::unique_ptr<MCAsmParser> Parser(
|
|
||||||
createMCAsmParser(SrcMgr, Ctx, *Str.get(), *MAI));
|
|
||||||
|
|
||||||
// FIXME: init MCTargetOptions from sanitizer flags here.
|
|
||||||
std::unique_ptr<MCTargetAsmParser> TAP(
|
|
||||||
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
|
|
||||||
if (!TAP)
|
|
||||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
|
||||||
|
|
||||||
// Set values for symbols, if any.
|
|
||||||
for (auto &S : Opts.SymbolDefs) {
|
|
||||||
auto Pair = StringRef(S).split('=');
|
|
||||||
auto Sym = Pair.first;
|
|
||||||
auto Val = Pair.second;
|
|
||||||
int64_t Value;
|
|
||||||
// We have already error checked this in the driver.
|
|
||||||
Val.getAsInteger(0, Value);
|
|
||||||
Ctx.setSymbolValue(Parser->getStreamer(), Sym, Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Failed) {
|
|
||||||
Parser->setTargetParser(*TAP.get());
|
|
||||||
Failed = Parser->Run(Opts.NoInitialTextSection);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close Streamer first.
|
|
||||||
// It might have a reference to the output stream.
|
|
||||||
Str.reset();
|
|
||||||
// Close the output stream early.
|
|
||||||
BOS.reset();
|
|
||||||
FDOS.reset();
|
|
||||||
|
|
||||||
// Delete output file if there were errors.
|
|
||||||
if (Failed) {
|
|
||||||
if (Opts.OutputPath != "-")
|
|
||||||
sys::fs::remove(Opts.OutputPath);
|
|
||||||
if (!Opts.SplitDwarfOutput.empty() && Opts.SplitDwarfOutput != "-")
|
|
||||||
sys::fs::remove(Opts.SplitDwarfOutput);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Failed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void LLVMErrorHandler(void *UserData, const std::string &Message,
|
|
||||||
bool GenCrashDiag) {
|
|
||||||
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
|
|
||||||
|
|
||||||
Diags.Report(diag::err_fe_error_backend) << Message;
|
|
||||||
|
|
||||||
// We cannot recover from llvm errors.
|
|
||||||
sys::Process::Exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
|
||||||
// Initialize targets and assembly printers/parsers.
|
|
||||||
InitializeAllTargetInfos();
|
|
||||||
InitializeAllTargetMCs();
|
|
||||||
InitializeAllAsmParsers();
|
|
||||||
|
|
||||||
// Construct our diagnostic client.
|
|
||||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
|
||||||
TextDiagnosticPrinter *DiagClient
|
|
||||||
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
|
|
||||||
DiagClient->setPrefix("clang -cc1as");
|
|
||||||
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
|
|
||||||
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
|
|
||||||
|
|
||||||
// Set an error handler, so that any LLVM backend diagnostics go through our
|
|
||||||
// error handler.
|
|
||||||
ScopedFatalErrorHandler FatalErrorHandler
|
|
||||||
(LLVMErrorHandler, static_cast<void*>(&Diags));
|
|
||||||
|
|
||||||
// Parse the arguments.
|
|
||||||
AssemblerInvocation Asm;
|
|
||||||
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv, Diags))
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (Asm.ShowHelp) {
|
|
||||||
getDriverOptTable().PrintHelp(
|
|
||||||
llvm::outs(), "clang -cc1as [options] file...",
|
|
||||||
"Clang Integrated Assembler",
|
|
||||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
|
||||||
/*ShowAllAliases=*/false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Honor -version.
|
|
||||||
//
|
|
||||||
// FIXME: Use a better -version message?
|
|
||||||
if (Asm.ShowVersion) {
|
|
||||||
llvm::cl::PrintVersionMessage();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Honor -mllvm.
|
|
||||||
//
|
|
||||||
// FIXME: Remove this, one day.
|
|
||||||
if (!Asm.LLVMArgs.empty()) {
|
|
||||||
unsigned NumArgs = Asm.LLVMArgs.size();
|
|
||||||
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
|
|
||||||
Args[0] = "clang (LLVM option parsing)";
|
|
||||||
for (unsigned i = 0; i != NumArgs; ++i)
|
|
||||||
Args[i + 1] = Asm.LLVMArgs[i].c_str();
|
|
||||||
Args[NumArgs + 1] = nullptr;
|
|
||||||
llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the invocation, unless there were parsing errors.
|
|
||||||
bool Failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
|
||||||
|
|
||||||
// If any timers were active but haven't been destroyed yet, print their
|
|
||||||
// results now.
|
|
||||||
TimerGroup::printAll(errs());
|
|
||||||
TimerGroup::clearAll();
|
|
||||||
|
|
||||||
return !!Failed;
|
|
||||||
}
|
|
||||||
-120
@@ -1,120 +0,0 @@
|
|||||||
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
|
|
||||||
//
|
|
||||||
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
||||||
// See https://llvm.org/LICENSE.txt for license information.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
//
|
|
||||||
// This is the entry point to the clang -cc1as functionality, which implements
|
|
||||||
// the direct interface to the LLVM MC based assembler.
|
|
||||||
//
|
|
||||||
//===----------------------------------------------------------------------===//
|
|
||||||
|
|
||||||
/// Helper class for representing a single invocation of the assembler.
|
|
||||||
struct AssemblerInvocation {
|
|
||||||
/// @name Target Options
|
|
||||||
/// @{
|
|
||||||
|
|
||||||
/// The name of the target triple to assemble for.
|
|
||||||
std::string Triple;
|
|
||||||
|
|
||||||
/// If given, the name of the target CPU to determine which instructions
|
|
||||||
/// are legal.
|
|
||||||
std::string CPU;
|
|
||||||
|
|
||||||
/// The list of target specific features to enable or disable -- this should
|
|
||||||
/// be a list of strings starting with '+' or '-'.
|
|
||||||
std::vector<std::string> Features;
|
|
||||||
|
|
||||||
/// The list of symbol definitions.
|
|
||||||
std::vector<std::string> SymbolDefs;
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
/// @name Language Options
|
|
||||||
/// @{
|
|
||||||
|
|
||||||
std::vector<std::string> IncludePaths;
|
|
||||||
unsigned NoInitialTextSection : 1;
|
|
||||||
unsigned SaveTemporaryLabels : 1;
|
|
||||||
unsigned GenDwarfForAssembly : 1;
|
|
||||||
unsigned RelaxELFRelocations : 1;
|
|
||||||
unsigned DwarfVersion;
|
|
||||||
std::string DwarfDebugFlags;
|
|
||||||
std::string DwarfDebugProducer;
|
|
||||||
std::string DebugCompilationDir;
|
|
||||||
std::map<const std::string, const std::string> DebugPrefixMap;
|
|
||||||
llvm::DebugCompressionType CompressDebugSections =
|
|
||||||
llvm::DebugCompressionType::None;
|
|
||||||
std::string MainFileName;
|
|
||||||
std::string SplitDwarfOutput;
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
/// @name Frontend Options
|
|
||||||
/// @{
|
|
||||||
|
|
||||||
std::string InputFile;
|
|
||||||
std::vector<std::string> LLVMArgs;
|
|
||||||
std::string OutputPath;
|
|
||||||
enum FileType {
|
|
||||||
FT_Asm, ///< Assembly (.s) output, transliterate mode.
|
|
||||||
FT_Null, ///< No output, for timing purposes.
|
|
||||||
FT_Obj ///< Object file output.
|
|
||||||
};
|
|
||||||
FileType OutputType;
|
|
||||||
unsigned ShowHelp : 1;
|
|
||||||
unsigned ShowVersion : 1;
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
/// @name Transliterate Options
|
|
||||||
/// @{
|
|
||||||
|
|
||||||
unsigned OutputAsmVariant;
|
|
||||||
unsigned ShowEncoding : 1;
|
|
||||||
unsigned ShowInst : 1;
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
/// @name Assembler Options
|
|
||||||
/// @{
|
|
||||||
|
|
||||||
unsigned RelaxAll : 1;
|
|
||||||
unsigned NoExecStack : 1;
|
|
||||||
unsigned FatalWarnings : 1;
|
|
||||||
unsigned NoWarn : 1;
|
|
||||||
unsigned IncrementalLinkerCompatible : 1;
|
|
||||||
unsigned EmbedBitcode : 1;
|
|
||||||
|
|
||||||
/// The name of the relocation model to use.
|
|
||||||
std::string RelocationModel;
|
|
||||||
|
|
||||||
/// The ABI targeted by the backend. Specified using -target-abi. Empty
|
|
||||||
/// otherwise.
|
|
||||||
std::string TargetABI;
|
|
||||||
|
|
||||||
/// @}
|
|
||||||
|
|
||||||
public:
|
|
||||||
AssemblerInvocation() {
|
|
||||||
Triple = "";
|
|
||||||
NoInitialTextSection = 0;
|
|
||||||
InputFile = "-";
|
|
||||||
OutputPath = "-";
|
|
||||||
OutputType = FT_Asm;
|
|
||||||
OutputAsmVariant = 0;
|
|
||||||
ShowInst = 0;
|
|
||||||
ShowEncoding = 0;
|
|
||||||
RelaxAll = 0;
|
|
||||||
NoExecStack = 0;
|
|
||||||
FatalWarnings = 0;
|
|
||||||
NoWarn = 0;
|
|
||||||
IncrementalLinkerCompatible = 0;
|
|
||||||
DwarfVersion = 0;
|
|
||||||
EmbedBitcode = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool CreateFromArgs(AssemblerInvocation &Res,
|
|
||||||
ArrayRef<const char *> Argv,
|
|
||||||
DiagnosticsEngine &Diags);
|
|
||||||
};
|
|
||||||
|
|
||||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags);
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSplitDepFile(t *testing.T) {
|
|
||||||
for i, tc := range []struct {
|
|
||||||
in string
|
|
||||||
out []string
|
|
||||||
}{
|
|
||||||
{`deps: foo bar`, []string{"foo", "bar"}},
|
|
||||||
{`deps: foo "bar"`, []string{"foo", "bar"}},
|
|
||||||
{`deps: "foo" bar`, []string{"foo", "bar"}},
|
|
||||||
{`deps: "foo bar"`, []string{"foo bar"}},
|
|
||||||
{`deps: "foo bar" `, []string{"foo bar"}},
|
|
||||||
{"deps: foo\nbar", []string{"foo"}},
|
|
||||||
{"deps: foo \\\nbar", []string{"foo", "bar"}},
|
|
||||||
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
|
|
||||||
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
|
|
||||||
} {
|
|
||||||
out, err := parseDepFile(tc.in)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("test #%d failed: %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(out, tc.out) {
|
|
||||||
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
// +build byollvm
|
|
||||||
|
|
||||||
#include <clang/Basic/DiagnosticOptions.h>
|
|
||||||
#include <clang/CodeGen/CodeGenAction.h>
|
|
||||||
#include <clang/Driver/Compilation.h>
|
|
||||||
#include <clang/Driver/Driver.h>
|
|
||||||
#include <clang/Frontend/CompilerInstance.h>
|
|
||||||
#include <clang/Frontend/CompilerInvocation.h>
|
|
||||||
#include <clang/Frontend/FrontendDiagnostic.h>
|
|
||||||
#include <clang/Frontend/TextDiagnosticPrinter.h>
|
|
||||||
#include <clang/FrontendTool/Utils.h>
|
|
||||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
|
||||||
#include <llvm/Option/Option.h>
|
|
||||||
#include <llvm/Support/Host.h>
|
|
||||||
|
|
||||||
using namespace llvm;
|
|
||||||
using namespace clang;
|
|
||||||
|
|
||||||
#include "cc1as.h"
|
|
||||||
|
|
||||||
// This file provides C wrappers for the builtin tools cc1 and cc1as
|
|
||||||
// provided by Clang, and calls them as the driver would call them.
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
bool tinygo_clang_driver(int argc, char **argv) {
|
|
||||||
std::vector<const char*> args(argv, argv + argc);
|
|
||||||
|
|
||||||
// The compiler invocation needs a DiagnosticsEngine so it can report problems
|
|
||||||
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
|
|
||||||
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
|
|
||||||
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
|
|
||||||
|
|
||||||
// Create the clang driver
|
|
||||||
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
|
|
||||||
|
|
||||||
// Create the set of actions to perform
|
|
||||||
std::unique_ptr<clang::driver::Compilation> C(TheDriver.BuildCompilation(args));
|
|
||||||
if (!C) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const clang::driver::JobList &Jobs = C->getJobs();
|
|
||||||
|
|
||||||
// There may be more than one job, for example for .S files
|
|
||||||
// (preprocessor + assembler).
|
|
||||||
for (auto Cmd : Jobs) {
|
|
||||||
// Select the tool: cc1 or cc1as.
|
|
||||||
const llvm::opt::ArgStringList &CCArgs = Cmd.getArguments();
|
|
||||||
|
|
||||||
if (strcmp(*CCArgs.data(), "-cc1") == 0) {
|
|
||||||
// This is the C frontend.
|
|
||||||
// Initialize a compiler invocation object from the clang (-cc1) arguments.
|
|
||||||
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
|
|
||||||
bool success = clang::CompilerInvocation::CreateFromArgs(
|
|
||||||
Clang->getInvocation(),
|
|
||||||
CCArgs,
|
|
||||||
Diags);
|
|
||||||
if (!success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the actual diagnostics engine.
|
|
||||||
Clang->createDiagnostics();
|
|
||||||
if (!Clang->hasDiagnostics()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the frontend actions.
|
|
||||||
success = ExecuteCompilerInvocation(Clang.get());
|
|
||||||
if (!success) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
|
|
||||||
// This is the assembler frontend. Parse the arguments.
|
|
||||||
AssemblerInvocation Asm;
|
|
||||||
ArrayRef<const char *> Argv = llvm::ArrayRef<const char*>(CCArgs);
|
|
||||||
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// Execute the invocation, unless there were parsing errors.
|
|
||||||
bool failed = Diags.hasErrorOccurred() || ExecuteAssembler(Asm, Diags);
|
|
||||||
if (failed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Unknown tool, print the tool and exit.
|
|
||||||
fprintf(stderr, "unknown tool: %s\n", *CCArgs.data());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commands executed successfully.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // extern "C"
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Commands lists command alternatives for various operating systems. These
|
|
||||||
// commands may have a slightly different name across operating systems and
|
|
||||||
// distributions or may not even exist in $PATH, in which case absolute paths
|
|
||||||
// may be used.
|
|
||||||
var commands = map[string][]string{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
|
||||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
|
||||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
|
||||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
|
||||||
commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
|
|
||||||
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
|
||||||
// manually set $PATH).
|
|
||||||
if runtime.GOOS == "darwin" {
|
|
||||||
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
|
||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
|
||||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
|
||||||
}
|
|
||||||
// Add the path for when LLVM was installed with the installer from
|
|
||||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
|
||||||
// variable.
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
|
||||||
commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
|
|
||||||
}
|
|
||||||
// Add the path to LLVM installed from ports.
|
|
||||||
if runtime.GOOS == "freebsd" {
|
|
||||||
prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
|
|
||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
|
||||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupCommand looks up the executable name for a given LLVM tool such as
|
|
||||||
// clang or wasm-ld. It returns the (relative) command that can be used to
|
|
||||||
// invoke the tool or an error if it could not be found.
|
|
||||||
func LookupCommand(name string) (string, error) {
|
|
||||||
for _, cmdName := range commands[name] {
|
|
||||||
_, err := exec.LookPath(cmdName)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Unwrap(err) == exec.ErrNotFound {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return cmdName, err
|
|
||||||
}
|
|
||||||
return cmdName, nil
|
|
||||||
}
|
|
||||||
return "", errors.New("%#v: none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCommand(name string, args ...string) error {
|
|
||||||
name, err := LookupCommand(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := exec.Command(name, args...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewConfig builds a new Config object from a set of compiler options. It also
|
|
||||||
// loads some information from the environment while doing that. For example, it
|
|
||||||
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
|
||||||
// version to use.
|
|
||||||
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
|
||||||
spec, err := compileopts.LoadTarget(options)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.OpenOCDCommands != nil {
|
|
||||||
// Override the OpenOCDCommands from the target spec if specified on
|
|
||||||
// the command-line
|
|
||||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
|
||||||
}
|
|
||||||
|
|
||||||
goroot := goenv.Get("GOROOT")
|
|
||||||
if goroot == "" {
|
|
||||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
|
||||||
}
|
|
||||||
|
|
||||||
major, minor, err := goenv.GetGorootVersion(goroot)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
|
||||||
}
|
|
||||||
if major != 1 || minor < 15 || minor > 17 {
|
|
||||||
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
|
|
||||||
}
|
|
||||||
|
|
||||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
|
||||||
|
|
||||||
return &compileopts.Config{
|
|
||||||
Options: options,
|
|
||||||
Target: spec,
|
|
||||||
GoMinorVersion: minor,
|
|
||||||
ClangHeaders: clangHeaderPath,
|
|
||||||
TestConfig: options.TestConfig,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"debug/elf"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
|
|
||||||
elfFile, err := elf.Open(executable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, elf.FileHeader{}, err
|
|
||||||
}
|
|
||||||
defer elfFile.Close()
|
|
||||||
|
|
||||||
section := elfFile.Section(sectionName)
|
|
||||||
if section == nil {
|
|
||||||
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := section.Data()
|
|
||||||
|
|
||||||
return data, elfFile.FileHeader, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceElfSection(executable string, sectionName string, data []byte) error {
|
|
||||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
elfFile, err := elf.Open(executable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer elfFile.Close()
|
|
||||||
|
|
||||||
section := elfFile.Section(sectionName)
|
|
||||||
if section == nil {
|
|
||||||
return fmt.Errorf("could not find %s section", sectionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implicitly check for compressed sections
|
|
||||||
if section.Size != section.FileSize {
|
|
||||||
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only permit complete replacement of section
|
|
||||||
if section.Size != uint64(len(data)) {
|
|
||||||
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the replacement section data
|
|
||||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
|
||||||
// multiple locations, which should make it find the directory when installed in
|
|
||||||
// various ways.
|
|
||||||
func getClangHeaderPath(TINYGOROOT string) string {
|
|
||||||
// Check whether we're running from the source directory.
|
|
||||||
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
|
|
||||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether we're running from the installation directory.
|
|
||||||
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
|
|
||||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
|
|
||||||
// It looks like we are built with a system-installed LLVM. Do a last
|
|
||||||
// attempt: try to use Clang headers relative to the clang binary.
|
|
||||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
|
||||||
for _, cmdName := range commands["clang"] {
|
|
||||||
binpath, err := exec.LookPath(cmdName)
|
|
||||||
if err == nil {
|
|
||||||
// This should be the command that will also be used by
|
|
||||||
// execCommand. To avoid inconsistencies, make sure we use the
|
|
||||||
// headers relative to this command.
|
|
||||||
binpath, err = filepath.EvalSymlinks(binpath)
|
|
||||||
if err != nil {
|
|
||||||
// Unexpected.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// Example executable:
|
|
||||||
// /usr/lib/llvm-9/bin/clang
|
|
||||||
// Example include path:
|
|
||||||
// /usr/lib/llvm-9/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/
|
|
||||||
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
|
|
||||||
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
|
|
||||||
if err64 != nil && err32 != nil {
|
|
||||||
// Unexpected.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dirnames := make([]string, len(dirs64)+len(dirs32))
|
|
||||||
dirCount := 0
|
|
||||||
for _, d := range dirs32 {
|
|
||||||
name := d.Name()
|
|
||||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
|
||||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
|
|
||||||
dirCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, d := range dirs64 {
|
|
||||||
name := d.Name()
|
|
||||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
|
||||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
|
|
||||||
dirCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Strings(dirnames)
|
|
||||||
// Check for the highest version first.
|
|
||||||
for i := dirCount - 1; i >= 0; i-- {
|
|
||||||
path := filepath.Join(dirnames[i], "include")
|
|
||||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
|
||||||
if err == nil {
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Could not find it.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
// MultiError is a list of multiple errors (actually: diagnostics) returned
|
|
||||||
// during LLVM IR generation.
|
|
||||||
type MultiError struct {
|
|
||||||
Errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MultiError) Error() string {
|
|
||||||
// Return the first error, to conform to the error interface. Clients should
|
|
||||||
// really do a type-assertion on *MultiError.
|
|
||||||
return e.Errs[0].Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMultiError returns a *MultiError if there is more than one error, or
|
|
||||||
// returns that error directly when there is only one. Passing an empty slice
|
|
||||||
// will lead to a panic.
|
|
||||||
func newMultiError(errs []error) error {
|
|
||||||
switch len(errs) {
|
|
||||||
case 0:
|
|
||||||
panic("attempted to create empty MultiError")
|
|
||||||
case 1:
|
|
||||||
return errs[0]
|
|
||||||
default:
|
|
||||||
return &MultiError{errs}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// commandError is an error type to wrap os/exec.Command errors. This provides
|
|
||||||
// some more information regarding what went wrong while running a command.
|
|
||||||
type commandError struct {
|
|
||||||
Msg string
|
|
||||||
File string
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *commandError) Error() string {
|
|
||||||
return e.Msg + " " + e.File + ": " + e.Err.Error()
|
|
||||||
}
|
|
||||||
-166
@@ -1,166 +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{}
|
|
||||||
|
|
||||||
// Chip IDs. Source:
|
|
||||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
|
|
||||||
chip_id := map[string]uint16{
|
|
||||||
"esp32": 0x0000,
|
|
||||||
"esp32c3": 0x0005,
|
|
||||||
}[format]
|
|
||||||
|
|
||||||
// Image header.
|
|
||||||
switch format {
|
|
||||||
case "esp32", "esp32c3":
|
|
||||||
// Header format:
|
|
||||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
|
|
||||||
// Note: not adding a SHA256 hash as the binary is modified by
|
|
||||||
// esptool.py while flashing and therefore the hash won't be valid
|
|
||||||
// anymore.
|
|
||||||
binary.Write(outf, binary.LittleEndian, struct {
|
|
||||||
magic uint8
|
|
||||||
segment_count uint8
|
|
||||||
spi_mode uint8
|
|
||||||
spi_speed_size uint8
|
|
||||||
entry_addr uint32
|
|
||||||
wp_pin uint8
|
|
||||||
spi_pin_drv [3]uint8
|
|
||||||
chip_id uint16
|
|
||||||
min_chip_rev uint8
|
|
||||||
reserved [8]uint8
|
|
||||||
hash_appended bool
|
|
||||||
}{
|
|
||||||
magic: 0xE9,
|
|
||||||
segment_count: byte(len(segments)),
|
|
||||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
|
||||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
|
||||||
entry_addr: uint32(inf.Entry),
|
|
||||||
wp_pin: 0xEE, // disable WP pin
|
|
||||||
chip_id: chip_id,
|
|
||||||
hash_appended: true, // add a SHA256 hash
|
|
||||||
})
|
|
||||||
case "esp8266":
|
|
||||||
// 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 != "esp8266" {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
-199
@@ -1,199 +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
|
|
||||||
result string // result (path)
|
|
||||||
run func(*compileJob) (err error)
|
|
||||||
state jobState
|
|
||||||
err error // error if finished
|
|
||||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dummyCompileJob returns a new *compileJob that produces an output without
|
|
||||||
// doing anything. This can be useful where a *compileJob producing an output is
|
|
||||||
// expected but nothing needs to be done, for example for a load from a cache.
|
|
||||||
func dummyCompileJob(result string) *compileJob {
|
|
||||||
return &compileJob{
|
|
||||||
description: "<dummy>",
|
|
||||||
result: result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// readyToRun returns whether this job is ready to run: it is itself not yet
|
|
||||||
// started and all dependencies are finished.
|
|
||||||
func (job *compileJob) readyToRun() bool {
|
|
||||||
if job.state != jobStateQueued {
|
|
||||||
// Already running or finished, so shouldn't be run again.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check dependencies.
|
|
||||||
for _, dep := range job.dependencies {
|
|
||||||
if dep.state != jobStateFinished {
|
|
||||||
// A dependency is not finished, so this job has to wait until it
|
|
||||||
// is.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// All conditions are satisfied.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// runJobs runs the indicated job and all its dependencies. For every job, all
|
|
||||||
// the dependencies are run first. It returns the error of the first job that
|
|
||||||
// fails.
|
|
||||||
// It runs all jobs in the order of the dependencies slice, depth-first.
|
|
||||||
// Therefore, if some jobs are preferred to run before others, they should be
|
|
||||||
// ordered as such in the job dependencies.
|
|
||||||
func runJobs(job *compileJob, parallelism int) error {
|
|
||||||
if parallelism == 0 {
|
|
||||||
// Have a default, if the parallelism isn't set. This is useful for
|
|
||||||
// tests.
|
|
||||||
parallelism = runtime.NumCPU()
|
|
||||||
}
|
|
||||||
if parallelism < 1 {
|
|
||||||
return fmt.Errorf("-p flag must be at least 1, provided -p=%d", parallelism)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a slice of jobs to run, where all dependencies are run in order.
|
|
||||||
jobs := []*compileJob{}
|
|
||||||
addedJobs := map[*compileJob]struct{}{}
|
|
||||||
var addJobs func(*compileJob)
|
|
||||||
addJobs = func(job *compileJob) {
|
|
||||||
if _, ok := addedJobs[job]; ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, dep := range job.dependencies {
|
|
||||||
addJobs(dep)
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
addedJobs[job] = struct{}{}
|
|
||||||
}
|
|
||||||
addJobs(job)
|
|
||||||
|
|
||||||
// Create channels to communicate with the workers.
|
|
||||||
doneChan := make(chan *compileJob)
|
|
||||||
workerChan := make(chan *compileJob)
|
|
||||||
defer close(workerChan)
|
|
||||||
|
|
||||||
// Start a number of workers.
|
|
||||||
for i := 0; i < parallelism; 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 < parallelism {
|
|
||||||
jobToRun := nextJob(jobs)
|
|
||||||
if jobToRun != nil {
|
|
||||||
// Start job.
|
|
||||||
if jobRunnerDebug {
|
|
||||||
fmt.Println("## start: ", jobToRun.description)
|
|
||||||
}
|
|
||||||
jobToRun.state = jobStateRunning
|
|
||||||
workerChan <- jobToRun
|
|
||||||
numRunningJobs++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When there are no jobs running, all jobs in the jobs slice must have
|
|
||||||
// been finished. Therefore, the work is done.
|
|
||||||
if numRunningJobs == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until a job is finished.
|
|
||||||
job := <-doneChan
|
|
||||||
job.state = jobStateFinished
|
|
||||||
numRunningJobs--
|
|
||||||
totalTime += job.duration
|
|
||||||
if jobRunnerDebug {
|
|
||||||
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
|
|
||||||
}
|
|
||||||
if job.err != nil {
|
|
||||||
// Wait for running jobs to finish.
|
|
||||||
for numRunningJobs != 0 {
|
|
||||||
<-doneChan
|
|
||||||
numRunningJobs--
|
|
||||||
}
|
|
||||||
// Return error of first failing job.
|
|
||||||
return job.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Some statistics, if debugging.
|
|
||||||
if jobRunnerDebug {
|
|
||||||
// Total duration of running all jobs.
|
|
||||||
duration := time.Since(start)
|
|
||||||
fmt.Println("## total: ", duration)
|
|
||||||
|
|
||||||
// The individual time of each job combined. On a multicore system, this
|
|
||||||
// should be lower than the total above.
|
|
||||||
fmt.Println("## job sum: ", totalTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextJob returns the first ready-to-run job.
|
|
||||||
// This is an implementation detail of runJobs.
|
|
||||||
func nextJob(jobs []*compileJob) *compileJob {
|
|
||||||
for _, job := range jobs {
|
|
||||||
if job.readyToRun() {
|
|
||||||
return job
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// jobWorker is the goroutine that runs received jobs.
|
|
||||||
// This is an implementation detail of runJobs.
|
|
||||||
func jobWorker(workerChan, doneChan chan *compileJob) {
|
|
||||||
for job := range workerChan {
|
|
||||||
start := time.Now()
|
|
||||||
if job.run != nil {
|
|
||||||
err := job.run(job)
|
|
||||||
if err != nil {
|
|
||||||
job.err = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
job.duration = time.Since(start)
|
|
||||||
doneChan <- job
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,211 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Library is a container for information about a single C library, such as a
|
|
||||||
// compiler runtime or libc.
|
|
||||||
type Library struct {
|
|
||||||
// The library name, such as compiler-rt or picolibc.
|
|
||||||
name string
|
|
||||||
|
|
||||||
// makeHeaders creates a header include dir for the library
|
|
||||||
makeHeaders func(target, includeDir string) error
|
|
||||||
|
|
||||||
// cflags returns the C flags specific to this library
|
|
||||||
cflags func(target, headerPath string) []string
|
|
||||||
|
|
||||||
// The source directory, relative to TINYGOROOT.
|
|
||||||
sourceDir string
|
|
||||||
|
|
||||||
// The source files, relative to sourceDir.
|
|
||||||
librarySources func(target string) []string
|
|
||||||
|
|
||||||
// The source code for the crt1.o file, relative to sourceDir.
|
|
||||||
crt1Source string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the library archive, possibly generating and caching it if needed.
|
|
||||||
// The resulting directory may be stored in the provided tmpdir, which is
|
|
||||||
// expected to be removed after the Load call.
|
|
||||||
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) {
|
|
||||||
job, err := l.load(config, tmpdir)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
err = runJobs(job, config.Options.Parallelism)
|
|
||||||
return filepath.Dir(job.result), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// load returns a compile job to build this library file for the given target
|
|
||||||
// and CPU. It may return a dummy compileJob if the library build is already
|
|
||||||
// cached. The path is stored as job.result but is only valid after the job has
|
|
||||||
// been run.
|
|
||||||
// The provided tmpdir will be used to store intermediary files and possibly the
|
|
||||||
// output archive file, it is expected to be removed after use.
|
|
||||||
// As a side effect, this call creates the library header files if they didn't
|
|
||||||
// exist yet.
|
|
||||||
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, err error) {
|
|
||||||
outdir, precompiled := config.LibcPath(l.name)
|
|
||||||
archiveFilePath := filepath.Join(outdir, "lib.a")
|
|
||||||
if precompiled {
|
|
||||||
// Found a precompiled library for this OS/architecture. Return the path
|
|
||||||
// directly.
|
|
||||||
return dummyCompileJob(archiveFilePath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to fetch this library from the cache.
|
|
||||||
if _, err := os.Stat(archiveFilePath); err == nil {
|
|
||||||
return dummyCompileJob(archiveFilePath), nil
|
|
||||||
}
|
|
||||||
// Cache miss, build it now.
|
|
||||||
|
|
||||||
// Create the destination directory where the components of this library
|
|
||||||
// (lib.a file, include directory) are placed.
|
|
||||||
outname := filepath.Base(outdir)
|
|
||||||
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
|
|
||||||
if err != nil {
|
|
||||||
// Could not create directory (and not because it already exists).
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make headers if needed.
|
|
||||||
headerPath := filepath.Join(outdir, "include")
|
|
||||||
target := config.Triple()
|
|
||||||
if l.makeHeaders != nil {
|
|
||||||
if _, err = os.Stat(headerPath); err != nil {
|
|
||||||
temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(temporaryHeaderPath)
|
|
||||||
err = l.makeHeaders(target, temporaryHeaderPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = os.Rename(temporaryHeaderPath, headerPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
|
||||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
|
||||||
err = os.Mkdir(dir, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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(target, headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
|
||||||
cpu := config.CPU()
|
|
||||||
if cpu != "" {
|
|
||||||
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
|
|
||||||
// However, ARM has not done this.
|
|
||||||
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
|
|
||||||
args = append(args, "-march="+cpu)
|
|
||||||
} else {
|
|
||||||
args = append(args, "-mcpu="+cpu)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
|
||||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(target, "riscv32-") {
|
|
||||||
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
|
|
||||||
// file is the (static) library file.
|
|
||||||
var objs []string
|
|
||||||
job = &compileJob{
|
|
||||||
description: "ar " + l.name + "/lib.a",
|
|
||||||
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
|
|
||||||
run: func(*compileJob) error {
|
|
||||||
// Create an archive of all object files.
|
|
||||||
f, err := ioutil.TempFile(outdir, "libc.a.tmp*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = makeArchive(f, objs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Store this archive in the cache.
|
|
||||||
return os.Rename(f.Name(), archiveFilePath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create jobs to compile all sources. These jobs are depended upon by the
|
|
||||||
// archive job above, so must be run first.
|
|
||||||
for _, path := range l.librarySources(target) {
|
|
||||||
// Strip leading "../" parts off the path.
|
|
||||||
cleanpath := path
|
|
||||||
for strings.HasPrefix(cleanpath, "../") {
|
|
||||||
cleanpath = cleanpath[3:]
|
|
||||||
}
|
|
||||||
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, path)
|
|
||||||
objpath := filepath.Join(dir, cleanpath+".o")
|
|
||||||
os.MkdirAll(filepath.Dir(objpath), 0o777)
|
|
||||||
objs = append(objs, objpath)
|
|
||||||
job.dependencies = append(job.dependencies, &compileJob{
|
|
||||||
description: "compile " + srcpath,
|
|
||||||
run: func(*compileJob) error {
|
|
||||||
var compileArgs []string
|
|
||||||
compileArgs = append(compileArgs, args...)
|
|
||||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
|
||||||
err := runCCompiler(compileArgs...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", srcpath, err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create crt1.o job, if needed.
|
|
||||||
// Add this as a (fake) dependency to the ar file so it gets compiled.
|
|
||||||
// (It could be done in parallel with creating the ar file, but it probably
|
|
||||||
// won't make much of a difference in speed).
|
|
||||||
if l.crt1Source != "" {
|
|
||||||
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, l.crt1Source)
|
|
||||||
job.dependencies = append(job.dependencies, &compileJob{
|
|
||||||
description: "compile " + srcpath,
|
|
||||||
run: func(*compileJob) error {
|
|
||||||
var compileArgs []string
|
|
||||||
compileArgs = append(compileArgs, args...)
|
|
||||||
tmpfile, err := ioutil.TempFile(outdir, "crt1.o.tmp*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpfile.Close()
|
|
||||||
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
|
|
||||||
err = runCCompiler(compileArgs...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", srcpath, err}
|
|
||||||
}
|
|
||||||
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return job, nil
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var MinGW = Library{
|
|
||||||
name: "mingw-w64",
|
|
||||||
makeHeaders: func(target, includeDir string) error {
|
|
||||||
// copy _mingw.h
|
|
||||||
srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
|
|
||||||
outf, err := os.Create(includeDir + "/_mingw.h")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outf.Close()
|
|
||||||
inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(outf, inf)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
// No flags necessary because there are no files to compile.
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
librarySources: func(target string) []string {
|
|
||||||
// We only use the UCRT DLL file. No source files necessary.
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
|
|
||||||
// libraries. This is done by converting input .def files to .lib files which
|
|
||||||
// can then be linked as usual.
|
|
||||||
//
|
|
||||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
|
||||||
// compile these files.
|
|
||||||
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
|
||||||
var jobs []*compileJob
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
|
|
||||||
// .lib file. But to simplify things, we're going to leave them as separate
|
|
||||||
// files.
|
|
||||||
for _, name := range []string{
|
|
||||||
"kernel32.def.in",
|
|
||||||
"api-ms-win-crt-conio-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-convert-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-environment-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-filesystem-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-heap-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-locale-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-math-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-multibyte-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-private-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-process-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-runtime-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-stdio-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-string-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-time-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-utility-l1-1-0.def",
|
|
||||||
} {
|
|
||||||
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
|
|
||||||
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
|
|
||||||
job := &compileJob{
|
|
||||||
description: "create lib file " + inpath,
|
|
||||||
result: outpath,
|
|
||||||
run: func(job *compileJob) error {
|
|
||||||
defpath := inpath
|
|
||||||
if strings.HasSuffix(inpath, ".in") {
|
|
||||||
// .in files need to be preprocessed by a preprocessor (-E)
|
|
||||||
// first.
|
|
||||||
defpath = outpath + ".def"
|
|
||||||
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", "-DDEF_X64", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
}
|
|
||||||
return jobs
|
|
||||||
}
|
|
||||||
-162
@@ -1,162 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Musl = Library{
|
|
||||||
name: "musl",
|
|
||||||
makeHeaders: func(target, includeDir string) error {
|
|
||||||
bits := filepath.Join(includeDir, "bits")
|
|
||||||
err := os.Mkdir(bits, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
|
|
||||||
|
|
||||||
// Create the file alltypes.h.
|
|
||||||
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
infiles := []string{
|
|
||||||
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
|
|
||||||
filepath.Join(muslDir, "include", "alltypes.h.in"),
|
|
||||||
}
|
|
||||||
for _, infile := range infiles {
|
|
||||||
data, err := ioutil.ReadFile(infile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(line, "TYPEDEF ") {
|
|
||||||
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
|
|
||||||
value := matches[1]
|
|
||||||
name := matches[2]
|
|
||||||
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "STRUCT ") {
|
|
||||||
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
|
|
||||||
name := matches[1]
|
|
||||||
value := matches[2]
|
|
||||||
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
|
|
||||||
}
|
|
||||||
f.WriteString(line + "\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
// Create the file syscall.h.
|
|
||||||
f, err = os.Create(filepath.Join(bits, "syscall.h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
|
|
||||||
return []string{
|
|
||||||
"-std=c99", // same as in musl
|
|
||||||
"-D_XOPEN_SOURCE=700", // same as in musl
|
|
||||||
// Musl triggers some warnings and we don't want to show any
|
|
||||||
// warnings while compiling (only errors or silence), so disable
|
|
||||||
// specific warnings that are triggered in musl.
|
|
||||||
"-Werror",
|
|
||||||
"-Wno-logical-op-parentheses",
|
|
||||||
"-Wno-bitwise-op-parentheses",
|
|
||||||
"-Wno-shift-op-parentheses",
|
|
||||||
"-Wno-ignored-attributes",
|
|
||||||
"-Wno-string-plus-int",
|
|
||||||
"-Qunused-arguments",
|
|
||||||
// Select include dirs. Don't include standard library includes
|
|
||||||
// (that would introduce host dependencies and other complications),
|
|
||||||
// but do include all the include directories expected by musl.
|
|
||||||
"-nostdlibinc",
|
|
||||||
"-I" + muslDir + "/arch/" + arch,
|
|
||||||
"-I" + muslDir + "/arch/generic",
|
|
||||||
"-I" + muslDir + "/src/include",
|
|
||||||
"-I" + muslDir + "/src/internal",
|
|
||||||
"-I" + headerPath,
|
|
||||||
"-I" + muslDir + "/include",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sourceDir: "lib/musl/src",
|
|
||||||
librarySources: func(target string) []string {
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
globs := []string{
|
|
||||||
"env/*.c",
|
|
||||||
"errno/*.c",
|
|
||||||
"exit/*.c",
|
|
||||||
"internal/defsysinfo.c",
|
|
||||||
"internal/libc.c",
|
|
||||||
"internal/syscall_ret.c",
|
|
||||||
"internal/vdso.c",
|
|
||||||
"malloc/*.c",
|
|
||||||
"mman/*.c",
|
|
||||||
"signal/*.c",
|
|
||||||
"stdio/*.c",
|
|
||||||
"string/*.c",
|
|
||||||
"thread/" + arch + "/*.s",
|
|
||||||
"thread/" + arch + "/*.c",
|
|
||||||
"thread/*.c",
|
|
||||||
"time/*.c",
|
|
||||||
"unistd/*.c",
|
|
||||||
}
|
|
||||||
|
|
||||||
var sources []string
|
|
||||||
seenSources := map[string]struct{}{}
|
|
||||||
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
|
|
||||||
for _, pattern := range globs {
|
|
||||||
matches, err := filepath.Glob(basepath + pattern)
|
|
||||||
if err != nil {
|
|
||||||
// From the documentation:
|
|
||||||
// > Glob ignores file system errors such as I/O errors reading
|
|
||||||
// > directories. The only possible returned error is
|
|
||||||
// > ErrBadPattern, when pattern is malformed.
|
|
||||||
// So the only possible error is when the (statically defined)
|
|
||||||
// pattern is wrong. In other words, a programming bug.
|
|
||||||
panic("could not glob source dirs: " + err.Error())
|
|
||||||
}
|
|
||||||
for _, match := range matches {
|
|
||||||
relpath, err := filepath.Rel(basepath, match)
|
|
||||||
if err != nil {
|
|
||||||
// Not sure if this is even possible.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
// Make sure architecture specific files override generic files.
|
|
||||||
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
|
|
||||||
if _, ok := seenSources[id]; ok {
|
|
||||||
// Already seen this file, skipping this (generic) file.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenSources[id] = struct{}{}
|
|
||||||
sources = append(sources, relpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources
|
|
||||||
},
|
|
||||||
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
)
|
|
||||||
|
|
||||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
|
|
||||||
|
|
||||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
|
||||||
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
|
|
||||||
|
|
||||||
if options.PrintCommands != nil {
|
|
||||||
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
|
||||||
cmd.Stdout = ioutil.Discard
|
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,245 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Picolibc is a C library for bare metal embedded devices. It was originally
|
|
||||||
// based on newlib.
|
|
||||||
var Picolibc = Library{
|
|
||||||
name: "picolibc",
|
|
||||||
makeHeaders: func(target, includeDir string) error {
|
|
||||||
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.Close()
|
|
||||||
},
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
|
|
||||||
return []string{
|
|
||||||
"-Werror",
|
|
||||||
"-Wall",
|
|
||||||
"-std=gnu11",
|
|
||||||
"-D_COMPILING_NEWLIB",
|
|
||||||
"-DHAVE_ALIAS_ATTRIBUTE",
|
|
||||||
"-DTINY_STDIO",
|
|
||||||
"-nostdlibinc",
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include",
|
|
||||||
"-I" + picolibcDir + "/tinystdio",
|
|
||||||
"-I" + headerPath,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sourceDir: "lib/picolibc/newlib/libc",
|
|
||||||
librarySources: func(target string) []string {
|
|
||||||
return picolibcSources
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var picolibcSources = []string{
|
|
||||||
"../../../picolibc-stdio.c",
|
|
||||||
|
|
||||||
"tinystdio/asprintf.c",
|
|
||||||
"tinystdio/atod_engine.c",
|
|
||||||
"tinystdio/atod_ryu.c",
|
|
||||||
"tinystdio/atof_engine.c",
|
|
||||||
"tinystdio/atof_ryu.c",
|
|
||||||
//"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
|
|
||||||
"tinystdio/clearerr.c",
|
|
||||||
"tinystdio/compare_exchange.c",
|
|
||||||
"tinystdio/dtoa_data.c",
|
|
||||||
"tinystdio/dtoa_engine.c",
|
|
||||||
"tinystdio/dtoa_ryu.c",
|
|
||||||
"tinystdio/ecvtbuf.c",
|
|
||||||
"tinystdio/ecvt.c",
|
|
||||||
"tinystdio/ecvt_data.c",
|
|
||||||
"tinystdio/ecvtfbuf.c",
|
|
||||||
"tinystdio/ecvtf.c",
|
|
||||||
"tinystdio/ecvtf_data.c",
|
|
||||||
"tinystdio/exchange.c",
|
|
||||||
//"tinystdio/fclose.c", // posix-io
|
|
||||||
"tinystdio/fcvtbuf.c",
|
|
||||||
"tinystdio/fcvt.c",
|
|
||||||
"tinystdio/fcvtfbuf.c",
|
|
||||||
"tinystdio/fcvtf.c",
|
|
||||||
"tinystdio/fdevopen.c",
|
|
||||||
//"tinystdio/fdopen.c", // posix-io
|
|
||||||
"tinystdio/feof.c",
|
|
||||||
"tinystdio/ferror.c",
|
|
||||||
"tinystdio/fflush.c",
|
|
||||||
"tinystdio/fgetc.c",
|
|
||||||
"tinystdio/fgets.c",
|
|
||||||
"tinystdio/fileno.c",
|
|
||||||
"tinystdio/filestrget.c",
|
|
||||||
"tinystdio/filestrputalloc.c",
|
|
||||||
"tinystdio/filestrput.c",
|
|
||||||
//"tinystdio/fopen.c", // posix-io
|
|
||||||
"tinystdio/fprintf.c",
|
|
||||||
"tinystdio/fputc.c",
|
|
||||||
"tinystdio/fputs.c",
|
|
||||||
"tinystdio/fread.c",
|
|
||||||
"tinystdio/fscanf.c",
|
|
||||||
"tinystdio/fseek.c",
|
|
||||||
"tinystdio/ftell.c",
|
|
||||||
"tinystdio/ftoa_data.c",
|
|
||||||
"tinystdio/ftoa_engine.c",
|
|
||||||
"tinystdio/ftoa_ryu.c",
|
|
||||||
"tinystdio/fwrite.c",
|
|
||||||
"tinystdio/gcvtbuf.c",
|
|
||||||
"tinystdio/gcvt.c",
|
|
||||||
"tinystdio/gcvtfbuf.c",
|
|
||||||
"tinystdio/gcvtf.c",
|
|
||||||
"tinystdio/getchar.c",
|
|
||||||
"tinystdio/gets.c",
|
|
||||||
"tinystdio/matchcaseprefix.c",
|
|
||||||
"tinystdio/perror.c",
|
|
||||||
//"tinystdio/posixiob.c", // posix-io
|
|
||||||
//"tinystdio/posixio.c", // posix-io
|
|
||||||
"tinystdio/printf.c",
|
|
||||||
"tinystdio/putchar.c",
|
|
||||||
"tinystdio/puts.c",
|
|
||||||
"tinystdio/ryu_divpow2.c",
|
|
||||||
"tinystdio/ryu_log10.c",
|
|
||||||
"tinystdio/ryu_log2pow5.c",
|
|
||||||
"tinystdio/ryu_pow5bits.c",
|
|
||||||
"tinystdio/ryu_table.c",
|
|
||||||
"tinystdio/ryu_umul128.c",
|
|
||||||
"tinystdio/scanf.c",
|
|
||||||
"tinystdio/setbuf.c",
|
|
||||||
"tinystdio/setvbuf.c",
|
|
||||||
//"tinystdio/sflags.c", // posix-io
|
|
||||||
"tinystdio/snprintf.c",
|
|
||||||
"tinystdio/snprintfd.c",
|
|
||||||
"tinystdio/snprintff.c",
|
|
||||||
"tinystdio/sprintf.c",
|
|
||||||
"tinystdio/sprintfd.c",
|
|
||||||
"tinystdio/sprintff.c",
|
|
||||||
"tinystdio/sscanf.c",
|
|
||||||
"tinystdio/strfromd.c",
|
|
||||||
"tinystdio/strfromf.c",
|
|
||||||
"tinystdio/strtod.c",
|
|
||||||
"tinystdio/strtod_l.c",
|
|
||||||
"tinystdio/strtof.c",
|
|
||||||
//"tinystdio/strtold.c", // have_long_double and not long_double_equals_double
|
|
||||||
//"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
|
|
||||||
"tinystdio/ungetc.c",
|
|
||||||
"tinystdio/vasprintf.c",
|
|
||||||
"tinystdio/vfiprintf.c",
|
|
||||||
"tinystdio/vfiscanf.c",
|
|
||||||
"tinystdio/vfprintf.c",
|
|
||||||
"tinystdio/vfprintff.c",
|
|
||||||
"tinystdio/vfscanf.c",
|
|
||||||
"tinystdio/vfscanff.c",
|
|
||||||
"tinystdio/vprintf.c",
|
|
||||||
"tinystdio/vscanf.c",
|
|
||||||
"tinystdio/vsnprintf.c",
|
|
||||||
"tinystdio/vsprintf.c",
|
|
||||||
"tinystdio/vsscanf.c",
|
|
||||||
|
|
||||||
"string/bcmp.c",
|
|
||||||
"string/bcopy.c",
|
|
||||||
"string/bzero.c",
|
|
||||||
"string/explicit_bzero.c",
|
|
||||||
"string/ffsl.c",
|
|
||||||
"string/ffsll.c",
|
|
||||||
"string/fls.c",
|
|
||||||
"string/flsl.c",
|
|
||||||
"string/flsll.c",
|
|
||||||
"string/gnu_basename.c",
|
|
||||||
"string/index.c",
|
|
||||||
"string/memccpy.c",
|
|
||||||
"string/memchr.c",
|
|
||||||
"string/memcmp.c",
|
|
||||||
"string/memcpy.c",
|
|
||||||
"string/memmem.c",
|
|
||||||
"string/memmove.c",
|
|
||||||
"string/mempcpy.c",
|
|
||||||
"string/memrchr.c",
|
|
||||||
"string/memset.c",
|
|
||||||
"string/rawmemchr.c",
|
|
||||||
"string/rindex.c",
|
|
||||||
"string/stpcpy.c",
|
|
||||||
"string/stpncpy.c",
|
|
||||||
"string/strcasecmp.c",
|
|
||||||
"string/strcasecmp_l.c",
|
|
||||||
"string/strcasestr.c",
|
|
||||||
"string/strcat.c",
|
|
||||||
"string/strchr.c",
|
|
||||||
"string/strchrnul.c",
|
|
||||||
"string/strcmp.c",
|
|
||||||
"string/strcoll.c",
|
|
||||||
"string/strcoll_l.c",
|
|
||||||
"string/strcpy.c",
|
|
||||||
"string/strcspn.c",
|
|
||||||
"string/strdup.c",
|
|
||||||
"string/strerror.c",
|
|
||||||
"string/strerror_r.c",
|
|
||||||
"string/strlcat.c",
|
|
||||||
"string/strlcpy.c",
|
|
||||||
"string/strlen.c",
|
|
||||||
"string/strlwr.c",
|
|
||||||
"string/strncasecmp.c",
|
|
||||||
"string/strncasecmp_l.c",
|
|
||||||
"string/strncat.c",
|
|
||||||
"string/strncmp.c",
|
|
||||||
"string/strncpy.c",
|
|
||||||
"string/strndup.c",
|
|
||||||
"string/strnlen.c",
|
|
||||||
"string/strnstr.c",
|
|
||||||
"string/strpbrk.c",
|
|
||||||
"string/strrchr.c",
|
|
||||||
"string/strsep.c",
|
|
||||||
"string/strsignal.c",
|
|
||||||
"string/strspn.c",
|
|
||||||
"string/strstr.c",
|
|
||||||
"string/strtok.c",
|
|
||||||
"string/strtok_r.c",
|
|
||||||
"string/strupr.c",
|
|
||||||
"string/strverscmp.c",
|
|
||||||
"string/strxfrm.c",
|
|
||||||
"string/strxfrm_l.c",
|
|
||||||
"string/swab.c",
|
|
||||||
"string/timingsafe_bcmp.c",
|
|
||||||
"string/timingsafe_memcmp.c",
|
|
||||||
"string/u_strerr.c",
|
|
||||||
"string/wcpcpy.c",
|
|
||||||
"string/wcpncpy.c",
|
|
||||||
"string/wcscasecmp.c",
|
|
||||||
"string/wcscasecmp_l.c",
|
|
||||||
"string/wcscat.c",
|
|
||||||
"string/wcschr.c",
|
|
||||||
"string/wcscmp.c",
|
|
||||||
"string/wcscoll.c",
|
|
||||||
"string/wcscoll_l.c",
|
|
||||||
"string/wcscpy.c",
|
|
||||||
"string/wcscspn.c",
|
|
||||||
"string/wcsdup.c",
|
|
||||||
"string/wcslcat.c",
|
|
||||||
"string/wcslcpy.c",
|
|
||||||
"string/wcslen.c",
|
|
||||||
"string/wcsncasecmp.c",
|
|
||||||
"string/wcsncasecmp_l.c",
|
|
||||||
"string/wcsncat.c",
|
|
||||||
"string/wcsncmp.c",
|
|
||||||
"string/wcsncpy.c",
|
|
||||||
"string/wcsnlen.c",
|
|
||||||
"string/wcspbrk.c",
|
|
||||||
"string/wcsrchr.c",
|
|
||||||
"string/wcsspn.c",
|
|
||||||
"string/wcsstr.c",
|
|
||||||
"string/wcstok.c",
|
|
||||||
"string/wcswidth.c",
|
|
||||||
"string/wcsxfrm.c",
|
|
||||||
"string/wcsxfrm_l.c",
|
|
||||||
"string/wcwidth.c",
|
|
||||||
"string/wmemchr.c",
|
|
||||||
"string/wmemcmp.c",
|
|
||||||
"string/wmemcpy.c",
|
|
||||||
"string/wmemmove.c",
|
|
||||||
"string/wmempcpy.c",
|
|
||||||
"string/wmemset.c",
|
|
||||||
"string/xpg_strerror_r.c",
|
|
||||||
}
|
|
||||||
@@ -1,595 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"debug/dwarf"
|
|
||||||
"debug/elf"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/aykevl/go-wasm"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set to true to print extra debug logs.
|
|
||||||
const sizesDebug = false
|
|
||||||
|
|
||||||
// programSize contains size statistics per package of a compiled program.
|
|
||||||
type programSize struct {
|
|
||||||
Packages map[string]packageSize
|
|
||||||
Code uint64
|
|
||||||
ROData uint64
|
|
||||||
Data uint64
|
|
||||||
BSS uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// sortedPackageNames returns the list of package names (ProgramSize.Packages)
|
|
||||||
// sorted alphabetically.
|
|
||||||
func (ps *programSize) sortedPackageNames() []string {
|
|
||||||
names := make([]string, 0, len(ps.Packages))
|
|
||||||
for name := range ps.Packages {
|
|
||||||
names = append(names, name)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flash usage in regular microcontrollers.
|
|
||||||
func (ps *programSize) Flash() uint64 {
|
|
||||||
return ps.Code + ps.ROData + ps.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static RAM usage in regular microcontrollers.
|
|
||||||
func (ps *programSize) RAM() uint64 {
|
|
||||||
return ps.Data + ps.BSS
|
|
||||||
}
|
|
||||||
|
|
||||||
// packageSize contains the size of a package, calculated from the linked object
|
|
||||||
// file.
|
|
||||||
type packageSize struct {
|
|
||||||
Code uint64
|
|
||||||
ROData uint64
|
|
||||||
Data uint64
|
|
||||||
BSS uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flash usage in regular microcontrollers.
|
|
||||||
func (ps *packageSize) Flash() uint64 {
|
|
||||||
return ps.Code + ps.ROData + ps.Data
|
|
||||||
}
|
|
||||||
|
|
||||||
// Static RAM usage in regular microcontrollers.
|
|
||||||
func (ps *packageSize) RAM() uint64 {
|
|
||||||
return ps.Data + ps.BSS
|
|
||||||
}
|
|
||||||
|
|
||||||
// A mapping of a single chunk of code or data to a file path.
|
|
||||||
type addressLine struct {
|
|
||||||
Address uint64
|
|
||||||
Length uint64 // length of this chunk
|
|
||||||
File string // file path as stored in DWARF
|
|
||||||
IsVariable bool // true if this is a variable (or constant), false if it is code
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sections defined in the input file. This struct defines them in a
|
|
||||||
// filetype-agnostic way but roughly follow the ELF types (.text, .data, .bss,
|
|
||||||
// etc).
|
|
||||||
type memorySection struct {
|
|
||||||
Type memoryType
|
|
||||||
Address uint64
|
|
||||||
Size uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type memoryType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
memoryCode memoryType = iota + 1
|
|
||||||
memoryData
|
|
||||||
memoryROData
|
|
||||||
memoryBSS
|
|
||||||
memoryStack
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regular expressions to match particular symbol names. These are not stored as
|
|
||||||
// DWARF variables because they have no mapping to source code global variables.
|
|
||||||
var (
|
|
||||||
// Various globals that aren't a variable but nonetheless need to be stored
|
|
||||||
// somewhere:
|
|
||||||
// alloc: heap allocations during init interpretation
|
|
||||||
// pack: data created when storing a constant in an interface for example
|
|
||||||
// string: buffer behind strings
|
|
||||||
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
|
|
||||||
|
|
||||||
// Reflect sidetables. Created by the reflect lowering pass.
|
|
||||||
// See src/reflect/sidetables.go.
|
|
||||||
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// readProgramSizeFromDWARF reads the source location for each line of code and
|
|
||||||
// each variable in the program, as far as this is stored in the DWARF debug
|
|
||||||
// information.
|
|
||||||
func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset uint64) ([]addressLine, error) {
|
|
||||||
r := data.Reader()
|
|
||||||
var lines []*dwarf.LineFile
|
|
||||||
var addresses []addressLine
|
|
||||||
for {
|
|
||||||
e, err := r.Next()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if e == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
switch e.Tag {
|
|
||||||
case dwarf.TagCompileUnit:
|
|
||||||
// Found a compile unit.
|
|
||||||
// We can read the .debug_line section using it, which contains a
|
|
||||||
// mapping for most instructions to their file/line/column - even
|
|
||||||
// for inlined functions!
|
|
||||||
lr, err := data.LineReader(e)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lines = lr.Files()
|
|
||||||
var lineEntry = dwarf.LineEntry{
|
|
||||||
EndSequence: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Line tables are organized as sequences of line entries until an
|
|
||||||
// end sequence. A single line table can contain multiple such
|
|
||||||
// sequences. The last line entry is an EndSequence to indicate the
|
|
||||||
// end.
|
|
||||||
for {
|
|
||||||
// Read the next .debug_line entry.
|
|
||||||
prevLineEntry := lineEntry
|
|
||||||
err := lr.Next(&lineEntry)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
|
|
||||||
// Tombstone value. This symbol has been removed, for
|
|
||||||
// example by the --gc-sections linker flag. It is still
|
|
||||||
// here in the debug information because the linker can't
|
|
||||||
// just remove this reference.
|
|
||||||
// Read until the next EndSequence so that this sequence is
|
|
||||||
// skipped.
|
|
||||||
// For more details, see (among others):
|
|
||||||
// https://reviews.llvm.org/D84825
|
|
||||||
for {
|
|
||||||
err := lr.Next(&lineEntry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if lineEntry.EndSequence {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !prevLineEntry.EndSequence {
|
|
||||||
// The chunk describes the code from prevLineEntry to
|
|
||||||
// lineEntry.
|
|
||||||
line := addressLine{
|
|
||||||
Address: prevLineEntry.Address + codeOffset,
|
|
||||||
Length: lineEntry.Address - prevLineEntry.Address,
|
|
||||||
File: prevLineEntry.File.Name,
|
|
||||||
}
|
|
||||||
if line.Length != 0 {
|
|
||||||
addresses = append(addresses, line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case dwarf.TagVariable:
|
|
||||||
// Global variable (or constant). Most of these are not actually
|
|
||||||
// stored in the binary, because they have been optimized out. Only
|
|
||||||
// the ones with a location are still present.
|
|
||||||
r.SkipChildren()
|
|
||||||
|
|
||||||
file := e.AttrField(dwarf.AttrDeclFile)
|
|
||||||
location := e.AttrField(dwarf.AttrLocation)
|
|
||||||
globalType := e.AttrField(dwarf.AttrType)
|
|
||||||
if file == nil || location == nil || globalType == nil {
|
|
||||||
// Doesn't contain the requested information.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to parse the location. While this could in theory be a very
|
|
||||||
// complex expression, usually it's just a DW_OP_addr opcode
|
|
||||||
// followed by an address.
|
|
||||||
locationCode := location.Val.([]uint8)
|
|
||||||
if locationCode[0] != 3 { // DW_OP_addr
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var addr uint64
|
|
||||||
switch len(locationCode) {
|
|
||||||
case 1 + 2:
|
|
||||||
addr = uint64(binary.LittleEndian.Uint16(locationCode[1:]))
|
|
||||||
case 1 + 4:
|
|
||||||
addr = uint64(binary.LittleEndian.Uint32(locationCode[1:]))
|
|
||||||
case 1 + 8:
|
|
||||||
addr = binary.LittleEndian.Uint64(locationCode[1:])
|
|
||||||
default:
|
|
||||||
continue // unknown address
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the type of the global variable, which (importantly)
|
|
||||||
// contains the variable size. We're not interested in the type,
|
|
||||||
// only in the size.
|
|
||||||
typ, err := data.Type(globalType.Val.(dwarf.Offset))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses = append(addresses, addressLine{
|
|
||||||
Address: addr,
|
|
||||||
Length: uint64(typ.Size()),
|
|
||||||
File: lines[file.Val.(int64)].Name,
|
|
||||||
IsVariable: true,
|
|
||||||
})
|
|
||||||
default:
|
|
||||||
r.SkipChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadProgramSize calculate a program/data size breakdown of each package for a
|
|
||||||
// given ELF file.
|
|
||||||
// If the file doesn't contain DWARF debug information, the returned program
|
|
||||||
// size will still have valid summaries but won't have complete size information
|
|
||||||
// per package.
|
|
||||||
func loadProgramSize(path string, packagePathMap map[string]string) (*programSize, error) {
|
|
||||||
// Open the binary file.
|
|
||||||
f, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
// This stores all chunks of addresses found in the binary.
|
|
||||||
var addresses []addressLine
|
|
||||||
|
|
||||||
// Load the binary file, which could be in a number of file formats.
|
|
||||||
var sections []memorySection
|
|
||||||
if file, err := elf.NewFile(f); err == nil {
|
|
||||||
// Read DWARF information. The error is intentionally ignored.
|
|
||||||
data, _ := file.DWARF()
|
|
||||||
if data != nil {
|
|
||||||
addresses, err = readProgramSizeFromDWARF(data, 0)
|
|
||||||
if err != nil {
|
|
||||||
// However, _do_ report an error here. Something must have gone
|
|
||||||
// wrong while trying to parse DWARF data.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the ELF symbols for some more chunks of location information.
|
|
||||||
// Some globals (such as strings) aren't stored in the DWARF debug
|
|
||||||
// information and therefore need to be obtained in a different way.
|
|
||||||
allSymbols, err := file.Symbols()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, symbol := range allSymbols {
|
|
||||||
symType := elf.ST_TYPE(symbol.Info)
|
|
||||||
if symbol.Size == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symbol.Section >= elf.SHN_LORESERVE {
|
|
||||||
// Not a regular section, so skip it.
|
|
||||||
// One example is elf.SHN_ABS, which is used for symbols
|
|
||||||
// declared with an absolute value such as the memset function
|
|
||||||
// on the ESP32 which is defined in the mask ROM.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
section := file.Sections[symbol.Section]
|
|
||||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
|
|
||||||
addresses = append(addresses, addressLine{
|
|
||||||
Address: symbol.Value,
|
|
||||||
Length: symbol.Size,
|
|
||||||
File: symbol.Name,
|
|
||||||
IsVariable: true,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load allocated sections.
|
|
||||||
for _, section := range file.Sections {
|
|
||||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if section.Type == elf.SHT_NOBITS {
|
|
||||||
if section.Name == ".stack" {
|
|
||||||
// TinyGo emits stack sections on microcontroller using the
|
|
||||||
// ".stack" name.
|
|
||||||
// This is a bit ugly, but I don't think there is a way to
|
|
||||||
// mark the stack section in a linker script.
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: section.Addr,
|
|
||||||
Size: section.Size,
|
|
||||||
Type: memoryStack,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Regular .bss section.
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: section.Addr,
|
|
||||||
Size: section.Size,
|
|
||||||
Type: memoryBSS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
|
|
||||||
// .text
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: section.Addr,
|
|
||||||
Size: section.Size,
|
|
||||||
Type: memoryCode,
|
|
||||||
})
|
|
||||||
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
|
|
||||||
// .data
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: section.Addr,
|
|
||||||
Size: section.Size,
|
|
||||||
Type: memoryData,
|
|
||||||
})
|
|
||||||
} else if section.Type == elf.SHT_PROGBITS {
|
|
||||||
// .rodata
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: section.Addr,
|
|
||||||
Size: section.Size,
|
|
||||||
Type: memoryROData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if file, err := wasm.Parse(f); err == nil {
|
|
||||||
// File is in WebAssembly format.
|
|
||||||
|
|
||||||
// Put code at a very high address, so that it won't conflict with the
|
|
||||||
// data in the memory section.
|
|
||||||
const codeOffset = 0x8000_0000_0000_0000
|
|
||||||
|
|
||||||
// Read DWARF information. The error is intentionally ignored.
|
|
||||||
data, err := file.DWARF()
|
|
||||||
if data != nil {
|
|
||||||
addresses, err = readProgramSizeFromDWARF(data, codeOffset)
|
|
||||||
if err != nil {
|
|
||||||
// However, _do_ report an error here. Something must have gone
|
|
||||||
// wrong while trying to parse DWARF data.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var linearMemorySize uint64
|
|
||||||
for _, section := range file.Sections {
|
|
||||||
switch section := section.(type) {
|
|
||||||
case *wasm.SectionCode:
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: codeOffset,
|
|
||||||
Size: uint64(section.Size()),
|
|
||||||
Type: memoryCode,
|
|
||||||
})
|
|
||||||
case *wasm.SectionMemory:
|
|
||||||
// This value is used when processing *wasm.SectionData (which
|
|
||||||
// always comes after *wasm.SectionMemory).
|
|
||||||
linearMemorySize = uint64(section.Entries[0].Limits.Initial) * 64 * 1024
|
|
||||||
case *wasm.SectionData:
|
|
||||||
// Data sections contain initial values for linear memory.
|
|
||||||
// First load the list of data sections, and sort them by
|
|
||||||
// address for easier processing.
|
|
||||||
var dataSections []memorySection
|
|
||||||
for _, entry := range section.Entries {
|
|
||||||
address, err := wasm.Eval(bytes.NewBuffer(entry.Offset))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse data section address: %w", err)
|
|
||||||
}
|
|
||||||
dataSections = append(dataSections, memorySection{
|
|
||||||
Address: uint64(address[0].(int32)),
|
|
||||||
Size: uint64(len(entry.Data)),
|
|
||||||
Type: memoryData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
sort.Slice(dataSections, func(i, j int) bool {
|
|
||||||
return dataSections[i].Address < dataSections[j].Address
|
|
||||||
})
|
|
||||||
|
|
||||||
// And now add all data sections for linear memory.
|
|
||||||
// Parts that are in the slice of data sections are added as
|
|
||||||
// memoryData, and parts that are not are added as memoryBSS.
|
|
||||||
addr := uint64(0)
|
|
||||||
for _, section := range dataSections {
|
|
||||||
if addr < section.Address {
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: addr,
|
|
||||||
Size: section.Address - addr,
|
|
||||||
Type: memoryBSS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if addr > section.Address {
|
|
||||||
// This might be allowed, I'm not sure.
|
|
||||||
// It certainly doesn't make a lot of sense.
|
|
||||||
return nil, fmt.Errorf("overlapping data section")
|
|
||||||
}
|
|
||||||
// addr == section.Address
|
|
||||||
sections = append(sections, section)
|
|
||||||
addr = section.Address + section.Size
|
|
||||||
}
|
|
||||||
if addr < linearMemorySize {
|
|
||||||
sections = append(sections, memorySection{
|
|
||||||
Address: addr,
|
|
||||||
Size: linearMemorySize - addr,
|
|
||||||
Type: memoryBSS,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("could not parse file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the slice of address chunks by address, so that we can iterate
|
|
||||||
// through it to calculate section sizes.
|
|
||||||
sort.Slice(addresses, func(i, j int) bool {
|
|
||||||
if addresses[i].Address == addresses[j].Address {
|
|
||||||
// Very rarely, there might be duplicate addresses.
|
|
||||||
// If that happens, sort the largest chunks first.
|
|
||||||
return addresses[i].Length > addresses[j].Length
|
|
||||||
}
|
|
||||||
return addresses[i].Address < addresses[j].Address
|
|
||||||
})
|
|
||||||
|
|
||||||
// Now finally determine the binary/RAM size usage per package by going
|
|
||||||
// through each allocated section.
|
|
||||||
sizes := make(map[string]packageSize)
|
|
||||||
for _, section := range sections {
|
|
||||||
switch section.Type {
|
|
||||||
case memoryCode:
|
|
||||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
|
||||||
field := sizes[path]
|
|
||||||
if isVariable {
|
|
||||||
field.ROData += size
|
|
||||||
} else {
|
|
||||||
field.Code += size
|
|
||||||
}
|
|
||||||
sizes[path] = field
|
|
||||||
}, packagePathMap)
|
|
||||||
case memoryROData:
|
|
||||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
|
||||||
field := sizes[path]
|
|
||||||
field.ROData += size
|
|
||||||
sizes[path] = field
|
|
||||||
}, packagePathMap)
|
|
||||||
case memoryData:
|
|
||||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
|
||||||
field := sizes[path]
|
|
||||||
field.Data += size
|
|
||||||
sizes[path] = field
|
|
||||||
}, packagePathMap)
|
|
||||||
case memoryBSS:
|
|
||||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
|
||||||
field := sizes[path]
|
|
||||||
field.BSS += size
|
|
||||||
sizes[path] = field
|
|
||||||
}, packagePathMap)
|
|
||||||
case memoryStack:
|
|
||||||
// We store the C stack as a pseudo-package.
|
|
||||||
sizes["C stack"] = packageSize{
|
|
||||||
BSS: section.Size,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ...and summarize the results.
|
|
||||||
program := &programSize{
|
|
||||||
Packages: sizes,
|
|
||||||
}
|
|
||||||
for _, pkg := range sizes {
|
|
||||||
program.Code += pkg.Code
|
|
||||||
program.ROData += pkg.ROData
|
|
||||||
program.Data += pkg.Data
|
|
||||||
program.BSS += pkg.BSS
|
|
||||||
}
|
|
||||||
return program, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readSection determines for each byte in this section to which package it
|
|
||||||
// belongs. It reports this usage through the addSize callback.
|
|
||||||
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
|
|
||||||
// The addr variable tracks at which address we are while going through this
|
|
||||||
// section. We start at the beginning.
|
|
||||||
addr := section.Address
|
|
||||||
sectionEnd := section.Address + section.Size
|
|
||||||
for _, line := range addresses {
|
|
||||||
if line.Address < section.Address || line.Address+line.Length >= sectionEnd {
|
|
||||||
// Check that this line is entirely within the section.
|
|
||||||
// Don't bother dealing with line entries that cross sections (that
|
|
||||||
// seems rather unlikely anyway).
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if addr < line.Address {
|
|
||||||
// There is a gap: there is a space between the current and the
|
|
||||||
// previous line entry.
|
|
||||||
addSize("(unknown)", line.Address-addr, false)
|
|
||||||
if sizesDebug {
|
|
||||||
fmt.Printf("%08x..%08x %4d: unknown (gap)\n", addr, line.Address, line.Address-addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if addr > line.Address+line.Length {
|
|
||||||
// The current line is already covered by a previous line entry.
|
|
||||||
// Simply skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// At this point, addr falls within the current line (probably at the
|
|
||||||
// start).
|
|
||||||
length := line.Length
|
|
||||||
if addr > line.Address {
|
|
||||||
// There is some overlap: the previous line entry already covered
|
|
||||||
// part of this line entry. So reduce the length to add to the
|
|
||||||
// remaining bit of the line entry.
|
|
||||||
length = line.Length - (addr - line.Address)
|
|
||||||
}
|
|
||||||
// Finally, mark this chunk of memory as used by the given package.
|
|
||||||
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
|
|
||||||
addr = line.Address + line.Length
|
|
||||||
}
|
|
||||||
if addr < sectionEnd {
|
|
||||||
// There is a gap at the end of the section.
|
|
||||||
addSize("(unknown)", sectionEnd-addr, false)
|
|
||||||
if sizesDebug {
|
|
||||||
fmt.Printf("%08x..%08x %4d: unknown (end)\n", addr, sectionEnd, sectionEnd-addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// findPackagePath returns the Go package (or a pseudo package) for the given
|
|
||||||
// path. It uses some heuristics, for example for some C libraries.
|
|
||||||
func findPackagePath(path string, packagePathMap map[string]string) string {
|
|
||||||
// Check whether this path is part of one of the compiled packages.
|
|
||||||
packagePath, ok := packagePathMap[filepath.Dir(path)]
|
|
||||||
if !ok {
|
|
||||||
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
|
|
||||||
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
|
|
||||||
// package, with a "C" prefix. For example: "C compiler-rt" for the
|
|
||||||
// compiler runtime library from LLVM.
|
|
||||||
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
|
|
||||||
} else if packageSymbolRegexp.MatchString(path) {
|
|
||||||
// Parse symbol names like main$alloc or runtime$string.
|
|
||||||
packagePath = path[:strings.LastIndex(path, "$")]
|
|
||||||
} else if reflectDataRegexp.MatchString(path) {
|
|
||||||
// Parse symbol names like reflect.structTypesSidetable.
|
|
||||||
packagePath = "Go reflect data"
|
|
||||||
} else if path == "<Go interface assert>" {
|
|
||||||
// Interface type assert, generated by the interface lowering pass.
|
|
||||||
packagePath = "Go interface assert"
|
|
||||||
} else if path == "<Go interface method>" {
|
|
||||||
// Interface method wrapper (switch over all concrete types),
|
|
||||||
// generated by the interface lowering pass.
|
|
||||||
packagePath = "Go interface method"
|
|
||||||
} else if path == "<stdin>" {
|
|
||||||
// This can happen when the source code (in Go) doesn't have a
|
|
||||||
// source file and uses "-" as the location. Somewhere this is
|
|
||||||
// converted to "<stdin>".
|
|
||||||
// Convert this back to the "-" string. Eventually, this should be
|
|
||||||
// fixed in the compiler.
|
|
||||||
packagePath = "-"
|
|
||||||
} else {
|
|
||||||
// This is some other path. Not sure what it is, so just emit its directory.
|
|
||||||
packagePath = filepath.Dir(path) // fallback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return packagePath
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
// +build byollvm
|
|
||||||
|
|
||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CXXFLAGS: -fno-rtti
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
bool tinygo_clang_driver(int argc, char **argv);
|
|
||||||
bool tinygo_link_elf(int argc, char **argv);
|
|
||||||
bool tinygo_link_mingw(int argc, char **argv);
|
|
||||||
bool tinygo_link_wasm(int argc, char **argv);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
const hasBuiltinTools = true
|
|
||||||
|
|
||||||
// RunTool runs the given tool (such as clang).
|
|
||||||
//
|
|
||||||
// This version actually runs the tools because TinyGo was compiled while
|
|
||||||
// linking statically with LLVM (with the byollvm build tag).
|
|
||||||
func RunTool(tool string, args ...string) error {
|
|
||||||
linker := "elf"
|
|
||||||
if tool == "ld.lld" && len(args) >= 2 && args[0] == "-m" && args[1] == "i386pep" {
|
|
||||||
linker = "mingw"
|
|
||||||
}
|
|
||||||
args = append([]string{"tinygo:" + tool}, args...)
|
|
||||||
|
|
||||||
var cflag *C.char
|
|
||||||
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
|
|
||||||
defer C.free(buf)
|
|
||||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
|
|
||||||
for i, flag := range args {
|
|
||||||
cflag := C.CString(flag)
|
|
||||||
cflags[i] = cflag
|
|
||||||
defer C.free(unsafe.Pointer(cflag))
|
|
||||||
}
|
|
||||||
|
|
||||||
var ok C.bool
|
|
||||||
switch tool {
|
|
||||||
case "clang":
|
|
||||||
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
|
|
||||||
case "ld.lld":
|
|
||||||
switch linker {
|
|
||||||
case "elf":
|
|
||||||
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
|
||||||
case "mingw":
|
|
||||||
ok = C.tinygo_link_mingw(C.int(len(args)), (**C.char)(buf))
|
|
||||||
default:
|
|
||||||
return errors.New("unknown linker: " + linker)
|
|
||||||
}
|
|
||||||
case "wasm-ld":
|
|
||||||
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
|
|
||||||
default:
|
|
||||||
return errors.New("unknown tool: " + tool)
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return errors.New("failed to run tool: " + tool)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
// +build !byollvm
|
|
||||||
|
|
||||||
package builder
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
const hasBuiltinTools = false
|
|
||||||
|
|
||||||
// RunTool runs the given tool (such as clang).
|
|
||||||
//
|
|
||||||
// This version doesn't actually run the tool: TinyGo has not been compiled by
|
|
||||||
// statically linking to LLVM.
|
|
||||||
func RunTool(tool string, args ...string) error {
|
|
||||||
return errors.New("cannot run tool: " + tool)
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// runCCompiler invokes a C compiler with the given arguments.
|
|
||||||
func runCCompiler(flags ...string) error {
|
|
||||||
if hasBuiltinTools {
|
|
||||||
// Compile this with the internal Clang compiler.
|
|
||||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
|
||||||
if headerPath == "" {
|
|
||||||
return errors.New("could not locate Clang headers")
|
|
||||||
}
|
|
||||||
flags = append(flags, "-I"+headerPath)
|
|
||||||
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile this with an external invocation of the Clang compiler.
|
|
||||||
return execCommand("clang", flags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// link invokes a linker with the given name and flags.
|
|
||||||
func link(linker string, flags ...string) error {
|
|
||||||
if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
|
|
||||||
// Run command with internal linker.
|
|
||||||
cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to external command.
|
|
||||||
if _, ok := commands[linker]; ok {
|
|
||||||
return execCommand(linker, flags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := exec.Command(linker, flags...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
+294
@@ -0,0 +1,294 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/blakesmith/ar"
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||||
|
var genericBuiltins = []string{
|
||||||
|
"absvdi2.c",
|
||||||
|
"absvsi2.c",
|
||||||
|
"absvti2.c",
|
||||||
|
"adddf3.c",
|
||||||
|
"addsf3.c",
|
||||||
|
"addtf3.c",
|
||||||
|
"addvdi3.c",
|
||||||
|
"addvsi3.c",
|
||||||
|
"addvti3.c",
|
||||||
|
"apple_versioning.c",
|
||||||
|
"ashldi3.c",
|
||||||
|
"ashlti3.c",
|
||||||
|
"ashrdi3.c",
|
||||||
|
"ashrti3.c",
|
||||||
|
"bswapdi2.c",
|
||||||
|
"bswapsi2.c",
|
||||||
|
"clzdi2.c",
|
||||||
|
"clzsi2.c",
|
||||||
|
"clzti2.c",
|
||||||
|
"cmpdi2.c",
|
||||||
|
"cmpti2.c",
|
||||||
|
"comparedf2.c",
|
||||||
|
"comparesf2.c",
|
||||||
|
"ctzdi2.c",
|
||||||
|
"ctzsi2.c",
|
||||||
|
"ctzti2.c",
|
||||||
|
"divdc3.c",
|
||||||
|
"divdf3.c",
|
||||||
|
"divdi3.c",
|
||||||
|
"divmoddi4.c",
|
||||||
|
"divmodsi4.c",
|
||||||
|
"divsc3.c",
|
||||||
|
"divsf3.c",
|
||||||
|
"divsi3.c",
|
||||||
|
"divtc3.c",
|
||||||
|
"divti3.c",
|
||||||
|
"divtf3.c",
|
||||||
|
"extendsfdf2.c",
|
||||||
|
"extendhfsf2.c",
|
||||||
|
"ffsdi2.c",
|
||||||
|
"ffssi2.c",
|
||||||
|
"ffsti2.c",
|
||||||
|
"fixdfdi.c",
|
||||||
|
"fixdfsi.c",
|
||||||
|
"fixdfti.c",
|
||||||
|
"fixsfdi.c",
|
||||||
|
"fixsfsi.c",
|
||||||
|
"fixsfti.c",
|
||||||
|
"fixunsdfdi.c",
|
||||||
|
"fixunsdfsi.c",
|
||||||
|
"fixunsdfti.c",
|
||||||
|
"fixunssfdi.c",
|
||||||
|
"fixunssfsi.c",
|
||||||
|
"fixunssfti.c",
|
||||||
|
"floatdidf.c",
|
||||||
|
"floatdisf.c",
|
||||||
|
"floatsidf.c",
|
||||||
|
"floatsisf.c",
|
||||||
|
"floattidf.c",
|
||||||
|
"floattisf.c",
|
||||||
|
"floatundidf.c",
|
||||||
|
"floatundisf.c",
|
||||||
|
"floatunsidf.c",
|
||||||
|
"floatunsisf.c",
|
||||||
|
"floatuntidf.c",
|
||||||
|
"floatuntisf.c",
|
||||||
|
//"int_util.c",
|
||||||
|
"lshrdi3.c",
|
||||||
|
"lshrti3.c",
|
||||||
|
"moddi3.c",
|
||||||
|
"modsi3.c",
|
||||||
|
"modti3.c",
|
||||||
|
"muldc3.c",
|
||||||
|
"muldf3.c",
|
||||||
|
"muldi3.c",
|
||||||
|
"mulodi4.c",
|
||||||
|
"mulosi4.c",
|
||||||
|
"muloti4.c",
|
||||||
|
"mulsc3.c",
|
||||||
|
"mulsf3.c",
|
||||||
|
"multi3.c",
|
||||||
|
"multf3.c",
|
||||||
|
"mulvdi3.c",
|
||||||
|
"mulvsi3.c",
|
||||||
|
"mulvti3.c",
|
||||||
|
"negdf2.c",
|
||||||
|
"negdi2.c",
|
||||||
|
"negsf2.c",
|
||||||
|
"negti2.c",
|
||||||
|
"negvdi2.c",
|
||||||
|
"negvsi2.c",
|
||||||
|
"negvti2.c",
|
||||||
|
"os_version_check.c",
|
||||||
|
"paritydi2.c",
|
||||||
|
"paritysi2.c",
|
||||||
|
"parityti2.c",
|
||||||
|
"popcountdi2.c",
|
||||||
|
"popcountsi2.c",
|
||||||
|
"popcountti2.c",
|
||||||
|
"powidf2.c",
|
||||||
|
"powisf2.c",
|
||||||
|
"powitf2.c",
|
||||||
|
"subdf3.c",
|
||||||
|
"subsf3.c",
|
||||||
|
"subvdi3.c",
|
||||||
|
"subvsi3.c",
|
||||||
|
"subvti3.c",
|
||||||
|
"subtf3.c",
|
||||||
|
"trampoline_setup.c",
|
||||||
|
"truncdfhf2.c",
|
||||||
|
"truncdfsf2.c",
|
||||||
|
"truncsfhf2.c",
|
||||||
|
"ucmpdi2.c",
|
||||||
|
"ucmpti2.c",
|
||||||
|
"udivdi3.c",
|
||||||
|
"udivmoddi4.c",
|
||||||
|
"udivmodsi4.c",
|
||||||
|
"udivmodti4.c",
|
||||||
|
"udivsi3.c",
|
||||||
|
"udivti3.c",
|
||||||
|
"umoddi3.c",
|
||||||
|
"umodsi3.c",
|
||||||
|
"umodti3.c",
|
||||||
|
}
|
||||||
|
|
||||||
|
var aeabiBuiltins = []string{
|
||||||
|
"arm/aeabi_cdcmp.S",
|
||||||
|
"arm/aeabi_cdcmpeq_check_nan.c",
|
||||||
|
"arm/aeabi_cfcmp.S",
|
||||||
|
"arm/aeabi_cfcmpeq_check_nan.c",
|
||||||
|
"arm/aeabi_dcmp.S",
|
||||||
|
"arm/aeabi_div0.c",
|
||||||
|
"arm/aeabi_drsub.c",
|
||||||
|
"arm/aeabi_fcmp.S",
|
||||||
|
"arm/aeabi_frsub.c",
|
||||||
|
"arm/aeabi_idivmod.S",
|
||||||
|
"arm/aeabi_ldivmod.S",
|
||||||
|
"arm/aeabi_memcmp.S",
|
||||||
|
"arm/aeabi_memcpy.S",
|
||||||
|
"arm/aeabi_memmove.S",
|
||||||
|
"arm/aeabi_memset.S",
|
||||||
|
"arm/aeabi_uidivmod.S",
|
||||||
|
"arm/aeabi_uldivmod.S",
|
||||||
|
}
|
||||||
|
|
||||||
|
func builtinFiles(target string) []string {
|
||||||
|
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||||
|
if strings.HasPrefix(target, "arm") {
|
||||||
|
builtins = append(builtins, aeabiBuiltins...)
|
||||||
|
}
|
||||||
|
return builtins
|
||||||
|
}
|
||||||
|
|
||||||
|
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||||
|
func builtinsDir() string {
|
||||||
|
return filepath.Join(sourceDir(), "lib", "compiler-rt", "lib", "builtins")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the builtins archive, possibly generating it as needed.
|
||||||
|
func loadBuiltins(target string) (path string, err error) {
|
||||||
|
// Try to load a precompiled compiler-rt library.
|
||||||
|
precompiledPath := filepath.Join(sourceDir(), "pkg", target, "compiler-rt.a")
|
||||||
|
if _, err := os.Stat(precompiledPath); err == nil {
|
||||||
|
// Found a precompiled compiler-rt for this OS/architecture. Return the
|
||||||
|
// path directly.
|
||||||
|
return precompiledPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
outfile := "librt-" + target + ".a"
|
||||||
|
builtinsDir := builtinsDir()
|
||||||
|
|
||||||
|
builtins := builtinFiles(target)
|
||||||
|
srcs := make([]string, len(builtins))
|
||||||
|
for i, name := range builtins {
|
||||||
|
srcs[i] = filepath.Join(builtinsDir, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
|
||||||
|
return path, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cachepath string
|
||||||
|
err = compileBuiltins(target, func(path string) error {
|
||||||
|
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||||
|
cachepath = path
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return cachepath, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// compileBuiltins compiles builtins from compiler-rt into a static library.
|
||||||
|
// When it succeeds, it will call the callback with the resulting path. The path
|
||||||
|
// will be removed after callback returns. If callback returns an error, this is
|
||||||
|
// passed through to the return value of this function.
|
||||||
|
func compileBuiltins(target string, callback func(path string) error) error {
|
||||||
|
builtinsDir := builtinsDir()
|
||||||
|
|
||||||
|
builtins := builtinFiles(target)
|
||||||
|
srcs := make([]string, len(builtins))
|
||||||
|
for i, name := range builtins {
|
||||||
|
srcs[i] = filepath.Join(builtinsDir, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
dirPrefix := "tinygo-builtins"
|
||||||
|
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
|
// Compile all builtins.
|
||||||
|
// TODO: use builtins optimized for a given target if available.
|
||||||
|
objs := make([]string, 0, len(builtins))
|
||||||
|
for _, name := range builtins {
|
||||||
|
objname := name
|
||||||
|
if strings.LastIndexByte(objname, '/') >= 0 {
|
||||||
|
objname = objname[strings.LastIndexByte(objname, '/'):]
|
||||||
|
}
|
||||||
|
objpath := filepath.Join(dir, objname+".o")
|
||||||
|
objs = append(objs, objpath)
|
||||||
|
srcpath := filepath.Join(builtinsDir, name)
|
||||||
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||||
|
// reproducible. Otherwise the temporary directory is stored in the
|
||||||
|
// archive itself, which varies each run.
|
||||||
|
err := execCommand(commands["clang"], "-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir, "-o", objpath, srcpath)
|
||||||
|
if err != nil {
|
||||||
|
return &commandError{"failed to build", srcpath, err}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Put all builtins in an archive to link as a static library.
|
||||||
|
// Note: this does not create a symbol index, but ld.lld doesn't seem to
|
||||||
|
// care.
|
||||||
|
arpath := filepath.Join(dir, "librt.a")
|
||||||
|
arfile, err := os.Create(arpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer arfile.Close()
|
||||||
|
arwriter := ar.NewWriter(arfile)
|
||||||
|
err = arwriter.WriteGlobalHeader()
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{"write ar header", arpath, err}
|
||||||
|
}
|
||||||
|
for _, objpath := range objs {
|
||||||
|
name := filepath.Base(objpath)
|
||||||
|
objfile, err := os.Open(objpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer objfile.Close()
|
||||||
|
st, err := objfile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
arwriter.WriteHeader(&ar.Header{
|
||||||
|
Name: name,
|
||||||
|
ModTime: time.Unix(0, 0),
|
||||||
|
Uid: 0,
|
||||||
|
Gid: 0,
|
||||||
|
Mode: 0644,
|
||||||
|
Size: st.Size(),
|
||||||
|
})
|
||||||
|
n, err := io.Copy(arwriter, objfile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != st.Size() {
|
||||||
|
return errors.New("file modified during ar creation: " + arpath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the caller the resulting file. The callback must copy the file,
|
||||||
|
// because after it returns the temporary directory will be removed.
|
||||||
|
arfile.Close()
|
||||||
|
return callback(arpath)
|
||||||
|
}
|
||||||
-1424
File diff suppressed because it is too large
Load Diff
-151
@@ -1,151 +0,0 @@
|
|||||||
package cgo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/build"
|
|
||||||
"go/format"
|
|
||||||
"go/parser"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pass -update to go test to update the output of the test files.
|
|
||||||
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
|
|
||||||
|
|
||||||
// normalizeResult normalizes Go source code that comes out of tests across
|
|
||||||
// platforms and Go versions.
|
|
||||||
func normalizeResult(result string) string {
|
|
||||||
actual := strings.ReplaceAll(result, "\r\n", "\n")
|
|
||||||
return actual
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCGo(t *testing.T) {
|
|
||||||
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"}
|
|
||||||
|
|
||||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
|
||||||
name := name // avoid a race condition
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
// Skip tests that require specific Go version.
|
|
||||||
if name == "errors" {
|
|
||||||
ok := false
|
|
||||||
for _, version := range build.Default.ReleaseTags {
|
|
||||||
if version == "go1.16" {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
t.Skip("Results for errors test are only valid for Go 1.16+")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the AST in memory.
|
|
||||||
path := filepath.Join("testdata", name+".go")
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
f, err := parser.ParseFile(fset, path, nil, parser.ParseComments)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not parse Go source file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the AST with CGo.
|
|
||||||
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
|
||||||
|
|
||||||
// Check the AST for type errors.
|
|
||||||
var typecheckErrors []error
|
|
||||||
config := types.Config{
|
|
||||||
Error: func(err error) {
|
|
||||||
typecheckErrors = append(typecheckErrors, err)
|
|
||||||
},
|
|
||||||
Importer: simpleImporter{},
|
|
||||||
Sizes: types.SizesFor("gccgo", "arm"),
|
|
||||||
}
|
|
||||||
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
|
|
||||||
if err != nil && len(typecheckErrors) == 0 {
|
|
||||||
// Only report errors when no type errors are found (an
|
|
||||||
// unexpected condition).
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the (formatted) output in a buffer. Format it, so it
|
|
||||||
// becomes easier to read (and will hopefully change less with CGo
|
|
||||||
// changes).
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if len(cgoErrors) != 0 {
|
|
||||||
buf.WriteString("// CGo errors:\n")
|
|
||||||
for _, err := range cgoErrors {
|
|
||||||
buf.WriteString(formatDiagnostic(err))
|
|
||||||
}
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
if len(typecheckErrors) != 0 {
|
|
||||||
buf.WriteString("// Type checking errors after CGo processing:\n")
|
|
||||||
for _, err := range typecheckErrors {
|
|
||||||
buf.WriteString(formatDiagnostic(err))
|
|
||||||
}
|
|
||||||
buf.WriteString("\n")
|
|
||||||
}
|
|
||||||
err = format.Node(buf, fset, cgoAST)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not write out CGo AST: %v", err)
|
|
||||||
}
|
|
||||||
actual := normalizeResult(buf.String())
|
|
||||||
|
|
||||||
// Read the file with the expected output, to compare against.
|
|
||||||
outfile := filepath.Join("testdata", name+".out.go")
|
|
||||||
expectedBytes, err := ioutil.ReadFile(outfile)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not read expected output: %v", err)
|
|
||||||
}
|
|
||||||
expected := strings.ReplaceAll(string(expectedBytes), "\r\n", "\n")
|
|
||||||
|
|
||||||
// Check whether the output is as expected.
|
|
||||||
if expected != actual {
|
|
||||||
// It is not. Test failed.
|
|
||||||
if *flagUpdate {
|
|
||||||
// Update the file with the expected data.
|
|
||||||
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("could not write updated output file:", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("output did not match:\n%s", string(actual))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// simpleImporter implements the types.Importer interface, but only allows
|
|
||||||
// importing the unsafe package.
|
|
||||||
type simpleImporter struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Import implements the Importer interface. For testing usage only: it only
|
|
||||||
// supports importing the unsafe package.
|
|
||||||
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
|
||||||
switch path {
|
|
||||||
case "unsafe":
|
|
||||||
return types.Unsafe, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("importer not implemented for package %s", path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// formatDiagnostics formats the error message to be an indented comment. It
|
|
||||||
// also fixes Windows path name issues (backward slashes).
|
|
||||||
func formatDiagnostic(err error) string {
|
|
||||||
msg := err.Error()
|
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
// Fix Windows path slashes.
|
|
||||||
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
|
|
||||||
}
|
|
||||||
return "// " + msg + "\n"
|
|
||||||
}
|
|
||||||
-308
@@ -1,308 +0,0 @@
|
|||||||
package cgo
|
|
||||||
|
|
||||||
// This file implements a parser of a subset of the C language, just enough to
|
|
||||||
// parse common #define statements to Go constant expressions.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
|
|
||||||
precedences = map[token.Token]int{
|
|
||||||
token.ADD: precedenceAdd,
|
|
||||||
token.SUB: precedenceAdd,
|
|
||||||
token.MUL: precedenceMul,
|
|
||||||
token.QUO: precedenceMul,
|
|
||||||
token.REM: precedenceMul,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
precedenceLowest = iota + 1
|
|
||||||
precedenceAdd
|
|
||||||
precedenceMul
|
|
||||||
precedencePrefix
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This must be done in an init function to avoid an initialization order
|
|
||||||
// failure.
|
|
||||||
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
|
|
||||||
token.IDENT: parseIdent,
|
|
||||||
token.INT: parseBasicLit,
|
|
||||||
token.FLOAT: parseBasicLit,
|
|
||||||
token.STRING: parseBasicLit,
|
|
||||||
token.CHAR: parseBasicLit,
|
|
||||||
token.LPAREN: parseParenExpr,
|
|
||||||
token.SUB: parseUnaryExpr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseConst parses the given string as a C constant.
|
|
||||||
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
|
|
||||||
t := newTokenizer(pos, fset, value)
|
|
||||||
expr, err := parseConstExpr(t, precedenceLowest)
|
|
||||||
t.Next()
|
|
||||||
if t.curToken != token.EOF {
|
|
||||||
return nil, &scanner.Error{
|
|
||||||
Pos: t.fset.Position(t.curPos),
|
|
||||||
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return expr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseConstExpr parses a stream of C tokens to a Go expression.
|
|
||||||
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
|
|
||||||
if t.curToken == token.EOF {
|
|
||||||
return nil, &scanner.Error{
|
|
||||||
Pos: t.fset.Position(t.curPos),
|
|
||||||
Msg: "empty constant",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prefix := prefixParseFns[t.curToken]
|
|
||||||
if prefix == nil {
|
|
||||||
return nil, &scanner.Error{
|
|
||||||
Pos: t.fset.Position(t.curPos),
|
|
||||||
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
leftExpr, err := prefix(t)
|
|
||||||
|
|
||||||
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
|
|
||||||
switch t.peekToken {
|
|
||||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
|
|
||||||
t.Next()
|
|
||||||
leftExpr, err = parseBinaryExpr(t, leftExpr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return leftExpr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: t.curPos,
|
|
||||||
Name: "C." + t.curValue,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
return &ast.BasicLit{
|
|
||||||
ValuePos: t.curPos,
|
|
||||||
Kind: t.curToken,
|
|
||||||
Value: t.curValue,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
lparen := t.curPos
|
|
||||||
t.Next()
|
|
||||||
x, err := parseConstExpr(t, precedenceLowest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.Next()
|
|
||||||
if t.curToken != token.RPAREN {
|
|
||||||
return nil, unexpectedToken(t, token.RPAREN)
|
|
||||||
}
|
|
||||||
expr := &ast.ParenExpr{
|
|
||||||
Lparen: lparen,
|
|
||||||
X: x,
|
|
||||||
Rparen: t.curPos,
|
|
||||||
}
|
|
||||||
return expr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
|
|
||||||
expression := &ast.BinaryExpr{
|
|
||||||
X: left,
|
|
||||||
Op: t.curToken,
|
|
||||||
OpPos: t.curPos,
|
|
||||||
}
|
|
||||||
precedence := precedences[t.curToken]
|
|
||||||
t.Next()
|
|
||||||
right, err := parseConstExpr(t, precedence)
|
|
||||||
expression.Y = right
|
|
||||||
return expression, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
expression := &ast.UnaryExpr{
|
|
||||||
OpPos: t.curPos,
|
|
||||||
Op: t.curToken,
|
|
||||||
}
|
|
||||||
t.Next()
|
|
||||||
x, err := parseConstExpr(t, precedencePrefix)
|
|
||||||
expression.X = x
|
|
||||||
return expression, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
|
||||||
// BAR".
|
|
||||||
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
|
||||||
return &scanner.Error{
|
|
||||||
Pos: t.fset.Position(t.curPos),
|
|
||||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tokenizer reads C source code and converts it to Go tokens.
|
|
||||||
type tokenizer struct {
|
|
||||||
curPos, peekPos token.Pos
|
|
||||||
fset *token.FileSet
|
|
||||||
curToken, peekToken token.Token
|
|
||||||
curValue, peekValue string
|
|
||||||
buf string
|
|
||||||
}
|
|
||||||
|
|
||||||
// newTokenizer initializes a new tokenizer, positioned at the first token in
|
|
||||||
// the string.
|
|
||||||
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
|
|
||||||
t := &tokenizer{
|
|
||||||
peekPos: start,
|
|
||||||
fset: fset,
|
|
||||||
buf: buf,
|
|
||||||
peekToken: token.ILLEGAL,
|
|
||||||
}
|
|
||||||
// Parse the first two tokens (cur and peek).
|
|
||||||
t.Next()
|
|
||||||
t.Next()
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next consumes the next token in the stream. There is no return value, read
|
|
||||||
// the next token from the pos, token and value properties.
|
|
||||||
func (t *tokenizer) Next() {
|
|
||||||
// The previous peek is now the current token.
|
|
||||||
t.curPos = t.peekPos
|
|
||||||
t.curToken = t.peekToken
|
|
||||||
t.curValue = t.peekValue
|
|
||||||
|
|
||||||
// Parse the next peek token.
|
|
||||||
t.peekPos += token.Pos(len(t.curValue))
|
|
||||||
for {
|
|
||||||
if len(t.buf) == 0 {
|
|
||||||
t.peekToken = token.EOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c := t.buf[0]
|
|
||||||
switch {
|
|
||||||
case c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v':
|
|
||||||
// Skip whitespace.
|
|
||||||
// Based on this source, not sure whether it represents C whitespace:
|
|
||||||
// https://en.cppreference.com/w/cpp/string/byte/isspace
|
|
||||||
t.peekPos++
|
|
||||||
t.buf = t.buf[1:]
|
|
||||||
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
|
|
||||||
// Single-character tokens.
|
|
||||||
// TODO: ++ (increment) and -- (decrement) operators.
|
|
||||||
switch c {
|
|
||||||
case '(':
|
|
||||||
t.peekToken = token.LPAREN
|
|
||||||
case ')':
|
|
||||||
t.peekToken = token.RPAREN
|
|
||||||
case '+':
|
|
||||||
t.peekToken = token.ADD
|
|
||||||
case '-':
|
|
||||||
t.peekToken = token.SUB
|
|
||||||
case '*':
|
|
||||||
t.peekToken = token.MUL
|
|
||||||
case '/':
|
|
||||||
t.peekToken = token.QUO
|
|
||||||
case '%':
|
|
||||||
t.peekToken = token.REM
|
|
||||||
}
|
|
||||||
t.peekValue = t.buf[:1]
|
|
||||||
t.buf = t.buf[1:]
|
|
||||||
return
|
|
||||||
case c >= '0' && c <= '9':
|
|
||||||
// Numeric constant (int, float, etc.).
|
|
||||||
// Find the last non-numeric character.
|
|
||||||
tokenLen := len(t.buf)
|
|
||||||
hasDot := false
|
|
||||||
for i, c := range t.buf {
|
|
||||||
if c == '.' {
|
|
||||||
hasDot = true
|
|
||||||
}
|
|
||||||
if c >= '0' && c <= '9' || c == '.' || c == '_' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
|
|
||||||
tokenLen = i + 1
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.peekValue = t.buf[:tokenLen]
|
|
||||||
t.buf = t.buf[tokenLen:]
|
|
||||||
if hasDot {
|
|
||||||
// Integer constants are more complicated than this but this is
|
|
||||||
// a close approximation.
|
|
||||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
|
||||||
t.peekToken = token.FLOAT
|
|
||||||
t.peekValue = strings.TrimRight(t.peekValue, "f")
|
|
||||||
} else {
|
|
||||||
t.peekToken = token.INT
|
|
||||||
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
|
||||||
// Identifier. Find all remaining tokens that are part of this
|
|
||||||
// identifier.
|
|
||||||
tokenLen := len(t.buf)
|
|
||||||
for i, c := range t.buf {
|
|
||||||
if c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_' {
|
|
||||||
tokenLen = i + 1
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.peekValue = t.buf[:tokenLen]
|
|
||||||
t.buf = t.buf[tokenLen:]
|
|
||||||
t.peekToken = token.IDENT
|
|
||||||
return
|
|
||||||
case c == '"':
|
|
||||||
// String constant. Find the first '"' character that is not
|
|
||||||
// preceded by a backslash.
|
|
||||||
escape := false
|
|
||||||
tokenLen := len(t.buf)
|
|
||||||
for i, c := range t.buf {
|
|
||||||
if i != 0 && c == '"' && !escape {
|
|
||||||
tokenLen = i + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !escape {
|
|
||||||
escape = c == '\\'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.peekToken = token.STRING
|
|
||||||
t.peekValue = t.buf[:tokenLen]
|
|
||||||
t.buf = t.buf[tokenLen:]
|
|
||||||
return
|
|
||||||
case c == '\'':
|
|
||||||
// Char (rune) constant. Find the first '\'' character that is not
|
|
||||||
// preceded by a backslash.
|
|
||||||
escape := false
|
|
||||||
tokenLen := len(t.buf)
|
|
||||||
for i, c := range t.buf {
|
|
||||||
if i != 0 && c == '\'' && !escape {
|
|
||||||
tokenLen = i + 1
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if !escape {
|
|
||||||
escape = c == '\\'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.peekToken = token.CHAR
|
|
||||||
t.peekValue = t.buf[:tokenLen]
|
|
||||||
t.buf = t.buf[tokenLen:]
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
t.peekToken = token.ILLEGAL
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
package cgo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"go/format"
|
|
||||||
"go/token"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseConst(t *testing.T) {
|
|
||||||
// Test converting a C constant to a Go constant.
|
|
||||||
for _, tc := range []struct {
|
|
||||||
C string
|
|
||||||
Go string
|
|
||||||
}{
|
|
||||||
{`5`, `5`},
|
|
||||||
{`(5)`, `(5)`},
|
|
||||||
{`(((5)))`, `(5)`},
|
|
||||||
{`)`, `error: 1:1: unexpected token )`},
|
|
||||||
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
|
|
||||||
{" \t)", `error: 1:4: unexpected token )`},
|
|
||||||
{`5.8f`, `5.8`},
|
|
||||||
{`foo`, `C.foo`},
|
|
||||||
{``, `error: 1:1: empty constant`}, // empty constants not allowed in Go
|
|
||||||
{`"foo"`, `"foo"`},
|
|
||||||
{`"a\\n"`, `"a\\n"`},
|
|
||||||
{`"a\n"`, `"a\n"`},
|
|
||||||
{`"a\""`, `"a\""`},
|
|
||||||
{`'a'`, `'a'`},
|
|
||||||
{`0b10`, `0b10`},
|
|
||||||
{`0x1234_5678`, `0x1234_5678`},
|
|
||||||
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
|
|
||||||
// Binary operators.
|
|
||||||
{`5+5`, `5 + 5`},
|
|
||||||
{`5-5`, `5 - 5`},
|
|
||||||
{`5*5`, `5 * 5`},
|
|
||||||
{`5/5`, `5 / 5`},
|
|
||||||
{`5%5`, `5 % 5`},
|
|
||||||
{`(5/5)`, `(5 / 5)`},
|
|
||||||
{`1 - 2`, `1 - 2`},
|
|
||||||
{`1 - 2 + 3`, `1 - 2 + 3`},
|
|
||||||
{`1 - 2 * 3`, `1 - 2*3`},
|
|
||||||
{`(1 - 2) * 3`, `(1 - 2) * 3`},
|
|
||||||
{`1 * 2 - 3`, `1*2 - 3`},
|
|
||||||
{`1 * (2 - 3)`, `1 * (2 - 3)`},
|
|
||||||
// Unary operators.
|
|
||||||
{`-5`, `-5`},
|
|
||||||
{`-5-2`, `-5 - 2`},
|
|
||||||
{`5 - - 2`, `5 - -2`},
|
|
||||||
} {
|
|
||||||
fset := token.NewFileSet()
|
|
||||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
|
||||||
expr, err := parseConst(startPos, fset, tc.C)
|
|
||||||
s := "<invalid>"
|
|
||||||
if err != nil {
|
|
||||||
if !strings.HasPrefix(tc.Go, "error: ") {
|
|
||||||
t.Errorf("expected value %#v for C constant %#v but got error %#v", tc.Go, tc.C, err.Error())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s = "error: " + err.Error()
|
|
||||||
} else if expr != nil {
|
|
||||||
// Serialize the Go constant to a string, for more readable test
|
|
||||||
// cases.
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
err := format.Node(buf, fset, expr)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("could not format expr from C constant %#v: %v", tc.C, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
s = buf.String()
|
|
||||||
}
|
|
||||||
if s != tc.Go {
|
|
||||||
t.Errorf("C constant %#v was parsed to %#v while expecting %#v", tc.C, s, tc.Go)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-845
@@ -1,845 +0,0 @@
|
|||||||
package cgo
|
|
||||||
|
|
||||||
// This file parses a fragment of C with libclang and stores the result for AST
|
|
||||||
// modification. It does not touch the AST itself.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha512"
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <clang-c/Index.h> // if this fails, install libclang-11-dev
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
// This struct should be ABI-compatible on all platforms (uintptr_t has the same
|
|
||||||
// alignment etc. as void*) but does not include void* pointers that are not
|
|
||||||
// always real pointers.
|
|
||||||
// The Go garbage collector assumes that all non-nil pointer-typed integers are
|
|
||||||
// actually pointers. This is not always true, as data[1] often contains 0x1,
|
|
||||||
// which is clearly not a valid pointer. Usually the GC won't catch this issue,
|
|
||||||
// but occasionally it will leading to a crash with a vague error message.
|
|
||||||
typedef struct {
|
|
||||||
enum CXCursorKind kind;
|
|
||||||
int xdata;
|
|
||||||
uintptr_t data[3];
|
|
||||||
} GoCXCursor;
|
|
||||||
|
|
||||||
// Forwarding functions. They are implemented in libclang_stubs.c and forward to
|
|
||||||
// the real functions without doing anything else, thus they are entirely
|
|
||||||
// compatible with the versions without tinygo_ prefix. The only difference is
|
|
||||||
// the CXCursor type, which has been replaced with GoCXCursor.
|
|
||||||
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
|
|
||||||
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
|
|
||||||
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
|
|
||||||
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getCursorType(GoCXCursor c);
|
|
||||||
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
|
|
||||||
CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getCursorResultType(GoCXCursor c);
|
|
||||||
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
|
|
||||||
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
|
|
||||||
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
|
|
||||||
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
|
|
||||||
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
|
|
||||||
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
|
|
||||||
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
|
|
||||||
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
|
||||||
|
|
||||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
|
||||||
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// storedRefs stores references to types, used for clang_visitChildren.
|
|
||||||
var storedRefs refMap
|
|
||||||
|
|
||||||
var diagnosticSeverity = [...]string{
|
|
||||||
C.CXDiagnostic_Ignored: "ignored",
|
|
||||||
C.CXDiagnostic_Note: "note",
|
|
||||||
C.CXDiagnostic_Warning: "warning",
|
|
||||||
C.CXDiagnostic_Error: "error",
|
|
||||||
C.CXDiagnostic_Fatal: "fatal",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *cgoPackage) parseFragment(fragment string, cflags []string, filename string) {
|
|
||||||
index := C.clang_createIndex(0, 0)
|
|
||||||
defer C.clang_disposeIndex(index)
|
|
||||||
|
|
||||||
// pretend to be a .c file
|
|
||||||
filenameC := C.CString(filename + "!cgo.c")
|
|
||||||
defer C.free(unsafe.Pointer(filenameC))
|
|
||||||
|
|
||||||
fragmentC := C.CString(fragment)
|
|
||||||
defer C.free(unsafe.Pointer(fragmentC))
|
|
||||||
|
|
||||||
unsavedFile := C.struct_CXUnsavedFile{
|
|
||||||
Filename: filenameC,
|
|
||||||
Length: C.ulong(len(fragment)),
|
|
||||||
Contents: fragmentC,
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert Go slice of strings to C array of strings.
|
|
||||||
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
|
|
||||||
defer C.free(cmdargsC)
|
|
||||||
cmdargs := (*[1 << 16]*C.char)(cmdargsC)
|
|
||||||
for i, cflag := range cflags {
|
|
||||||
s := C.CString(cflag)
|
|
||||||
cmdargs[i] = s
|
|
||||||
defer C.free(unsafe.Pointer(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
var unit C.CXTranslationUnit
|
|
||||||
errCode := C.clang_parseTranslationUnit2(
|
|
||||||
index,
|
|
||||||
filenameC,
|
|
||||||
(**C.char)(cmdargsC), C.int(len(cflags)), // command line args
|
|
||||||
&unsavedFile, 1, // unsaved files
|
|
||||||
C.CXTranslationUnit_DetailedPreprocessingRecord,
|
|
||||||
&unit)
|
|
||||||
if errCode != 0 {
|
|
||||||
// This is probably a bug in the usage of libclang.
|
|
||||||
panic("cgo: failed to parse source with libclang")
|
|
||||||
}
|
|
||||||
defer C.clang_disposeTranslationUnit(unit)
|
|
||||||
|
|
||||||
// Report parser and type errors.
|
|
||||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
|
||||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
|
||||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
|
||||||
severity := diagnosticSeverity[C.clang_getDiagnosticSeverity(diagnostic)]
|
|
||||||
location := C.clang_getDiagnosticLocation(diagnostic)
|
|
||||||
pos := p.getClangLocationPosition(location, unit)
|
|
||||||
p.addError(pos, severity+": "+spelling)
|
|
||||||
}
|
|
||||||
for i := 0; i < numDiagnostics; i++ {
|
|
||||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
|
||||||
addDiagnostic(diagnostic)
|
|
||||||
|
|
||||||
// Child diagnostics (like notes on redefinitions).
|
|
||||||
diagnostics := C.clang_getChildDiagnostics(diagnostic)
|
|
||||||
for j := 0; j < int(C.clang_getNumDiagnosticsInSet(diagnostics)); j++ {
|
|
||||||
addDiagnostic(C.clang_getDiagnosticInSet(diagnostics, C.uint(j)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract information required by CGo.
|
|
||||||
ref := storedRefs.Put(p)
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
|
||||||
|
|
||||||
// Determine files read during CGo processing, for caching.
|
|
||||||
inclusionCallback := func(includedFile C.CXFile) {
|
|
||||||
// Get full file path.
|
|
||||||
path := getString(C.clang_getFileName(includedFile))
|
|
||||||
|
|
||||||
// Get contents of file (that should be in-memory).
|
|
||||||
size := C.size_t(0)
|
|
||||||
rawData := C.clang_getFileContents(unit, includedFile, &size)
|
|
||||||
if rawData == nil {
|
|
||||||
// Sanity check. This should (hopefully) never trigger.
|
|
||||||
panic("libclang: file contents was not loaded")
|
|
||||||
}
|
|
||||||
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
|
|
||||||
|
|
||||||
// Hash the contents if it isn't hashed yet.
|
|
||||||
if _, ok := p.visitedFiles[path]; !ok {
|
|
||||||
// already stored
|
|
||||||
sum := sha512.Sum512_224(data)
|
|
||||||
p.visitedFiles[path] = sum[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
|
|
||||||
defer storedRefs.Remove(inclusionCallbackRef)
|
|
||||||
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_globals_visitor
|
|
||||||
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
|
||||||
kind := C.tinygo_clang_getCursorKind(c)
|
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
switch kind {
|
|
||||||
case C.CXCursor_FunctionDecl:
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
if _, required := p.missingSymbols[name]; !required {
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
cursorType := C.tinygo_clang_getCursorType(c)
|
|
||||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
|
||||||
fn := &functionInfo{
|
|
||||||
pos: pos,
|
|
||||||
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
|
|
||||||
}
|
|
||||||
p.functions[name] = fn
|
|
||||||
for i := 0; i < numArgs; i++ {
|
|
||||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
|
||||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
|
||||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
|
||||||
if argName == "" {
|
|
||||||
argName = "$" + strconv.Itoa(i)
|
|
||||||
}
|
|
||||||
fn.args = append(fn.args, paramInfo{
|
|
||||||
name: argName,
|
|
||||||
typeExpr: p.makeDecayingASTType(argType, pos),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
|
||||||
if resultType.kind != C.CXType_Void {
|
|
||||||
fn.results = &ast.FieldList{
|
|
||||||
List: []*ast.Field{
|
|
||||||
{
|
|
||||||
Type: p.makeASTType(resultType, pos),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case C.CXCursor_StructDecl:
|
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
if _, required := p.missingSymbols["struct_"+name]; !required {
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
p.makeASTType(typ, pos)
|
|
||||||
case C.CXCursor_TypedefDecl:
|
|
||||||
typedefType := C.tinygo_clang_getCursorType(c)
|
|
||||||
name := getString(C.clang_getTypedefName(typedefType))
|
|
||||||
if _, required := p.missingSymbols[name]; !required {
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
p.makeASTType(typedefType, pos)
|
|
||||||
case C.CXCursor_VarDecl:
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
if _, required := p.missingSymbols[name]; !required {
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
cursorType := C.tinygo_clang_getCursorType(c)
|
|
||||||
p.globals[name] = globalInfo{
|
|
||||||
typeExpr: p.makeASTType(cursorType, pos),
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
case C.CXCursor_MacroDefinition:
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
if _, required := p.missingSymbols[name]; !required {
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
|
||||||
start := C.clang_getRangeStart(sourceRange)
|
|
||||||
end := C.clang_getRangeEnd(sourceRange)
|
|
||||||
var file, endFile C.CXFile
|
|
||||||
var startOffset, endOffset C.unsigned
|
|
||||||
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
|
|
||||||
if file == nil {
|
|
||||||
p.addError(pos, "internal error: could not find file where macro is defined")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
|
|
||||||
if file != endFile {
|
|
||||||
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if startOffset > endOffset {
|
|
||||||
p.addError(pos, "internal error: start offset of macro is after end offset")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// read file contents and extract the relevant byte range
|
|
||||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
|
||||||
var size C.size_t
|
|
||||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
|
||||||
if endOffset >= C.uint(size) {
|
|
||||||
p.addError(pos, "internal error: end offset of macro lies after end of file")
|
|
||||||
break
|
|
||||||
}
|
|
||||||
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
|
|
||||||
if !strings.HasPrefix(source, name) {
|
|
||||||
p.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
value := source[len(name):]
|
|
||||||
// Try to convert this #define into a Go constant expression.
|
|
||||||
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
|
||||||
if scannerError != nil {
|
|
||||||
p.errors = append(p.errors, *scannerError)
|
|
||||||
}
|
|
||||||
if expr != nil {
|
|
||||||
// Parsing was successful.
|
|
||||||
p.constants[name] = constantInfo{expr, pos}
|
|
||||||
}
|
|
||||||
case C.CXCursor_EnumDecl:
|
|
||||||
// Visit all enums, because the fields may be used even when the enum
|
|
||||||
// type itself is not.
|
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
|
||||||
p.makeASTType(typ, pos)
|
|
||||||
}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func getString(clangString C.CXString) (s string) {
|
|
||||||
rawString := C.clang_getCString(clangString)
|
|
||||||
s = C.GoString(rawString)
|
|
||||||
C.clang_disposeString(clangString)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCursorPosition returns a usable token.Pos from a libclang cursor.
|
|
||||||
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
|
|
||||||
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
|
|
||||||
}
|
|
||||||
|
|
||||||
// getClangLocationPosition returns a usable token.Pos based on a libclang
|
|
||||||
// location and translation unit. If the file for this cursor has not been seen
|
|
||||||
// before, it is read from libclang (which already has the file in memory) and
|
|
||||||
// added to the token.FileSet.
|
|
||||||
func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.CXTranslationUnit) token.Pos {
|
|
||||||
var file C.CXFile
|
|
||||||
var line C.unsigned
|
|
||||||
var column C.unsigned
|
|
||||||
var offset C.unsigned
|
|
||||||
C.clang_getExpansionLocation(location, &file, &line, &column, &offset)
|
|
||||||
if line == 0 || file == nil {
|
|
||||||
// Invalid token.
|
|
||||||
return token.NoPos
|
|
||||||
}
|
|
||||||
filename := getString(C.clang_getFileName(file))
|
|
||||||
if _, ok := p.tokenFiles[filename]; !ok {
|
|
||||||
// File has not been seen before in this package, add line information
|
|
||||||
// now by reading the file from libclang.
|
|
||||||
var size C.size_t
|
|
||||||
sourcePtr := C.clang_getFileContents(tu, file, &size)
|
|
||||||
source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
|
|
||||||
lines := []int{0}
|
|
||||||
for i := 0; i < len(source)-1; i++ {
|
|
||||||
if source[i] == '\n' {
|
|
||||||
lines = append(lines, i+1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f := p.fset.AddFile(filename, -1, int(size))
|
|
||||||
f.SetLines(lines)
|
|
||||||
p.tokenFiles[filename] = f
|
|
||||||
}
|
|
||||||
positionFile := p.tokenFiles[filename]
|
|
||||||
|
|
||||||
// Check for alternative line/column information (set with a line directive).
|
|
||||||
var filename2String C.CXString
|
|
||||||
var line2 C.unsigned
|
|
||||||
var column2 C.unsigned
|
|
||||||
C.clang_getPresumedLocation(location, &filename2String, &line2, &column2)
|
|
||||||
filename2 := getString(filename2String)
|
|
||||||
if filename2 != filename || line2 != line || column2 != column {
|
|
||||||
// The location was changed with a preprocessor directive.
|
|
||||||
// TODO: this only works for locations that are added in order. Adding
|
|
||||||
// line/column info to a file that already has line/column info after
|
|
||||||
// the given offset is ignored.
|
|
||||||
positionFile.AddLineColumnInfo(int(offset), filename2, int(line2), int(column2))
|
|
||||||
}
|
|
||||||
|
|
||||||
return positionFile.Pos(int(offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
// addError is a utility function to add an error to the list of errors. It will
|
|
||||||
// convert the token position to a line/column position first, and call
|
|
||||||
// addErrorAt.
|
|
||||||
func (p *cgoPackage) addError(pos token.Pos, msg string) {
|
|
||||||
p.addErrorAt(p.fset.PositionFor(pos, true), msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addErrorAfter is like addError, but adds the text `after` to the source
|
|
||||||
// location.
|
|
||||||
func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
|
|
||||||
position := p.fset.PositionFor(pos, true)
|
|
||||||
lines := strings.Split(after, "\n")
|
|
||||||
if len(lines) != 1 {
|
|
||||||
// Adjust lines.
|
|
||||||
// For why we can't just do pos+token.Pos(len(after)), see:
|
|
||||||
// https://github.com/golang/go/issues/35803
|
|
||||||
position.Line += len(lines) - 1
|
|
||||||
position.Column = len(lines[len(lines)-1]) + 1
|
|
||||||
} else {
|
|
||||||
position.Column += len(after)
|
|
||||||
}
|
|
||||||
p.addErrorAt(position, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// addErrorAt is a utility function to add an error to the list of errors.
|
|
||||||
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
|
|
||||||
if filepath.IsAbs(position.Filename) {
|
|
||||||
// Relative paths for readability, like other Go parser errors.
|
|
||||||
relpath, err := filepath.Rel(p.currentDir, position.Filename)
|
|
||||||
if err == nil {
|
|
||||||
position.Filename = relpath
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.errors = append(p.errors, scanner.Error{
|
|
||||||
Pos: position,
|
|
||||||
Msg: msg,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeDecayingASTType does the same as makeASTType but takes care of decaying
|
|
||||||
// types (arrays in function parameters, etc). It is otherwise identical to
|
|
||||||
// makeASTType.
|
|
||||||
func (p *cgoPackage) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|
||||||
// Strip typedefs, if any.
|
|
||||||
underlyingType := typ
|
|
||||||
if underlyingType.kind == C.CXType_Typedef {
|
|
||||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
|
||||||
// TODO: support a chain of typedefs. At the moment, it seems to get
|
|
||||||
// stuck in an endless loop when trying to get to the most underlying
|
|
||||||
// type.
|
|
||||||
}
|
|
||||||
// Check for decaying type. An example would be an array type in a
|
|
||||||
// parameter. This declaration:
|
|
||||||
// void foo(char buf[6]);
|
|
||||||
// is the same as this one:
|
|
||||||
// void foo(char *buf);
|
|
||||||
// But this one:
|
|
||||||
// void bar(char buf[6][4]);
|
|
||||||
// equals this:
|
|
||||||
// void bar(char *buf[4]);
|
|
||||||
// so not all array dimensions should be stripped, just the first one.
|
|
||||||
// TODO: there are more kinds of decaying types.
|
|
||||||
if underlyingType.kind == C.CXType_ConstantArray {
|
|
||||||
// Apply type decaying.
|
|
||||||
pointeeType := C.clang_getElementType(underlyingType)
|
|
||||||
return &ast.StarExpr{
|
|
||||||
Star: pos,
|
|
||||||
X: p.makeASTType(pointeeType, pos),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p.makeASTType(typ, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
|
||||||
// it converts a libclang type to a type in the Go AST.
|
|
||||||
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
|
||||||
var typeName string
|
|
||||||
switch typ.kind {
|
|
||||||
case C.CXType_Char_S, C.CXType_Char_U:
|
|
||||||
typeName = "C.char"
|
|
||||||
case C.CXType_SChar:
|
|
||||||
typeName = "C.schar"
|
|
||||||
case C.CXType_UChar:
|
|
||||||
typeName = "C.uchar"
|
|
||||||
case C.CXType_Short:
|
|
||||||
typeName = "C.short"
|
|
||||||
case C.CXType_UShort:
|
|
||||||
typeName = "C.ushort"
|
|
||||||
case C.CXType_Int:
|
|
||||||
typeName = "C.int"
|
|
||||||
case C.CXType_UInt:
|
|
||||||
typeName = "C.uint"
|
|
||||||
case C.CXType_Long:
|
|
||||||
typeName = "C.long"
|
|
||||||
case C.CXType_ULong:
|
|
||||||
typeName = "C.ulong"
|
|
||||||
case C.CXType_LongLong:
|
|
||||||
typeName = "C.longlong"
|
|
||||||
case C.CXType_ULongLong:
|
|
||||||
typeName = "C.ulonglong"
|
|
||||||
case C.CXType_Bool:
|
|
||||||
typeName = "bool"
|
|
||||||
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
|
|
||||||
switch C.clang_Type_getSizeOf(typ) {
|
|
||||||
case 4:
|
|
||||||
typeName = "float32"
|
|
||||||
case 8:
|
|
||||||
typeName = "float64"
|
|
||||||
default:
|
|
||||||
// Don't do anything, rely on the fallback code to show a somewhat
|
|
||||||
// sensible error message like "undeclared name: C.long double".
|
|
||||||
}
|
|
||||||
case C.CXType_Complex:
|
|
||||||
switch C.clang_Type_getSizeOf(typ) {
|
|
||||||
case 8:
|
|
||||||
typeName = "complex64"
|
|
||||||
case 16:
|
|
||||||
typeName = "complex128"
|
|
||||||
}
|
|
||||||
case C.CXType_Pointer:
|
|
||||||
pointeeType := C.clang_getPointeeType(typ)
|
|
||||||
if pointeeType.kind == C.CXType_Void {
|
|
||||||
// void* type is translated to Go as unsafe.Pointer
|
|
||||||
return &ast.SelectorExpr{
|
|
||||||
X: &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "unsafe",
|
|
||||||
},
|
|
||||||
Sel: &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "Pointer",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ast.StarExpr{
|
|
||||||
Star: pos,
|
|
||||||
X: p.makeASTType(pointeeType, pos),
|
|
||||||
}
|
|
||||||
case C.CXType_ConstantArray:
|
|
||||||
return &ast.ArrayType{
|
|
||||||
Lbrack: pos,
|
|
||||||
Len: &ast.BasicLit{
|
|
||||||
ValuePos: pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
|
|
||||||
},
|
|
||||||
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
|
|
||||||
}
|
|
||||||
case C.CXType_FunctionProto:
|
|
||||||
// Be compatible with gc, which uses the *[0]byte type for function
|
|
||||||
// pointer types.
|
|
||||||
// Return type [0]byte because this is a function type, not a pointer to
|
|
||||||
// this function type.
|
|
||||||
return &ast.ArrayType{
|
|
||||||
Lbrack: pos,
|
|
||||||
Len: &ast.BasicLit{
|
|
||||||
ValuePos: pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: "0",
|
|
||||||
},
|
|
||||||
Elt: &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "byte",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case C.CXType_Typedef:
|
|
||||||
name := getString(C.clang_getTypedefName(typ))
|
|
||||||
if _, ok := p.typedefs[name]; !ok {
|
|
||||||
p.typedefs[name] = nil // don't recurse
|
|
||||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
|
||||||
expr := p.makeASTType(underlyingType, pos)
|
|
||||||
if strings.HasPrefix(name, "_Cgo_") {
|
|
||||||
expr := expr.(*ast.Ident)
|
|
||||||
typeSize := C.clang_Type_getSizeOf(underlyingType)
|
|
||||||
switch expr.Name {
|
|
||||||
case "C.char":
|
|
||||||
if typeSize != 1 {
|
|
||||||
// This happens for some very special purpose architectures
|
|
||||||
// (DSPs etc.) that are not currently targeted.
|
|
||||||
// https://www.embecosm.com/2017/04/18/non-8-bit-char-support-in-clang-and-llvm/
|
|
||||||
p.addError(pos, fmt.Sprintf("unknown char width: %d", typeSize))
|
|
||||||
}
|
|
||||||
switch underlyingType.kind {
|
|
||||||
case C.CXType_Char_S:
|
|
||||||
expr.Name = "int8"
|
|
||||||
case C.CXType_Char_U:
|
|
||||||
expr.Name = "uint8"
|
|
||||||
}
|
|
||||||
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "int8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "int16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "int32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "int64"
|
|
||||||
}
|
|
||||||
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
|
|
||||||
switch typeSize {
|
|
||||||
case 1:
|
|
||||||
expr.Name = "uint8"
|
|
||||||
case 2:
|
|
||||||
expr.Name = "uint16"
|
|
||||||
case 4:
|
|
||||||
expr.Name = "uint32"
|
|
||||||
case 8:
|
|
||||||
expr.Name = "uint64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.typedefs[name] = &typedefInfo{
|
|
||||||
typeExpr: expr,
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C." + name,
|
|
||||||
}
|
|
||||||
case C.CXType_Elaborated:
|
|
||||||
underlying := C.clang_Type_getNamedType(typ)
|
|
||||||
switch underlying.kind {
|
|
||||||
case C.CXType_Record:
|
|
||||||
return p.makeASTType(underlying, pos)
|
|
||||||
case C.CXType_Enum:
|
|
||||||
return p.makeASTType(underlying, pos)
|
|
||||||
default:
|
|
||||||
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
|
|
||||||
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
|
|
||||||
typeName = "<unknown>"
|
|
||||||
}
|
|
||||||
case C.CXType_Record:
|
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
|
||||||
var cgoRecordPrefix string
|
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
|
||||||
case C.CXCursor_StructDecl:
|
|
||||||
cgoRecordPrefix = "struct_"
|
|
||||||
case C.CXCursor_UnionDecl:
|
|
||||||
cgoRecordPrefix = "union_"
|
|
||||||
default:
|
|
||||||
// makeASTRecordType will create an appropriate error.
|
|
||||||
cgoRecordPrefix = "record_"
|
|
||||||
}
|
|
||||||
if name == "" {
|
|
||||||
// Anonymous record, probably inside a typedef.
|
|
||||||
typeInfo := p.makeASTRecordType(cursor, pos)
|
|
||||||
if typeInfo.bitfields != nil || typeInfo.unionSize != 0 {
|
|
||||||
// This record is a union or is a struct with bitfields, so we
|
|
||||||
// have to declare it as a named type (for getters/setters to
|
|
||||||
// work).
|
|
||||||
p.anonStructNum++
|
|
||||||
cgoName := cgoRecordPrefix + strconv.Itoa(p.anonStructNum)
|
|
||||||
p.elaboratedTypes[cgoName] = typeInfo
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C." + cgoName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return typeInfo.typeExpr
|
|
||||||
} else {
|
|
||||||
cgoName := cgoRecordPrefix + name
|
|
||||||
if _, ok := p.elaboratedTypes[cgoName]; !ok {
|
|
||||||
p.elaboratedTypes[cgoName] = nil // predeclare (to avoid endless recursion)
|
|
||||||
p.elaboratedTypes[cgoName] = p.makeASTRecordType(cursor, pos)
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C." + cgoName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case C.CXType_Enum:
|
|
||||||
cursor := C.tinygo_clang_getTypeDeclaration(typ)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
|
|
||||||
underlying := C.tinygo_clang_getEnumDeclIntegerType(cursor)
|
|
||||||
if name == "" {
|
|
||||||
// anonymous enum
|
|
||||||
ref := storedRefs.Put(p)
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
|
||||||
return p.makeASTType(underlying, pos)
|
|
||||||
} else {
|
|
||||||
// named enum
|
|
||||||
if _, ok := p.enums[name]; !ok {
|
|
||||||
ref := storedRefs.Put(p)
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_enum_visitor), C.CXClientData(ref))
|
|
||||||
p.enums[name] = enumInfo{
|
|
||||||
typeExpr: p.makeASTType(underlying, pos),
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: "C.enum_" + name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typeName == "" {
|
|
||||||
// Report this as an error.
|
|
||||||
typeSpelling := getString(C.clang_getTypeSpelling(typ))
|
|
||||||
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
|
|
||||||
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
|
|
||||||
typeName = "C.<unknown>"
|
|
||||||
}
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: typeName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeASTRecordType parses a C record (struct or union) and translates it into
|
|
||||||
// a Go struct type.
|
|
||||||
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
|
|
||||||
fieldList := &ast.FieldList{
|
|
||||||
Opening: pos,
|
|
||||||
Closing: pos,
|
|
||||||
}
|
|
||||||
var bitfieldList []bitfieldInfo
|
|
||||||
inBitfield := false
|
|
||||||
bitfieldNum := 0
|
|
||||||
ref := storedRefs.Put(struct {
|
|
||||||
fieldList *ast.FieldList
|
|
||||||
pkg *cgoPackage
|
|
||||||
inBitfield *bool
|
|
||||||
bitfieldNum *int
|
|
||||||
bitfieldList *[]bitfieldInfo
|
|
||||||
}{fieldList, p, &inBitfield, &bitfieldNum, &bitfieldList})
|
|
||||||
defer storedRefs.Remove(ref)
|
|
||||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_struct_visitor), C.CXClientData(ref))
|
|
||||||
renameFieldKeywords(fieldList)
|
|
||||||
switch C.tinygo_clang_getCursorKind(cursor) {
|
|
||||||
case C.CXCursor_StructDecl:
|
|
||||||
return &elaboratedTypeInfo{
|
|
||||||
typeExpr: &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
Fields: fieldList,
|
|
||||||
},
|
|
||||||
pos: pos,
|
|
||||||
bitfields: bitfieldList,
|
|
||||||
}
|
|
||||||
case C.CXCursor_UnionDecl:
|
|
||||||
typeInfo := &elaboratedTypeInfo{
|
|
||||||
typeExpr: &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
Fields: fieldList,
|
|
||||||
},
|
|
||||||
pos: pos,
|
|
||||||
bitfields: bitfieldList,
|
|
||||||
}
|
|
||||||
if len(fieldList.List) <= 1 {
|
|
||||||
// Useless union, treat it as a regular struct.
|
|
||||||
return typeInfo
|
|
||||||
}
|
|
||||||
if bitfieldList != nil {
|
|
||||||
// This is valid C... but please don't do this.
|
|
||||||
p.addError(pos, "bitfield in a union is not supported")
|
|
||||||
}
|
|
||||||
typ := C.tinygo_clang_getCursorType(cursor)
|
|
||||||
alignInBytes := int64(C.clang_Type_getAlignOf(typ))
|
|
||||||
sizeInBytes := int64(C.clang_Type_getSizeOf(typ))
|
|
||||||
if sizeInBytes == 0 {
|
|
||||||
p.addError(pos, "zero-length union is not supported")
|
|
||||||
}
|
|
||||||
typeInfo.unionSize = sizeInBytes
|
|
||||||
typeInfo.unionAlign = alignInBytes
|
|
||||||
return typeInfo
|
|
||||||
default:
|
|
||||||
cursorKind := C.tinygo_clang_getCursorKind(cursor)
|
|
||||||
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
|
||||||
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
|
|
||||||
return &elaboratedTypeInfo{
|
|
||||||
typeExpr: &ast.StructType{
|
|
||||||
Struct: pos,
|
|
||||||
},
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_struct_visitor
|
|
||||||
func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
passed := storedRefs.Get(unsafe.Pointer(client_data)).(struct {
|
|
||||||
fieldList *ast.FieldList
|
|
||||||
pkg *cgoPackage
|
|
||||||
inBitfield *bool
|
|
||||||
bitfieldNum *int
|
|
||||||
bitfieldList *[]bitfieldInfo
|
|
||||||
})
|
|
||||||
fieldList := passed.fieldList
|
|
||||||
p := passed.pkg
|
|
||||||
inBitfield := passed.inBitfield
|
|
||||||
bitfieldNum := passed.bitfieldNum
|
|
||||||
bitfieldList := passed.bitfieldList
|
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
|
|
||||||
case C.CXCursor_FieldDecl:
|
|
||||||
// Expected. This is a regular field.
|
|
||||||
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
|
|
||||||
// Ignore. The next field will be the struct/union itself.
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
default:
|
|
||||||
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
|
|
||||||
p.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
if name == "" {
|
|
||||||
// Assume this is a bitfield of 0 bits.
|
|
||||||
// Warning: this is not necessarily true!
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
typ := C.tinygo_clang_getCursorType(c)
|
|
||||||
field := &ast.Field{
|
|
||||||
Type: p.makeASTType(typ, p.getCursorPosition(c)),
|
|
||||||
}
|
|
||||||
offsetof := int64(C.clang_Type_getOffsetOf(C.tinygo_clang_getCursorType(parent), C.CString(name)))
|
|
||||||
alignOf := int64(C.clang_Type_getAlignOf(typ) * 8)
|
|
||||||
bitfieldOffset := offsetof % alignOf
|
|
||||||
if bitfieldOffset != 0 {
|
|
||||||
if C.tinygo_clang_Cursor_isBitField(c) != 1 {
|
|
||||||
p.addError(pos, "expected a bitfield")
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
if !*inBitfield {
|
|
||||||
*bitfieldNum++
|
|
||||||
}
|
|
||||||
bitfieldName := "__bitfield_" + strconv.Itoa(*bitfieldNum)
|
|
||||||
prevField := fieldList.List[len(fieldList.List)-1]
|
|
||||||
if !*inBitfield {
|
|
||||||
// The previous element also was a bitfield, but wasn't noticed
|
|
||||||
// then. Add it now.
|
|
||||||
*inBitfield = true
|
|
||||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
|
||||||
field: prevField,
|
|
||||||
name: prevField.Names[0].Name,
|
|
||||||
startBit: 0,
|
|
||||||
pos: prevField.Names[0].NamePos,
|
|
||||||
})
|
|
||||||
prevField.Names[0].Name = bitfieldName
|
|
||||||
prevField.Names[0].Obj.Name = bitfieldName
|
|
||||||
}
|
|
||||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
|
||||||
prevBitfield.endBit = bitfieldOffset
|
|
||||||
*bitfieldList = append(*bitfieldList, bitfieldInfo{
|
|
||||||
field: prevField,
|
|
||||||
name: name,
|
|
||||||
startBit: bitfieldOffset,
|
|
||||||
pos: pos,
|
|
||||||
})
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
*inBitfield = false
|
|
||||||
field.Names = []*ast.Ident{
|
|
||||||
{
|
|
||||||
NamePos: pos,
|
|
||||||
Name: name,
|
|
||||||
Obj: &ast.Object{
|
|
||||||
Kind: ast.Var,
|
|
||||||
Name: name,
|
|
||||||
Decl: field,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
fieldList.List = append(fieldList.List, field)
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_enum_visitor
|
|
||||||
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
|
|
||||||
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
|
|
||||||
name := getString(C.tinygo_clang_getCursorSpelling(c))
|
|
||||||
pos := p.getCursorPosition(c)
|
|
||||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
|
||||||
p.constants[name] = constantInfo{
|
|
||||||
expr: &ast.BasicLit{
|
|
||||||
ValuePos: pos,
|
|
||||||
Kind: token.INT,
|
|
||||||
Value: strconv.FormatInt(int64(value), 10),
|
|
||||||
},
|
|
||||||
pos: pos,
|
|
||||||
}
|
|
||||||
return C.CXChildVisit_Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
//export tinygo_clang_inclusion_visitor
|
|
||||||
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
|
|
||||||
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
|
|
||||||
callback(includedFile)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
// +build !byollvm
|
|
||||||
|
|
||||||
package cgo
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
|
||||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
|
|
||||||
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
|
||||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
|
||||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
-301
@@ -1,301 +0,0 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This file has been copied from the Go 1.13 release tree.
|
|
||||||
|
|
||||||
// Checking of compiler and linker flags.
|
|
||||||
// We must avoid flags like -fplugin=, which can allow
|
|
||||||
// arbitrary code execution during the build.
|
|
||||||
// Do not make changes here without carefully
|
|
||||||
// considering the implications.
|
|
||||||
// (That's why the code is isolated in a file named security.go.)
|
|
||||||
//
|
|
||||||
// Note that -Wl,foo means split foo on commas and pass to
|
|
||||||
// the linker, so that -Wl,-foo,bar means pass -foo bar to
|
|
||||||
// the linker. Similarly -Wa,foo for the assembler and so on.
|
|
||||||
// If any of these are permitted, the wildcard portion must
|
|
||||||
// disallow commas.
|
|
||||||
//
|
|
||||||
// Note also that GNU binutils accept any argument @foo
|
|
||||||
// as meaning "read more flags from the file foo", so we must
|
|
||||||
// guard against any command-line argument beginning with @,
|
|
||||||
// even things like "-I @foo".
|
|
||||||
// We use safeArg (which is even more conservative)
|
|
||||||
// to reject these.
|
|
||||||
//
|
|
||||||
// Even worse, gcc -I@foo (one arg) turns into cc1 -I @foo (two args),
|
|
||||||
// so although gcc doesn't expand the @foo, cc1 will.
|
|
||||||
// So out of paranoia, we reject @ at the beginning of every
|
|
||||||
// flag argument that might be split into its own argument.
|
|
||||||
|
|
||||||
package cgo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
var re = regexp.MustCompile
|
|
||||||
|
|
||||||
var validCompilerFlags = []*regexp.Regexp{
|
|
||||||
re(`-D([A-Za-z_].*)`),
|
|
||||||
re(`-F([^@\-].*)`),
|
|
||||||
re(`-I([^@\-].*)`),
|
|
||||||
re(`-O`),
|
|
||||||
re(`-O([^@\-].*)`),
|
|
||||||
re(`-W`),
|
|
||||||
re(`-W([^@,]+)`), // -Wall but not -Wa,-foo.
|
|
||||||
re(`-Wa,-mbig-obj`),
|
|
||||||
re(`-Wp,-D([A-Za-z_].*)`),
|
|
||||||
re(`-ansi`),
|
|
||||||
re(`-f(no-)?asynchronous-unwind-tables`),
|
|
||||||
re(`-f(no-)?blocks`),
|
|
||||||
re(`-f(no-)builtin-[a-zA-Z0-9_]*`),
|
|
||||||
re(`-f(no-)?common`),
|
|
||||||
re(`-f(no-)?constant-cfstrings`),
|
|
||||||
re(`-fdiagnostics-show-note-include-stack`),
|
|
||||||
re(`-f(no-)?eliminate-unused-debug-types`),
|
|
||||||
re(`-f(no-)?exceptions`),
|
|
||||||
re(`-f(no-)?fast-math`),
|
|
||||||
re(`-f(no-)?inline-functions`),
|
|
||||||
re(`-finput-charset=([^@\-].*)`),
|
|
||||||
re(`-f(no-)?fat-lto-objects`),
|
|
||||||
re(`-f(no-)?keep-inline-dllexport`),
|
|
||||||
re(`-f(no-)?lto`),
|
|
||||||
re(`-fmacro-backtrace-limit=(.+)`),
|
|
||||||
re(`-fmessage-length=(.+)`),
|
|
||||||
re(`-f(no-)?modules`),
|
|
||||||
re(`-f(no-)?objc-arc`),
|
|
||||||
re(`-f(no-)?objc-nonfragile-abi`),
|
|
||||||
re(`-f(no-)?objc-legacy-dispatch`),
|
|
||||||
re(`-f(no-)?omit-frame-pointer`),
|
|
||||||
re(`-f(no-)?openmp(-simd)?`),
|
|
||||||
re(`-f(no-)?permissive`),
|
|
||||||
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
|
||||||
re(`-f(no-)?plt`),
|
|
||||||
re(`-f(no-)?rtti`),
|
|
||||||
re(`-f(no-)?split-stack`),
|
|
||||||
re(`-f(no-)?stack-(.+)`),
|
|
||||||
re(`-f(no-)?strict-aliasing`),
|
|
||||||
re(`-f(un)signed-char`),
|
|
||||||
re(`-f(no-)?use-linker-plugin`), // safe if -B is not used; we don't permit -B
|
|
||||||
re(`-f(no-)?visibility-inlines-hidden`),
|
|
||||||
re(`-fsanitize=(.+)`),
|
|
||||||
re(`-ftemplate-depth-(.+)`),
|
|
||||||
re(`-fvisibility=(.+)`),
|
|
||||||
re(`-g([^@\-].*)?`),
|
|
||||||
re(`-m32`),
|
|
||||||
re(`-m64`),
|
|
||||||
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
|
||||||
re(`-m(no-)?v?aes`),
|
|
||||||
re(`-marm`),
|
|
||||||
re(`-m(no-)?avx[0-9a-z]*`),
|
|
||||||
re(`-mfloat-abi=([^@\-].*)`),
|
|
||||||
re(`-mfpmath=[0-9a-z,+]*`),
|
|
||||||
re(`-m(no-)?avx[0-9a-z.]*`),
|
|
||||||
re(`-m(no-)?ms-bitfields`),
|
|
||||||
re(`-m(no-)?stack-(.+)`),
|
|
||||||
re(`-mmacosx-(.+)`),
|
|
||||||
re(`-mios-simulator-version-min=(.+)`),
|
|
||||||
re(`-miphoneos-version-min=(.+)`),
|
|
||||||
re(`-mtvos-simulator-version-min=(.+)`),
|
|
||||||
re(`-mtvos-version-min=(.+)`),
|
|
||||||
re(`-mwatchos-simulator-version-min=(.+)`),
|
|
||||||
re(`-mwatchos-version-min=(.+)`),
|
|
||||||
re(`-mnop-fun-dllimport`),
|
|
||||||
re(`-m(no-)?sse[0-9.]*`),
|
|
||||||
re(`-m(no-)?ssse3`),
|
|
||||||
re(`-mthumb(-interwork)?`),
|
|
||||||
re(`-mthreads`),
|
|
||||||
re(`-mwindows`),
|
|
||||||
re(`--param=ssp-buffer-size=[0-9]*`),
|
|
||||||
re(`-pedantic(-errors)?`),
|
|
||||||
re(`-pipe`),
|
|
||||||
re(`-pthread`),
|
|
||||||
re(`-?-std=([^@\-].*)`),
|
|
||||||
re(`-?-stdlib=([^@\-].*)`),
|
|
||||||
re(`--sysroot=([^@\-].*)`),
|
|
||||||
re(`-w`),
|
|
||||||
re(`-x([^@\-].*)`),
|
|
||||||
re(`-v`),
|
|
||||||
}
|
|
||||||
|
|
||||||
var validCompilerFlagsWithNextArg = []string{
|
|
||||||
"-arch",
|
|
||||||
"-D",
|
|
||||||
"-I",
|
|
||||||
"-framework",
|
|
||||||
"-isysroot",
|
|
||||||
"-isystem",
|
|
||||||
"--sysroot",
|
|
||||||
"-target",
|
|
||||||
"-x",
|
|
||||||
}
|
|
||||||
|
|
||||||
var validLinkerFlags = []*regexp.Regexp{
|
|
||||||
re(`-F([^@\-].*)`),
|
|
||||||
re(`-l([^@\-].*)`),
|
|
||||||
re(`-L([^@\-].*)`),
|
|
||||||
re(`-O`),
|
|
||||||
re(`-O([^@\-].*)`),
|
|
||||||
re(`-f(no-)?(pic|PIC|pie|PIE)`),
|
|
||||||
re(`-f(no-)?openmp(-simd)?`),
|
|
||||||
re(`-fsanitize=([^@\-].*)`),
|
|
||||||
re(`-flat_namespace`),
|
|
||||||
re(`-g([^@\-].*)?`),
|
|
||||||
re(`-headerpad_max_install_names`),
|
|
||||||
re(`-m(abi|arch|cpu|fpu|tune)=([^@\-].*)`),
|
|
||||||
re(`-mfloat-abi=([^@\-].*)`),
|
|
||||||
re(`-mmacosx-(.+)`),
|
|
||||||
re(`-mios-simulator-version-min=(.+)`),
|
|
||||||
re(`-miphoneos-version-min=(.+)`),
|
|
||||||
re(`-mthreads`),
|
|
||||||
re(`-mwindows`),
|
|
||||||
re(`-(pic|PIC|pie|PIE)`),
|
|
||||||
re(`-pthread`),
|
|
||||||
re(`-rdynamic`),
|
|
||||||
re(`-shared`),
|
|
||||||
re(`-?-static([-a-z0-9+]*)`),
|
|
||||||
re(`-?-stdlib=([^@\-].*)`),
|
|
||||||
re(`-v`),
|
|
||||||
|
|
||||||
// Note that any wildcards in -Wl need to exclude comma,
|
|
||||||
// since -Wl splits its argument at commas and passes
|
|
||||||
// them all to the linker uninterpreted. Allowing comma
|
|
||||||
// in a wildcard would allow tunnelling arbitrary additional
|
|
||||||
// linker arguments through one of these.
|
|
||||||
re(`-Wl,--(no-)?allow-multiple-definition`),
|
|
||||||
re(`-Wl,--(no-)?allow-shlib-undefined`),
|
|
||||||
re(`-Wl,--(no-)?as-needed`),
|
|
||||||
re(`-Wl,-Bdynamic`),
|
|
||||||
re(`-Wl,-berok`),
|
|
||||||
re(`-Wl,-Bstatic`),
|
|
||||||
re(`-WL,-O([^@,\-][^,]*)?`),
|
|
||||||
re(`-Wl,-d[ny]`),
|
|
||||||
re(`-Wl,--disable-new-dtags`),
|
|
||||||
re(`-Wl,-e[=,][a-zA-Z0-9]*`),
|
|
||||||
re(`-Wl,--enable-new-dtags`),
|
|
||||||
re(`-Wl,--end-group`),
|
|
||||||
re(`-Wl,--(no-)?export-dynamic`),
|
|
||||||
re(`-Wl,-framework,[^,@\-][^,]+`),
|
|
||||||
re(`-Wl,-headerpad_max_install_names`),
|
|
||||||
re(`-Wl,--no-undefined`),
|
|
||||||
re(`-Wl,-R([^@\-][^,@]*$)`),
|
|
||||||
re(`-Wl,--just-symbols[=,]([^,@\-][^,@]+)`),
|
|
||||||
re(`-Wl,-rpath(-link)?[=,]([^,@\-][^,]+)`),
|
|
||||||
re(`-Wl,-s`),
|
|
||||||
re(`-Wl,-search_paths_first`),
|
|
||||||
re(`-Wl,-sectcreate,([^,@\-][^,]+),([^,@\-][^,]+),([^,@\-][^,]+)`),
|
|
||||||
re(`-Wl,--start-group`),
|
|
||||||
re(`-Wl,-?-static`),
|
|
||||||
re(`-Wl,-?-subsystem,(native|windows|console|posix|xbox)`),
|
|
||||||
re(`-Wl,-syslibroot[=,]([^,@\-][^,]+)`),
|
|
||||||
re(`-Wl,-undefined[=,]([^,@\-][^,]+)`),
|
|
||||||
re(`-Wl,-?-unresolved-symbols=[^,]+`),
|
|
||||||
re(`-Wl,--(no-)?warn-([^,]+)`),
|
|
||||||
re(`-Wl,-z,(no)?execstack`),
|
|
||||||
re(`-Wl,-z,relro`),
|
|
||||||
|
|
||||||
re(`[a-zA-Z0-9_/].*\.(a|o|obj|dll|dylib|so)`), // direct linker inputs: x.o or libfoo.so (but not -foo.o or @foo.o)
|
|
||||||
re(`\./.*\.(a|o|obj|dll|dylib|so)`),
|
|
||||||
}
|
|
||||||
|
|
||||||
var validLinkerFlagsWithNextArg = []string{
|
|
||||||
"-arch",
|
|
||||||
"-F",
|
|
||||||
"-l",
|
|
||||||
"-L",
|
|
||||||
"-framework",
|
|
||||||
"-isysroot",
|
|
||||||
"--sysroot",
|
|
||||||
"-target",
|
|
||||||
"-Wl,-framework",
|
|
||||||
"-Wl,-rpath",
|
|
||||||
"-Wl,-R",
|
|
||||||
"-Wl,--just-symbols",
|
|
||||||
"-Wl,-undefined",
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCompilerFlags(name string, list []string) error {
|
|
||||||
return checkFlags(name, list, validCompilerFlags, validCompilerFlagsWithNextArg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkLinkerFlags(name string, list []string) error {
|
|
||||||
return checkFlags(name, list, validLinkerFlags, validLinkerFlagsWithNextArg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkFlags(name string, list []string, valid []*regexp.Regexp, validNext []string) error {
|
|
||||||
// Let users override rules with $CGO_CFLAGS_ALLOW, $CGO_CFLAGS_DISALLOW, etc.
|
|
||||||
var (
|
|
||||||
allow *regexp.Regexp
|
|
||||||
disallow *regexp.Regexp
|
|
||||||
)
|
|
||||||
if env := os.Getenv("CGO_" + name + "_ALLOW"); env != "" {
|
|
||||||
r, err := regexp.Compile(env)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing $CGO_%s_ALLOW: %v", name, err)
|
|
||||||
}
|
|
||||||
allow = r
|
|
||||||
}
|
|
||||||
if env := os.Getenv("CGO_" + name + "_DISALLOW"); env != "" {
|
|
||||||
r, err := regexp.Compile(env)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing $CGO_%s_DISALLOW: %v", name, err)
|
|
||||||
}
|
|
||||||
disallow = r
|
|
||||||
}
|
|
||||||
|
|
||||||
Args:
|
|
||||||
for i := 0; i < len(list); i++ {
|
|
||||||
arg := list[i]
|
|
||||||
if disallow != nil && disallow.FindString(arg) == arg {
|
|
||||||
goto Bad
|
|
||||||
}
|
|
||||||
if allow != nil && allow.FindString(arg) == arg {
|
|
||||||
continue Args
|
|
||||||
}
|
|
||||||
for _, re := range valid {
|
|
||||||
if re.FindString(arg) == arg { // must be complete match
|
|
||||||
continue Args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, x := range validNext {
|
|
||||||
if arg == x {
|
|
||||||
if i+1 < len(list) && safeArg(list[i+1]) {
|
|
||||||
i++
|
|
||||||
continue Args
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permit -Wl,-framework -Wl,name.
|
|
||||||
if i+1 < len(list) &&
|
|
||||||
strings.HasPrefix(arg, "-Wl,") &&
|
|
||||||
strings.HasPrefix(list[i+1], "-Wl,") &&
|
|
||||||
safeArg(list[i+1][4:]) &&
|
|
||||||
!strings.Contains(list[i+1][4:], ",") {
|
|
||||||
i++
|
|
||||||
continue Args
|
|
||||||
}
|
|
||||||
|
|
||||||
if i+1 < len(list) {
|
|
||||||
return fmt.Errorf("invalid flag: %s %s (see https://golang.org/s/invalidflag)", arg, list[i+1])
|
|
||||||
}
|
|
||||||
return fmt.Errorf("invalid flag: %s without argument (see https://golang.org/s/invalidflag)", arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Bad:
|
|
||||||
return fmt.Errorf("invalid flag: %s", arg)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func safeArg(name string) bool {
|
|
||||||
if name == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
c := name[0]
|
|
||||||
return '0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '.' || c == '_' || c == '/' || c >= utf8.RuneSelf
|
|
||||||
}
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
// Copyright 2018 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// This file has been copied from the Go 1.13 release tree.
|
|
||||||
|
|
||||||
package cgo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var goodCompilerFlags = [][]string{
|
|
||||||
{"-DFOO"},
|
|
||||||
{"-Dfoo=bar"},
|
|
||||||
{"-F/Qt"},
|
|
||||||
{"-I/"},
|
|
||||||
{"-I/etc/passwd"},
|
|
||||||
{"-I."},
|
|
||||||
{"-O"},
|
|
||||||
{"-O2"},
|
|
||||||
{"-Osmall"},
|
|
||||||
{"-W"},
|
|
||||||
{"-Wall"},
|
|
||||||
{"-fobjc-arc"},
|
|
||||||
{"-fno-objc-arc"},
|
|
||||||
{"-fomit-frame-pointer"},
|
|
||||||
{"-fno-omit-frame-pointer"},
|
|
||||||
{"-fpic"},
|
|
||||||
{"-fno-pic"},
|
|
||||||
{"-fPIC"},
|
|
||||||
{"-fno-PIC"},
|
|
||||||
{"-fpie"},
|
|
||||||
{"-fno-pie"},
|
|
||||||
{"-fPIE"},
|
|
||||||
{"-fno-PIE"},
|
|
||||||
{"-fsplit-stack"},
|
|
||||||
{"-fno-split-stack"},
|
|
||||||
{"-fstack-xxx"},
|
|
||||||
{"-fno-stack-xxx"},
|
|
||||||
{"-fsanitize=hands"},
|
|
||||||
{"-g"},
|
|
||||||
{"-ggdb"},
|
|
||||||
{"-march=souza"},
|
|
||||||
{"-mcpu=123"},
|
|
||||||
{"-mfpu=123"},
|
|
||||||
{"-mtune=happybirthday"},
|
|
||||||
{"-mstack-overflow"},
|
|
||||||
{"-mno-stack-overflow"},
|
|
||||||
{"-mmacosx-version"},
|
|
||||||
{"-mnop-fun-dllimport"},
|
|
||||||
{"-pthread"},
|
|
||||||
{"-std=c99"},
|
|
||||||
{"-xc"},
|
|
||||||
{"-D", "FOO"},
|
|
||||||
{"-D", "foo=bar"},
|
|
||||||
{"-I", "."},
|
|
||||||
{"-I", "/etc/passwd"},
|
|
||||||
{"-I", "世界"},
|
|
||||||
{"-framework", "Chocolate"},
|
|
||||||
{"-x", "c"},
|
|
||||||
{"-v"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var badCompilerFlags = [][]string{
|
|
||||||
{"-D@X"},
|
|
||||||
{"-D-X"},
|
|
||||||
{"-F@dir"},
|
|
||||||
{"-F-dir"},
|
|
||||||
{"-I@dir"},
|
|
||||||
{"-I-dir"},
|
|
||||||
{"-O@1"},
|
|
||||||
{"-Wa,-foo"},
|
|
||||||
{"-W@foo"},
|
|
||||||
{"-g@gdb"},
|
|
||||||
{"-g-gdb"},
|
|
||||||
{"-march=@dawn"},
|
|
||||||
{"-march=-dawn"},
|
|
||||||
{"-std=@c99"},
|
|
||||||
{"-std=-c99"},
|
|
||||||
{"-x@c"},
|
|
||||||
{"-x-c"},
|
|
||||||
{"-D", "@foo"},
|
|
||||||
{"-D", "-foo"},
|
|
||||||
{"-I", "@foo"},
|
|
||||||
{"-I", "-foo"},
|
|
||||||
{"-framework", "-Caffeine"},
|
|
||||||
{"-framework", "@Home"},
|
|
||||||
{"-x", "--c"},
|
|
||||||
{"-x", "@obj"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckCompilerFlags(t *testing.T) {
|
|
||||||
for _, f := range goodCompilerFlags {
|
|
||||||
if err := checkCompilerFlags("test", f); err != nil {
|
|
||||||
t.Errorf("unexpected error for %q: %v", f, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, f := range badCompilerFlags {
|
|
||||||
if err := checkCompilerFlags("test", f); err == nil {
|
|
||||||
t.Errorf("missing error for %q", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var goodLinkerFlags = [][]string{
|
|
||||||
{"-Fbar"},
|
|
||||||
{"-lbar"},
|
|
||||||
{"-Lbar"},
|
|
||||||
{"-fpic"},
|
|
||||||
{"-fno-pic"},
|
|
||||||
{"-fPIC"},
|
|
||||||
{"-fno-PIC"},
|
|
||||||
{"-fpie"},
|
|
||||||
{"-fno-pie"},
|
|
||||||
{"-fPIE"},
|
|
||||||
{"-fno-PIE"},
|
|
||||||
{"-fsanitize=hands"},
|
|
||||||
{"-g"},
|
|
||||||
{"-ggdb"},
|
|
||||||
{"-march=souza"},
|
|
||||||
{"-mcpu=123"},
|
|
||||||
{"-mfpu=123"},
|
|
||||||
{"-mtune=happybirthday"},
|
|
||||||
{"-pic"},
|
|
||||||
{"-pthread"},
|
|
||||||
{"-Wl,-rpath,foo"},
|
|
||||||
{"-Wl,-rpath,$ORIGIN/foo"},
|
|
||||||
{"-Wl,-R", "/foo"},
|
|
||||||
{"-Wl,-R", "foo"},
|
|
||||||
{"-Wl,-R,foo"},
|
|
||||||
{"-Wl,--just-symbols=foo"},
|
|
||||||
{"-Wl,--just-symbols,foo"},
|
|
||||||
{"-Wl,--warn-error"},
|
|
||||||
{"-Wl,--no-warn-error"},
|
|
||||||
{"foo.so"},
|
|
||||||
{"_世界.dll"},
|
|
||||||
{"./x.o"},
|
|
||||||
{"libcgosotest.dylib"},
|
|
||||||
{"-F", "framework"},
|
|
||||||
{"-l", "."},
|
|
||||||
{"-l", "/etc/passwd"},
|
|
||||||
{"-l", "世界"},
|
|
||||||
{"-L", "framework"},
|
|
||||||
{"-framework", "Chocolate"},
|
|
||||||
{"-v"},
|
|
||||||
{"-Wl,-framework", "-Wl,Chocolate"},
|
|
||||||
{"-Wl,-framework,Chocolate"},
|
|
||||||
{"-Wl,-unresolved-symbols=ignore-all"},
|
|
||||||
}
|
|
||||||
|
|
||||||
var badLinkerFlags = [][]string{
|
|
||||||
{"-DFOO"},
|
|
||||||
{"-Dfoo=bar"},
|
|
||||||
{"-W"},
|
|
||||||
{"-Wall"},
|
|
||||||
{"-fobjc-arc"},
|
|
||||||
{"-fno-objc-arc"},
|
|
||||||
{"-fomit-frame-pointer"},
|
|
||||||
{"-fno-omit-frame-pointer"},
|
|
||||||
{"-fsplit-stack"},
|
|
||||||
{"-fno-split-stack"},
|
|
||||||
{"-fstack-xxx"},
|
|
||||||
{"-fno-stack-xxx"},
|
|
||||||
{"-mstack-overflow"},
|
|
||||||
{"-mno-stack-overflow"},
|
|
||||||
{"-mnop-fun-dllimport"},
|
|
||||||
{"-std=c99"},
|
|
||||||
{"-xc"},
|
|
||||||
{"-D", "FOO"},
|
|
||||||
{"-D", "foo=bar"},
|
|
||||||
{"-I", "FOO"},
|
|
||||||
{"-L", "@foo"},
|
|
||||||
{"-L", "-foo"},
|
|
||||||
{"-x", "c"},
|
|
||||||
{"-D@X"},
|
|
||||||
{"-D-X"},
|
|
||||||
{"-I@dir"},
|
|
||||||
{"-I-dir"},
|
|
||||||
{"-O@1"},
|
|
||||||
{"-Wa,-foo"},
|
|
||||||
{"-W@foo"},
|
|
||||||
{"-g@gdb"},
|
|
||||||
{"-g-gdb"},
|
|
||||||
{"-march=@dawn"},
|
|
||||||
{"-march=-dawn"},
|
|
||||||
{"-std=@c99"},
|
|
||||||
{"-std=-c99"},
|
|
||||||
{"-x@c"},
|
|
||||||
{"-x-c"},
|
|
||||||
{"-D", "@foo"},
|
|
||||||
{"-D", "-foo"},
|
|
||||||
{"-I", "@foo"},
|
|
||||||
{"-I", "-foo"},
|
|
||||||
{"-l", "@foo"},
|
|
||||||
{"-l", "-foo"},
|
|
||||||
{"-framework", "-Caffeine"},
|
|
||||||
{"-framework", "@Home"},
|
|
||||||
{"-Wl,-framework,-Caffeine"},
|
|
||||||
{"-Wl,-framework", "-Wl,@Home"},
|
|
||||||
{"-Wl,-framework", "@Home"},
|
|
||||||
{"-Wl,-framework,Chocolate,@Home"},
|
|
||||||
{"-x", "--c"},
|
|
||||||
{"-x", "@obj"},
|
|
||||||
{"-Wl,-rpath,@foo"},
|
|
||||||
{"-Wl,-R,foo,bar"},
|
|
||||||
{"-Wl,-R,@foo"},
|
|
||||||
{"-Wl,--just-symbols,@foo"},
|
|
||||||
{"../x.o"},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckLinkerFlags(t *testing.T) {
|
|
||||||
for _, f := range goodLinkerFlags {
|
|
||||||
if err := checkLinkerFlags("test", f); err != nil {
|
|
||||||
t.Errorf("unexpected error for %q: %v", f, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, f := range badLinkerFlags {
|
|
||||||
if err := checkLinkerFlags("test", f); err == nil {
|
|
||||||
t.Errorf("missing error for %q", f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckFlagAllowDisallow(t *testing.T) {
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
|
||||||
t.Fatalf("missing error for -disallow")
|
|
||||||
}
|
|
||||||
os.Setenv("CGO_TEST_ALLOW", "-disallo")
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err == nil {
|
|
||||||
t.Fatalf("missing error for -disallow with CGO_TEST_ALLOW=-disallo")
|
|
||||||
}
|
|
||||||
os.Setenv("CGO_TEST_ALLOW", "-disallow")
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-disallow"}); err != nil {
|
|
||||||
t.Fatalf("unexpected error for -disallow with CGO_TEST_ALLOW=-disallow: %v", err)
|
|
||||||
}
|
|
||||||
os.Unsetenv("CGO_TEST_ALLOW")
|
|
||||||
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err != nil {
|
|
||||||
t.Fatalf("unexpected error for -Wall: %v", err)
|
|
||||||
}
|
|
||||||
os.Setenv("CGO_TEST_DISALLOW", "-Wall")
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
|
||||||
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall")
|
|
||||||
}
|
|
||||||
os.Setenv("CGO_TEST_ALLOW", "-Wall") // disallow wins
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-Wall"}); err == nil {
|
|
||||||
t.Fatalf("missing error for -Wall with CGO_TEST_DISALLOW=-Wall and CGO_TEST_ALLOW=-Wall")
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Setenv("CGO_TEST_ALLOW", "-fplugin.*")
|
|
||||||
os.Setenv("CGO_TEST_DISALLOW", "-fplugin=lint.so")
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-fplugin=faster.so"}); err != nil {
|
|
||||||
t.Fatalf("unexpected error for -fplugin=faster.so: %v", err)
|
|
||||||
}
|
|
||||||
if err := checkCompilerFlags("TEST", []string{"-fplugin=lint.so"}); err == nil {
|
|
||||||
t.Fatalf("missing error for -fplugin=lint.so: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vendored
-3
@@ -1,3 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
Vendored
-26
@@ -1,26 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
type C.int16_t = int16
|
|
||||||
type C.int32_t = int32
|
|
||||||
type C.int64_t = int64
|
|
||||||
type C.int8_t = int8
|
|
||||||
type C.uint16_t = uint16
|
|
||||||
type C.uint32_t = uint32
|
|
||||||
type C.uint64_t = uint64
|
|
||||||
type C.uint8_t = uint8
|
|
||||||
type C.uintptr_t = uintptr
|
|
||||||
type C.char uint8
|
|
||||||
type C.int int32
|
|
||||||
type C.long int32
|
|
||||||
type C.longlong int64
|
|
||||||
type C.schar int8
|
|
||||||
type C.short int16
|
|
||||||
type C.uchar uint8
|
|
||||||
type C.uint uint32
|
|
||||||
type C.ulong uint32
|
|
||||||
type C.ulonglong uint64
|
|
||||||
type C.ushort uint16
|
|
||||||
Vendored
-12
@@ -1,12 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
#define foo 3
|
|
||||||
#define bar foo
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
const (
|
|
||||||
Foo = C.foo
|
|
||||||
Bar = C.bar
|
|
||||||
)
|
|
||||||
Vendored
-29
@@ -1,29 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
const C.bar = C.foo
|
|
||||||
const C.foo = 3
|
|
||||||
|
|
||||||
type C.int16_t = int16
|
|
||||||
type C.int32_t = int32
|
|
||||||
type C.int64_t = int64
|
|
||||||
type C.int8_t = int8
|
|
||||||
type C.uint16_t = uint16
|
|
||||||
type C.uint32_t = uint32
|
|
||||||
type C.uint64_t = uint64
|
|
||||||
type C.uint8_t = uint8
|
|
||||||
type C.uintptr_t = uintptr
|
|
||||||
type C.char uint8
|
|
||||||
type C.int int32
|
|
||||||
type C.long int32
|
|
||||||
type C.longlong int64
|
|
||||||
type C.schar int8
|
|
||||||
type C.short int16
|
|
||||||
type C.uchar uint8
|
|
||||||
type C.uint uint32
|
|
||||||
type C.ulong uint32
|
|
||||||
type C.ulonglong uint64
|
|
||||||
type C.ushort uint16
|
|
||||||
Vendored
-41
@@ -1,41 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
#warning some warning
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
} point_t;
|
|
||||||
|
|
||||||
typedef someType noType; // undefined type
|
|
||||||
|
|
||||||
#define SOME_CONST_1 5) // invalid const syntax
|
|
||||||
#define SOME_CONST_2 6) // const not used (so no error)
|
|
||||||
#define SOME_CONST_3 1234 // const too large for byte
|
|
||||||
*/
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// #define SOME_CONST_4 8) // after some empty lines
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// #warning another warning
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// Make sure that errors for the following lines won't change with future
|
|
||||||
// additions to the CGo preamble.
|
|
||||||
//line errors.go:100
|
|
||||||
var (
|
|
||||||
// constant too large
|
|
||||||
_ C.uint8_t = 2 << 10
|
|
||||||
|
|
||||||
// z member does not exist
|
|
||||||
_ C.point_t = C.point_t{z: 3}
|
|
||||||
|
|
||||||
// constant has syntax error
|
|
||||||
_ = C.SOME_CONST_1
|
|
||||||
|
|
||||||
_ byte = C.SOME_CONST_3
|
|
||||||
|
|
||||||
_ = C.SOME_CONST_4
|
|
||||||
)
|
|
||||||
Vendored
-46
@@ -1,46 +0,0 @@
|
|||||||
// CGo errors:
|
|
||||||
// testdata/errors.go:4:2: warning: some warning
|
|
||||||
// testdata/errors.go:11:9: error: unknown type name 'someType'
|
|
||||||
// testdata/errors.go:22:5: warning: another warning
|
|
||||||
// testdata/errors.go:13:23: unexpected token ), expected end of expression
|
|
||||||
// testdata/errors.go:19:26: unexpected token ), expected end of expression
|
|
||||||
|
|
||||||
// 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:105: unknown field z in struct literal
|
|
||||||
// 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:112: undeclared name: C.SOME_CONST_4
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
const C.SOME_CONST_3 = 1234
|
|
||||||
|
|
||||||
type C.int16_t = int16
|
|
||||||
type C.int32_t = int32
|
|
||||||
type C.int64_t = int64
|
|
||||||
type C.int8_t = int8
|
|
||||||
type C.uint16_t = uint16
|
|
||||||
type C.uint32_t = uint32
|
|
||||||
type C.uint64_t = uint64
|
|
||||||
type C.uint8_t = uint8
|
|
||||||
type C.uintptr_t = uintptr
|
|
||||||
type C.char uint8
|
|
||||||
type C.int int32
|
|
||||||
type C.long int32
|
|
||||||
type C.longlong int64
|
|
||||||
type C.schar int8
|
|
||||||
type C.short int16
|
|
||||||
type C.uchar uint8
|
|
||||||
type C.uint uint32
|
|
||||||
type C.ulong uint32
|
|
||||||
type C.ulonglong uint64
|
|
||||||
type C.ushort uint16
|
|
||||||
type C.point_t = struct {
|
|
||||||
x C.int
|
|
||||||
y C.int
|
|
||||||
}
|
|
||||||
Vendored
-37
@@ -1,37 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
// this name doesn't exist
|
|
||||||
#cgo NOFLAGS: -foo
|
|
||||||
|
|
||||||
// unknown flag
|
|
||||||
#cgo CFLAGS: -fdoes-not-exist -DNOTDEFINED
|
|
||||||
|
|
||||||
#cgo CFLAGS: -DFOO
|
|
||||||
|
|
||||||
#cgo CFLAGS: -Iinclude
|
|
||||||
#include "foo.h"
|
|
||||||
|
|
||||||
#if defined(FOO)
|
|
||||||
#define BAR 3
|
|
||||||
#else
|
|
||||||
#define BAR 5
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(NOTDEFINED)
|
|
||||||
#warning flag must not be defined
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check Compiler flags
|
|
||||||
#cgo LDFLAGS: -lc
|
|
||||||
|
|
||||||
// This flag is not valid ldflags
|
|
||||||
#cgo LDFLAGS: -does-not-exists
|
|
||||||
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ = C.BAR
|
|
||||||
_ = C.FOO_H
|
|
||||||
)
|
|
||||||
Vendored
-34
@@ -1,34 +0,0 @@
|
|||||||
// CGo errors:
|
|
||||||
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
|
||||||
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
|
||||||
// testdata/flags.go:29:14: invalid flag: -does-not-exists
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
const C.BAR = 3
|
|
||||||
const C.FOO_H = 1
|
|
||||||
|
|
||||||
type C.int16_t = int16
|
|
||||||
type C.int32_t = int32
|
|
||||||
type C.int64_t = int64
|
|
||||||
type C.int8_t = int8
|
|
||||||
type C.uint16_t = uint16
|
|
||||||
type C.uint32_t = uint32
|
|
||||||
type C.uint64_t = uint64
|
|
||||||
type C.uint8_t = uint8
|
|
||||||
type C.uintptr_t = uintptr
|
|
||||||
type C.char uint8
|
|
||||||
type C.int int32
|
|
||||||
type C.long int32
|
|
||||||
type C.longlong int64
|
|
||||||
type C.schar int8
|
|
||||||
type C.short int16
|
|
||||||
type C.uchar uint8
|
|
||||||
type C.uint uint32
|
|
||||||
type C.ulong uint32
|
|
||||||
type C.ulonglong uint64
|
|
||||||
type C.ushort uint16
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
#define FOO_H 1
|
|
||||||
Vendored
-179
@@ -1,179 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Simple typedef.
|
|
||||||
typedef int myint;
|
|
||||||
|
|
||||||
// Structs, with or without name.
|
|
||||||
typedef struct {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
} point2d_t;
|
|
||||||
typedef struct point3d {
|
|
||||||
int x;
|
|
||||||
int y;
|
|
||||||
int z;
|
|
||||||
} point3d_t;
|
|
||||||
|
|
||||||
// Structs with reserved field names.
|
|
||||||
struct type1 {
|
|
||||||
// All these fields should be renamed.
|
|
||||||
int type;
|
|
||||||
int _type;
|
|
||||||
int __type;
|
|
||||||
};
|
|
||||||
struct type2 {
|
|
||||||
// This field should not be renamed.
|
|
||||||
int _type;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Unions.
|
|
||||||
typedef union {
|
|
||||||
// Union should be treated as a struct.
|
|
||||||
int i;
|
|
||||||
} union1_t;
|
|
||||||
typedef union {
|
|
||||||
// Union must contain a single field and have special getters/setters.
|
|
||||||
int i;
|
|
||||||
double d;
|
|
||||||
short s;
|
|
||||||
} union3_t;
|
|
||||||
typedef union union2d {
|
|
||||||
int i;
|
|
||||||
double d[2];
|
|
||||||
} union2d_t;
|
|
||||||
typedef union {
|
|
||||||
unsigned char arr[10];
|
|
||||||
} unionarray_t;
|
|
||||||
|
|
||||||
// Nested structs and unions.
|
|
||||||
typedef struct {
|
|
||||||
point2d_t begin;
|
|
||||||
point2d_t end;
|
|
||||||
int tag;
|
|
||||||
union {
|
|
||||||
point2d_t area;
|
|
||||||
point3d_t solid;
|
|
||||||
} coord;
|
|
||||||
} struct_nested_t;
|
|
||||||
typedef union {
|
|
||||||
point3d_t point;
|
|
||||||
unionarray_t array;
|
|
||||||
union3_t thing;
|
|
||||||
} union_nested_t;
|
|
||||||
|
|
||||||
// Enums. These define constant numbers. All these constants must be given the
|
|
||||||
// correct number.
|
|
||||||
typedef enum option {
|
|
||||||
optionA,
|
|
||||||
optionB,
|
|
||||||
optionC = -5,
|
|
||||||
optionD,
|
|
||||||
optionE = 10,
|
|
||||||
optionF,
|
|
||||||
optionG,
|
|
||||||
} option_t;
|
|
||||||
enum unused {
|
|
||||||
unused1 = 5,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Anonymous enum.
|
|
||||||
typedef enum {
|
|
||||||
option2A = 20,
|
|
||||||
} option2_t;
|
|
||||||
|
|
||||||
// Various types that are usually translated directly to Go types, but storing
|
|
||||||
// them in a struct reveals them.
|
|
||||||
typedef struct {
|
|
||||||
float f;
|
|
||||||
double d;
|
|
||||||
int *ptr;
|
|
||||||
} types_t;
|
|
||||||
|
|
||||||
// Arrays.
|
|
||||||
typedef int myIntArray[10];
|
|
||||||
|
|
||||||
// Bitfields.
|
|
||||||
typedef struct {
|
|
||||||
unsigned char start;
|
|
||||||
unsigned char a : 5;
|
|
||||||
unsigned char b : 1;
|
|
||||||
unsigned char c : 2;
|
|
||||||
unsigned char :0; // new field
|
|
||||||
unsigned char d : 6;
|
|
||||||
unsigned char e : 3;
|
|
||||||
// Note that C++ allows bitfields bigger than the underlying type.
|
|
||||||
} bitfield_t;
|
|
||||||
|
|
||||||
// Function signatures.
|
|
||||||
void variadic0();
|
|
||||||
void variadic2(int x, int y, ...);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// // Test that we can refer from this CGo fragment to the fragment above.
|
|
||||||
// typedef myint myint2;
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
var (
|
|
||||||
// Simple typedefs.
|
|
||||||
_ C.myint
|
|
||||||
|
|
||||||
// Structs.
|
|
||||||
_ C.point2d_t
|
|
||||||
_ C.point3d_t
|
|
||||||
_ C.struct_point3d
|
|
||||||
|
|
||||||
// Structs with reserved field names.
|
|
||||||
_ C.struct_type1
|
|
||||||
_ C.struct_type2
|
|
||||||
|
|
||||||
// Unions.
|
|
||||||
_ C.union1_t
|
|
||||||
_ C.union3_t
|
|
||||||
_ C.union2d_t
|
|
||||||
_ C.unionarray_t
|
|
||||||
|
|
||||||
// Nested structs and unions.
|
|
||||||
_ C.struct_nested_t
|
|
||||||
_ C.union_nested_t
|
|
||||||
|
|
||||||
// Enums (anonymous and named).
|
|
||||||
_ C.option_t
|
|
||||||
_ C.enum_option
|
|
||||||
_ C.option2_t
|
|
||||||
|
|
||||||
// Various types.
|
|
||||||
_ C.types_t
|
|
||||||
|
|
||||||
// Arrays.
|
|
||||||
_ C.myIntArray
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test bitfield accesses.
|
|
||||||
func accessBitfields() {
|
|
||||||
var x C.bitfield_t
|
|
||||||
x.start = 3
|
|
||||||
x.set_bitfield_a(4)
|
|
||||||
x.set_bitfield_b(1)
|
|
||||||
x.set_bitfield_c(2)
|
|
||||||
x.d = 10
|
|
||||||
x.e = 5
|
|
||||||
var _ C.uchar = x.bitfield_a()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test union accesses.
|
|
||||||
func accessUnion() {
|
|
||||||
var union1 C.union1_t
|
|
||||||
union1.i = 5
|
|
||||||
|
|
||||||
var union2d C.union2d_t
|
|
||||||
var _ *C.int = union2d.unionfield_i()
|
|
||||||
var _ *[2]float64 = union2d.unionfield_d()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test function signatures.
|
|
||||||
func accessFunctions() {
|
|
||||||
C.variadic0()
|
|
||||||
C.variadic2(3, 5)
|
|
||||||
}
|
|
||||||
Vendored
-140
@@ -1,140 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
func C.variadic0() //go:variadic
|
|
||||||
func C.variadic2(x C.int, y C.int) //go:variadic
|
|
||||||
var C.variadic0$funcaddr unsafe.Pointer
|
|
||||||
var C.variadic2$funcaddr unsafe.Pointer
|
|
||||||
|
|
||||||
const C.option2A = 20
|
|
||||||
const C.optionA = 0
|
|
||||||
const C.optionB = 1
|
|
||||||
const C.optionC = -5
|
|
||||||
const C.optionD = -4
|
|
||||||
const C.optionE = 10
|
|
||||||
const C.optionF = 11
|
|
||||||
const C.optionG = 12
|
|
||||||
const C.unused1 = 5
|
|
||||||
|
|
||||||
type C.int16_t = int16
|
|
||||||
type C.int32_t = int32
|
|
||||||
type C.int64_t = int64
|
|
||||||
type C.int8_t = int8
|
|
||||||
type C.uint16_t = uint16
|
|
||||||
type C.uint32_t = uint32
|
|
||||||
type C.uint64_t = uint64
|
|
||||||
type C.uint8_t = uint8
|
|
||||||
type C.uintptr_t = uintptr
|
|
||||||
type C.char uint8
|
|
||||||
type C.int int32
|
|
||||||
type C.long int32
|
|
||||||
type C.longlong int64
|
|
||||||
type C.schar int8
|
|
||||||
type C.short int16
|
|
||||||
type C.uchar uint8
|
|
||||||
type C.uint uint32
|
|
||||||
type C.ulong uint32
|
|
||||||
type C.ulonglong uint64
|
|
||||||
type C.ushort uint16
|
|
||||||
type C.bitfield_t = C.struct_4
|
|
||||||
type C.myIntArray = [10]C.int
|
|
||||||
type C.myint = C.int
|
|
||||||
type C.option2_t = C.uint
|
|
||||||
type C.option_t = C.enum_option
|
|
||||||
type C.point2d_t = struct {
|
|
||||||
x C.int
|
|
||||||
y C.int
|
|
||||||
}
|
|
||||||
type C.point3d_t = C.struct_point3d
|
|
||||||
type C.struct_nested_t = struct {
|
|
||||||
begin C.point2d_t
|
|
||||||
end C.point2d_t
|
|
||||||
tag C.int
|
|
||||||
|
|
||||||
coord C.union_2
|
|
||||||
}
|
|
||||||
type C.types_t = struct {
|
|
||||||
f float32
|
|
||||||
d float64
|
|
||||||
ptr *C.int
|
|
||||||
}
|
|
||||||
type C.union1_t = struct{ i C.int }
|
|
||||||
type C.union2d_t = C.union_union2d
|
|
||||||
type C.union3_t = C.union_1
|
|
||||||
type C.union_nested_t = C.union_3
|
|
||||||
type C.unionarray_t = struct{ arr [10]C.uchar }
|
|
||||||
|
|
||||||
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
|
|
||||||
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
|
|
||||||
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
|
|
||||||
}
|
|
||||||
func (s *C.struct_4) bitfield_b() C.uchar {
|
|
||||||
return s.__bitfield_1 >> 5 & 0x1
|
|
||||||
}
|
|
||||||
func (s *C.struct_4) set_bitfield_b(value C.uchar) {
|
|
||||||
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
|
|
||||||
}
|
|
||||||
func (s *C.struct_4) bitfield_c() C.uchar {
|
|
||||||
return s.__bitfield_1 >> 6
|
|
||||||
}
|
|
||||||
func (s *C.struct_4) set_bitfield_c(value C.uchar,
|
|
||||||
|
|
||||||
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
|
|
||||||
|
|
||||||
type C.struct_4 struct {
|
|
||||||
start C.uchar
|
|
||||||
__bitfield_1 C.uchar
|
|
||||||
|
|
||||||
d C.uchar
|
|
||||||
e C.uchar
|
|
||||||
}
|
|
||||||
type C.struct_point3d struct {
|
|
||||||
x C.int
|
|
||||||
y C.int
|
|
||||||
z C.int
|
|
||||||
}
|
|
||||||
type C.struct_type1 struct {
|
|
||||||
_type C.int
|
|
||||||
__type C.int
|
|
||||||
___type C.int
|
|
||||||
}
|
|
||||||
type C.struct_type2 struct{ _type C.int }
|
|
||||||
|
|
||||||
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
|
||||||
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
|
|
||||||
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
|
|
||||||
|
|
||||||
type C.union_1 struct{ $union uint64 }
|
|
||||||
|
|
||||||
func (union *C.union_2) unionfield_area() *C.point2d_t {
|
|
||||||
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *C.union_2) unionfield_solid() *C.point3d_t {
|
|
||||||
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type C.union_2 struct{ $union [3]uint32 }
|
|
||||||
|
|
||||||
func (union *C.union_3) unionfield_point() *C.point3d_t {
|
|
||||||
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *C.union_3) unionfield_array() *C.unionarray_t {
|
|
||||||
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *C.union_3) unionfield_thing() *C.union3_t {
|
|
||||||
return (*C.union3_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type C.union_3 struct{ $union [2]uint64 }
|
|
||||||
|
|
||||||
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
|
||||||
func (union *C.union_union2d) unionfield_d() *[2]float64 {
|
|
||||||
return (*[2]float64)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type C.union_union2d struct{ $union [2]uint64 }
|
|
||||||
type C.enum_option C.int
|
|
||||||
type C.enum_unused C.uint
|
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commands used by the compilation process might have different file names
|
||||||
|
// across operating systems and distributions.
|
||||||
|
var commands = map[string][]string{
|
||||||
|
"clang": {"clang-8"},
|
||||||
|
"ld.lld": {"ld.lld-8", "ld.lld"},
|
||||||
|
"wasm-ld": {"wasm-ld-8", "wasm-ld"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCommand(cmdNames []string, args ...string) error {
|
||||||
|
for _, cmdName := range cmdNames {
|
||||||
|
cmd := exec.Command(cmdName, args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
if err, ok := err.(*exec.Error); ok && err.Err == exec.ErrNotFound {
|
||||||
|
// this command was not found, try the next
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||||
|
}
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
// Package compileopts contains the configuration for a single to-be-built
|
|
||||||
// binary.
|
|
||||||
package compileopts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config keeps all configuration affecting the build in a single struct.
|
|
||||||
type Config struct {
|
|
||||||
Options *Options
|
|
||||||
Target *TargetSpec
|
|
||||||
GoMinorVersion int
|
|
||||||
ClangHeaders string // Clang built-in header include path
|
|
||||||
TestConfig TestConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
|
|
||||||
func (c *Config) Triple() string {
|
|
||||||
return c.Target.Triple
|
|
||||||
}
|
|
||||||
|
|
||||||
// CPU returns the LLVM CPU name, like atmega328p or arm7tdmi. It may return an
|
|
||||||
// empty string if the CPU name is not known.
|
|
||||||
func (c *Config) CPU() string {
|
|
||||||
return c.Target.CPU
|
|
||||||
}
|
|
||||||
|
|
||||||
// Features returns a list of features this CPU supports. For example, for a
|
|
||||||
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
|
||||||
// will be returned.
|
|
||||||
func (c *Config) Features() string {
|
|
||||||
if c.Target.Features == "" {
|
|
||||||
return c.Options.LLVMFeatures
|
|
||||||
}
|
|
||||||
if c.Options.LLVMFeatures == "" {
|
|
||||||
return c.Target.Features
|
|
||||||
}
|
|
||||||
return c.Target.Features + "," + c.Options.LLVMFeatures
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
|
||||||
// for example, bare-metal targets will usually pretend to be linux to get the
|
|
||||||
// standard library to compile.
|
|
||||||
func (c *Config) GOOS() string {
|
|
||||||
return c.Target.GOOS
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOARCH returns the GOARCH of the target. This might not always be the actual
|
|
||||||
// archtecture: for example, the AVR target is not supported by the Go standard
|
|
||||||
// library so such targets will usually pretend to be linux/arm.
|
|
||||||
func (c *Config) GOARCH() string {
|
|
||||||
return c.Target.GOARCH
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOARM will return the GOARM environment variable given to the compiler when
|
|
||||||
// building a program.
|
|
||||||
func (c *Config) GOARM() string {
|
|
||||||
return c.Options.GOARM
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildTags returns the complete list of build tags used during this build.
|
|
||||||
func (c *Config) BuildTags() []string {
|
|
||||||
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
|
||||||
for i := 1; i <= c.GoMinorVersion; i++ {
|
|
||||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
|
||||||
}
|
|
||||||
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
|
|
||||||
tags = append(tags, extraTags...)
|
|
||||||
}
|
|
||||||
return tags
|
|
||||||
}
|
|
||||||
|
|
||||||
// CgoEnabled returns true if (and only if) CGo is enabled. It is true by
|
|
||||||
// default and false if CGO_ENABLED is set to "0".
|
|
||||||
func (c *Config) CgoEnabled() bool {
|
|
||||||
return goenv.Get("CGO_ENABLED") == "1"
|
|
||||||
}
|
|
||||||
|
|
||||||
// GC returns the garbage collection strategy in use on this platform. Valid
|
|
||||||
// values are "none", "leaking", "extalloc", and "conservative".
|
|
||||||
func (c *Config) GC() string {
|
|
||||||
if c.Options.GC != "" {
|
|
||||||
return c.Options.GC
|
|
||||||
}
|
|
||||||
if c.Target.GC != "" {
|
|
||||||
return c.Target.GC
|
|
||||||
}
|
|
||||||
return "conservative"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
|
||||||
// that can be traced by the garbage collector.
|
|
||||||
func (c *Config) NeedsStackObjects() bool {
|
|
||||||
switch c.GC() {
|
|
||||||
case "conservative", "extalloc":
|
|
||||||
for _, tag := range c.BuildTags() {
|
|
||||||
if tag == "tinygo.wasm" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scheduler returns the scheduler implementation. Valid values are "none",
|
|
||||||
//"coroutines" and "tasks".
|
|
||||||
func (c *Config) Scheduler() string {
|
|
||||||
if c.Options.Scheduler != "" {
|
|
||||||
return c.Options.Scheduler
|
|
||||||
}
|
|
||||||
if c.Target.Scheduler != "" {
|
|
||||||
return c.Target.Scheduler
|
|
||||||
}
|
|
||||||
// Fall back to coroutines, which are supported everywhere.
|
|
||||||
return "coroutines"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serial returns the serial implementation for this build configuration: uart,
|
|
||||||
// usb (meaning USB-CDC), or none.
|
|
||||||
func (c *Config) Serial() string {
|
|
||||||
if c.Options.Serial != "" {
|
|
||||||
return c.Options.Serial
|
|
||||||
}
|
|
||||||
if c.Target.Serial != "" {
|
|
||||||
return c.Target.Serial
|
|
||||||
}
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
|
||||||
// threshold as used in the LLVM optimization pipeline.
|
|
||||||
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
|
||||||
switch c.Options.Opt {
|
|
||||||
case "none", "0":
|
|
||||||
return 0, 0, 0 // -O0
|
|
||||||
case "1":
|
|
||||||
return 1, 0, 0 // -O1
|
|
||||||
case "2":
|
|
||||||
return 2, 0, 225 // -O2
|
|
||||||
case "s":
|
|
||||||
return 2, 1, 225 // -Os
|
|
||||||
case "z":
|
|
||||||
return 2, 2, 5 // -Oz, default
|
|
||||||
default:
|
|
||||||
// This is not shown to the user: valid choices are already checked as
|
|
||||||
// part of Options.Verify(). It is here as a sanity check.
|
|
||||||
panic("unknown optimization level: -opt=" + c.Options.Opt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FuncImplementation picks an appropriate func value implementation for the
|
|
||||||
// target.
|
|
||||||
func (c *Config) FuncImplementation() string {
|
|
||||||
switch c.Scheduler() {
|
|
||||||
case "tasks", "asyncify":
|
|
||||||
// A func value is implemented as a pair of pointers:
|
|
||||||
// {context, function pointer}
|
|
||||||
// where the context may be a pointer to a heap-allocated struct
|
|
||||||
// containing the free variables, or it may be undef if the function
|
|
||||||
// being pointed to doesn't need a context. The function pointer is a
|
|
||||||
// regular function pointer.
|
|
||||||
return "doubleword"
|
|
||||||
case "none", "coroutines":
|
|
||||||
// As "doubleword", but with the function pointer replaced by a unique
|
|
||||||
// ID per function signature. Function values are called by using a
|
|
||||||
// switch statement and choosing which function to call.
|
|
||||||
// Pick the switch implementation with the coroutines scheduler, as it
|
|
||||||
// allows the use of blocking inside a function that is used as a func
|
|
||||||
// value.
|
|
||||||
return "switch"
|
|
||||||
default:
|
|
||||||
panic("unknown scheduler type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
|
||||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
|
||||||
// instruction).
|
|
||||||
func (c *Config) PanicStrategy() string {
|
|
||||||
return c.Options.PanicStrategy
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
|
||||||
// calculates and patches in the checksum for the 2nd stage bootloader.
|
|
||||||
func (c *Config) RP2040BootPatch() bool {
|
|
||||||
if c.Target.RP2040BootPatch != nil {
|
|
||||||
return *c.Target.RP2040BootPatch
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MuslArchitecture returns the architecture name as used in musl libc. It is
|
|
||||||
// usually the same as the first part of the LLVM triple, but not always.
|
|
||||||
func MuslArchitecture(triple string) string {
|
|
||||||
arch := strings.Split(triple, "-")[0]
|
|
||||||
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
|
|
||||||
arch = "arm"
|
|
||||||
}
|
|
||||||
return arch
|
|
||||||
}
|
|
||||||
|
|
||||||
// LibcPath returns the path to the libc directory. The libc path will be either
|
|
||||||
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
|
|
||||||
// directory (which might not yet be built).
|
|
||||||
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
|
|
||||||
// Try to load a precompiled library.
|
|
||||||
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", c.Triple(), name)
|
|
||||||
if _, err := os.Stat(precompiledDir); err == nil {
|
|
||||||
// Found a precompiled library for this OS/architecture. Return the path
|
|
||||||
// directly.
|
|
||||||
return precompiledDir, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// No precompiled library found. Determine the path name that will be used
|
|
||||||
// in the build cache.
|
|
||||||
var outname string
|
|
||||||
if c.CPU() != "" {
|
|
||||||
outname = name + "-" + c.Triple() + "-" + c.CPU()
|
|
||||||
} else {
|
|
||||||
outname = name + "-" + c.Triple()
|
|
||||||
}
|
|
||||||
return filepath.Join(goenv.Get("GOCACHE"), outname), false
|
|
||||||
}
|
|
||||||
|
|
||||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
|
||||||
// preprocessing.
|
|
||||||
func (c *Config) CFlags() []string {
|
|
||||||
var cflags []string
|
|
||||||
for _, flag := range c.Target.CFlags {
|
|
||||||
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
|
||||||
}
|
|
||||||
switch c.Target.Libc {
|
|
||||||
case "picolibc":
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
|
|
||||||
path, _ := c.LibcPath("picolibc")
|
|
||||||
cflags = append(cflags,
|
|
||||||
"--sysroot="+path,
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "include"),
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(picolibcDir, "tinystdio"),
|
|
||||||
)
|
|
||||||
case "musl":
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
path, _ := c.LibcPath("musl")
|
|
||||||
arch := MuslArchitecture(c.Triple())
|
|
||||||
cflags = append(cflags,
|
|
||||||
"--sysroot="+path,
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "arch", arch),
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "musl", "include"),
|
|
||||||
)
|
|
||||||
case "wasi-libc":
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
|
|
||||||
case "mingw-w64":
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
path, _ := c.LibcPath("mingw-w64")
|
|
||||||
cflags = append(cflags,
|
|
||||||
"--sysroot="+path,
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
|
|
||||||
"-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
|
|
||||||
"-D_UCRT",
|
|
||||||
)
|
|
||||||
case "":
|
|
||||||
// No libc specified, nothing to add.
|
|
||||||
default:
|
|
||||||
// Incorrect configuration. This could be handled in a better way, but
|
|
||||||
// usually this will be found by developers (not by TinyGo users).
|
|
||||||
panic("unknown libc: " + c.Target.Libc)
|
|
||||||
}
|
|
||||||
// Always emit debug information. It is optionally stripped at link time.
|
|
||||||
cflags = append(cflags, "-g")
|
|
||||||
// Use the same optimization level as TinyGo.
|
|
||||||
cflags = append(cflags, "-O"+c.Options.Opt)
|
|
||||||
// Set the LLVM target triple.
|
|
||||||
cflags = append(cflags, "--target="+c.Triple())
|
|
||||||
// Set the -mcpu (or similar) flag.
|
|
||||||
if c.Target.CPU != "" {
|
|
||||||
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
|
||||||
// x86 prefers the -march flag (-mcpu is deprecated there).
|
|
||||||
cflags = append(cflags, "-march="+c.Target.CPU)
|
|
||||||
} else if strings.HasPrefix(c.Triple(), "avr") {
|
|
||||||
// AVR MCUs use -mmcu instead of -mcpu.
|
|
||||||
cflags = append(cflags, "-mmcu="+c.Target.CPU)
|
|
||||||
} else {
|
|
||||||
// The rest just uses -mcpu.
|
|
||||||
cflags = append(cflags, "-mcpu="+c.Target.CPU)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cflags
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDFlags returns the flags to pass to the linker. A few more flags are needed
|
|
||||||
// (like the one for the compiler runtime), but this represents the majority of
|
|
||||||
// the flags.
|
|
||||||
func (c *Config) LDFlags() []string {
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
// Merge and adjust LDFlags.
|
|
||||||
var ldflags []string
|
|
||||||
for _, flag := range c.Target.LDFlags {
|
|
||||||
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
|
|
||||||
}
|
|
||||||
ldflags = append(ldflags, "-L", root)
|
|
||||||
if c.Target.LinkerScript != "" {
|
|
||||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
|
||||||
}
|
|
||||||
return ldflags
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExtraFiles returns the list of extra files to be built and linked with the
|
|
||||||
// executable. This can include extra C and assembly files.
|
|
||||||
func (c *Config) ExtraFiles() []string {
|
|
||||||
return c.Target.ExtraFiles
|
|
||||||
}
|
|
||||||
|
|
||||||
// DumpSSA returns whether to dump Go SSA while compiling (-dumpssa flag). Only
|
|
||||||
// enable this for debugging.
|
|
||||||
func (c *Config) DumpSSA() bool {
|
|
||||||
return c.Options.DumpSSA
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyIR returns whether to run extra checks on the IR. This is normally
|
|
||||||
// disabled but enabled during testing.
|
|
||||||
func (c *Config) VerifyIR() bool {
|
|
||||||
return c.Options.VerifyIR
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug returns whether debug (DWARF) information should be retained by the
|
|
||||||
// linker. By default, debug information is retained but it can be removed with
|
|
||||||
// the -no-debug flag.
|
|
||||||
func (c *Config) Debug() bool {
|
|
||||||
return c.Options.Debug
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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"
|
|
||||||
case ".zip":
|
|
||||||
if c.Target.BinaryFormat != "" {
|
|
||||||
return c.Target.BinaryFormat
|
|
||||||
}
|
|
||||||
return "zip"
|
|
||||||
default:
|
|
||||||
// Use the ELF format for unrecognized file formats.
|
|
||||||
return "elf"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Programmer returns the flash method and OpenOCD interface name given a
|
|
||||||
// particular configuration. It may either be all configured in the target JSON
|
|
||||||
// file or be modified using the -programmmer command-line option.
|
|
||||||
func (c *Config) Programmer() (method, openocdInterface string) {
|
|
||||||
switch c.Options.Programmer {
|
|
||||||
case "":
|
|
||||||
// No configuration supplied.
|
|
||||||
return c.Target.FlashMethod, c.Target.OpenOCDInterface
|
|
||||||
case "openocd", "msd", "command":
|
|
||||||
// The -programmer flag only specifies the flash method.
|
|
||||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
|
||||||
case "bmp":
|
|
||||||
// The -programmer flag only specifies the flash method.
|
|
||||||
return c.Options.Programmer, ""
|
|
||||||
default:
|
|
||||||
// The -programmer flag specifies something else, assume it specifies
|
|
||||||
// the OpenOCD interface name.
|
|
||||||
return "openocd", c.Options.Programmer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OpenOCDConfiguration returns a list of command line arguments to OpenOCD.
|
|
||||||
// This list of command-line arguments is based on the various OpenOCD-related
|
|
||||||
// flags in the target specification.
|
|
||||||
func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
|
||||||
_, openocdInterface := c.Programmer()
|
|
||||||
if openocdInterface == "" {
|
|
||||||
return nil, errors.New("OpenOCD programmer not set")
|
|
||||||
}
|
|
||||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
|
|
||||||
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
|
||||||
}
|
|
||||||
if c.Target.OpenOCDTarget == "" {
|
|
||||||
return nil, errors.New("OpenOCD chip not set")
|
|
||||||
}
|
|
||||||
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
|
|
||||||
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
|
||||||
}
|
|
||||||
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
|
||||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
|
||||||
}
|
|
||||||
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
|
||||||
for _, cmd := range c.Target.OpenOCDCommands {
|
|
||||||
args = append(args, "-c", cmd)
|
|
||||||
}
|
|
||||||
if c.Target.OpenOCDTransport != "" {
|
|
||||||
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
|
||||||
}
|
|
||||||
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
CompileTestBinary bool
|
|
||||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package compileopts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
|
||||||
validSchedulerOptions = []string{"none", "tasks", "coroutines", "asyncify"}
|
|
||||||
validSerialOptions = []string{"none", "uart", "usb"}
|
|
||||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
|
||||||
validPanicStrategyOptions = []string{"print", "trap"}
|
|
||||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Options contains extra options to give to the compiler. These options are
|
|
||||||
// usually passed from the command line, but can also be passed in environment
|
|
||||||
// variables for example.
|
|
||||||
type Options struct {
|
|
||||||
GOOS string // environment variable
|
|
||||||
GOARCH string // environment variable
|
|
||||||
GOARM string // environment variable (only used with GOARCH=arm)
|
|
||||||
Target string
|
|
||||||
Opt string
|
|
||||||
GC string
|
|
||||||
PanicStrategy string
|
|
||||||
Scheduler string
|
|
||||||
Serial string
|
|
||||||
PrintIR bool
|
|
||||||
DumpSSA bool
|
|
||||||
VerifyIR bool
|
|
||||||
PrintCommands func(cmd string, args ...string)
|
|
||||||
Parallelism int // -p flag
|
|
||||||
Debug bool
|
|
||||||
PrintSizes string
|
|
||||||
PrintAllocs *regexp.Regexp // regexp string
|
|
||||||
PrintStacks bool
|
|
||||||
Tags string
|
|
||||||
WasmAbi string
|
|
||||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
|
||||||
TestConfig TestConfig
|
|
||||||
Programmer string
|
|
||||||
OpenOCDCommands []string
|
|
||||||
LLVMFeatures string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
|
||||||
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.Serial != "" {
|
|
||||||
valid := isInArray(validSerialOptions, o.Serial)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
|
||||||
o.Serial,
|
|
||||||
strings.Join(validSerialOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.PrintSizes != "" {
|
|
||||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
|
||||||
if !valid {
|
|
||||||
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, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.Opt != "" {
|
|
||||||
if !isInArray(validOptOptions, o.Opt) {
|
|
||||||
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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, asyncify`)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
package compileopts
|
|
||||||
|
|
||||||
// This file loads a target specification from a JSON file.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Target specification for a given target. Used for bare metal targets.
|
|
||||||
//
|
|
||||||
// The target specification is mostly inspired by Rust:
|
|
||||||
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
|
|
||||||
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
|
|
||||||
type TargetSpec struct {
|
|
||||||
Inherits []string `json:"inherits"`
|
|
||||||
Triple string `json:"llvm-target"`
|
|
||||||
CPU string `json:"cpu"`
|
|
||||||
Features string `json:"features"`
|
|
||||||
GOOS string `json:"goos"`
|
|
||||||
GOARCH string `json:"goarch"`
|
|
||||||
BuildTags []string `json:"build-tags"`
|
|
||||||
GC string `json:"gc"`
|
|
||||||
Scheduler string `json:"scheduler"`
|
|
||||||
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
|
|
||||||
Linker string `json:"linker"`
|
|
||||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
|
||||||
Libc string `json:"libc"`
|
|
||||||
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"`
|
|
||||||
LDFlags []string `json:"ldflags"`
|
|
||||||
LinkerScript string `json:"linkerscript"`
|
|
||||||
ExtraFiles []string `json:"extra-files"`
|
|
||||||
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
|
|
||||||
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
|
||||||
FlashCommand string `json:"flash-command"`
|
|
||||||
GDB []string `json:"gdb"`
|
|
||||||
PortReset string `json:"flash-1200-bps-reset"`
|
|
||||||
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
|
|
||||||
FlashMethod string `json:"flash-method"`
|
|
||||||
FlashVolume string `json:"msd-volume-name"`
|
|
||||||
FlashFilename string `json:"msd-firmware-name"`
|
|
||||||
UF2FamilyID string `json:"uf2-family-id"`
|
|
||||||
BinaryFormat string `json:"binary-format"`
|
|
||||||
OpenOCDInterface string `json:"openocd-interface"`
|
|
||||||
OpenOCDTarget string `json:"openocd-target"`
|
|
||||||
OpenOCDTransport string `json:"openocd-transport"`
|
|
||||||
OpenOCDCommands []string `json:"openocd-commands"`
|
|
||||||
JLinkDevice string `json:"jlink-device"`
|
|
||||||
CodeModel string `json:"code-model"`
|
|
||||||
RelocationModel string `json:"relocation-model"`
|
|
||||||
WasmAbi string `json:"wasm-abi"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// overrideProperties overrides all properties that are set in child into itself using reflection.
|
|
||||||
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
|
||||||
specType := reflect.TypeOf(spec).Elem()
|
|
||||||
specValue := reflect.ValueOf(spec).Elem()
|
|
||||||
childValue := reflect.ValueOf(child).Elem()
|
|
||||||
|
|
||||||
for i := 0; i < specType.NumField(); i++ {
|
|
||||||
field := specType.Field(i)
|
|
||||||
src := childValue.Field(i)
|
|
||||||
dst := specValue.Field(i)
|
|
||||||
|
|
||||||
switch kind := field.Type.Kind(); kind {
|
|
||||||
case reflect.String: // for strings, just copy the field of child to spec if not empty
|
|
||||||
if src.Len() > 0 {
|
|
||||||
dst.Set(src)
|
|
||||||
}
|
|
||||||
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
|
|
||||||
if src.Uint() != 0 {
|
|
||||||
dst.Set(src)
|
|
||||||
}
|
|
||||||
case reflect.Ptr: // for pointers, copy if not nil
|
|
||||||
if !src.IsNil() {
|
|
||||||
dst.Set(src)
|
|
||||||
}
|
|
||||||
case reflect.Slice: // for slices...
|
|
||||||
if src.Len() > 0 { // ... if not empty ...
|
|
||||||
switch tag := field.Tag.Get("override"); tag {
|
|
||||||
case "copy":
|
|
||||||
// copy the field of child to spec
|
|
||||||
dst.Set(src)
|
|
||||||
case "append", "":
|
|
||||||
// or append the field of child to spec
|
|
||||||
dst.Set(reflect.AppendSlice(dst, src))
|
|
||||||
default:
|
|
||||||
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown field type : " + kind.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load reads a target specification from the JSON in the given io.Reader. It
|
|
||||||
// may load more targets specified using the "inherits" property.
|
|
||||||
func (spec *TargetSpec) load(r io.Reader) error {
|
|
||||||
err := json.NewDecoder(r).Decode(spec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadFromGivenStr loads the TargetSpec from the given string that could be:
|
|
||||||
// - targets/ directory inside the compiler sources
|
|
||||||
// - a relative or absolute path to custom (project specific) target specification .json file;
|
|
||||||
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
|
|
||||||
// as well as path to custom files (ex. myAwesomeProject.json)
|
|
||||||
func (spec *TargetSpec) loadFromGivenStr(str string) error {
|
|
||||||
path := ""
|
|
||||||
if strings.HasSuffix(str, ".json") {
|
|
||||||
path, _ = filepath.Abs(str)
|
|
||||||
} else {
|
|
||||||
path = filepath.Join(goenv.Get("TINYGOROOT"), "targets", strings.ToLower(str)+".json")
|
|
||||||
}
|
|
||||||
fp, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
return spec.load(fp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolveInherits loads inherited targets, recursively.
|
|
||||||
func (spec *TargetSpec) resolveInherits() error {
|
|
||||||
// First create a new spec with all the inherited properties.
|
|
||||||
newSpec := &TargetSpec{}
|
|
||||||
for _, name := range spec.Inherits {
|
|
||||||
subtarget := &TargetSpec{}
|
|
||||||
err := subtarget.loadFromGivenStr(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = subtarget.resolveInherits()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newSpec.overrideProperties(subtarget)
|
|
||||||
}
|
|
||||||
|
|
||||||
// When all properties are loaded, make sure they are properly inherited.
|
|
||||||
newSpec.overrideProperties(spec)
|
|
||||||
*spec = *newSpec
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a target specification.
|
|
||||||
func LoadTarget(options *Options) (*TargetSpec, error) {
|
|
||||||
if options.Target == "" {
|
|
||||||
// Configure based on GOOS/GOARCH environment variables (falling back to
|
|
||||||
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
|
|
||||||
var llvmarch string
|
|
||||||
switch options.GOARCH {
|
|
||||||
case "386":
|
|
||||||
llvmarch = "i386"
|
|
||||||
case "amd64":
|
|
||||||
llvmarch = "x86_64"
|
|
||||||
case "arm64":
|
|
||||||
llvmarch = "aarch64"
|
|
||||||
case "arm":
|
|
||||||
switch options.GOARM {
|
|
||||||
case "5":
|
|
||||||
llvmarch = "armv5"
|
|
||||||
case "6":
|
|
||||||
llvmarch = "armv6"
|
|
||||||
case "7":
|
|
||||||
llvmarch = "armv7"
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
llvmarch = options.GOARCH
|
|
||||||
}
|
|
||||||
llvmos := options.GOOS
|
|
||||||
if llvmos == "darwin" {
|
|
||||||
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
|
|
||||||
// to iOS!
|
|
||||||
llvmos = "macosx10.12.0"
|
|
||||||
if llvmarch == "aarch64" {
|
|
||||||
// Looks like Apple prefers to call this architecture ARM64
|
|
||||||
// instead of AArch64.
|
|
||||||
llvmarch = "arm64"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Target triples (which actually have four components, but are called
|
|
||||||
// triples for historical reasons) have the form:
|
|
||||||
// arch-vendor-os-environment
|
|
||||||
target := llvmarch + "-unknown-" + llvmos
|
|
||||||
if options.GOARCH == "arm" {
|
|
||||||
target += "-gnueabihf"
|
|
||||||
}
|
|
||||||
if options.GOOS == "windows" {
|
|
||||||
target += "-gnu"
|
|
||||||
}
|
|
||||||
return defaultTarget(options.GOOS, options.GOARCH, target)
|
|
||||||
}
|
|
||||||
|
|
||||||
// See whether there is a target specification for this target (e.g.
|
|
||||||
// Arduino).
|
|
||||||
spec := &TargetSpec{}
|
|
||||||
err := spec.loadFromGivenStr(options.Target)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Successfully loaded this target from a built-in .json file. Make sure
|
|
||||||
// it includes all parents as specified in the "inherits" key.
|
|
||||||
err = spec.resolveInherits()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.Scheduler == "asyncify" {
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S")
|
|
||||||
}
|
|
||||||
|
|
||||||
return spec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
|
||||||
// No target spec available. Use the default one, useful on most systems
|
|
||||||
// with a regular OS.
|
|
||||||
spec := TargetSpec{
|
|
||||||
Triple: triple,
|
|
||||||
GOOS: goos,
|
|
||||||
GOARCH: goarch,
|
|
||||||
BuildTags: []string{goos, goarch},
|
|
||||||
Scheduler: "tasks",
|
|
||||||
Linker: "cc",
|
|
||||||
DefaultStackSize: 1024 * 64, // 64kB
|
|
||||||
GDB: []string{"gdb"},
|
|
||||||
PortReset: "false",
|
|
||||||
}
|
|
||||||
switch goarch {
|
|
||||||
case "386":
|
|
||||||
spec.CPU = "pentium4"
|
|
||||||
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
||||||
case "amd64":
|
|
||||||
spec.CPU = "x86-64"
|
|
||||||
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
||||||
case "arm":
|
|
||||||
spec.CPU = "generic"
|
|
||||||
switch strings.Split(triple, "-")[0] {
|
|
||||||
case "armv5":
|
|
||||||
spec.Features = "+armv5t,+strict-align,-thumb-mode"
|
|
||||||
case "armv6":
|
|
||||||
spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-thumb-mode"
|
|
||||||
case "armv7":
|
|
||||||
spec.Features = "+armv7-a,+dsp,+fp64,+vfp2,+vfp2sp,+vfp3d16,+vfp3d16sp,-thumb-mode"
|
|
||||||
}
|
|
||||||
case "arm64":
|
|
||||||
spec.CPU = "generic"
|
|
||||||
spec.Features = "+neon"
|
|
||||||
}
|
|
||||||
if goos == "darwin" {
|
|
||||||
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
|
|
||||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
|
||||||
} else if goos == "linux" {
|
|
||||||
spec.Linker = "ld.lld"
|
|
||||||
spec.RTLib = "compiler-rt"
|
|
||||||
spec.Libc = "musl"
|
|
||||||
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
|
|
||||||
} else if goos == "windows" {
|
|
||||||
spec.Linker = "ld.lld"
|
|
||||||
spec.Libc = "mingw-w64"
|
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
|
||||||
"-m", "i386pep",
|
|
||||||
"-Bdynamic",
|
|
||||||
"--image-base", "0x400000",
|
|
||||||
"--gc-sections",
|
|
||||||
"--no-insert-timestamp",
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
|
||||||
}
|
|
||||||
if goarch != "wasm" {
|
|
||||||
suffix := ""
|
|
||||||
if goos == "windows" {
|
|
||||||
// Windows uses a different calling convention from other operating
|
|
||||||
// systems so we need separate assembly files.
|
|
||||||
suffix = "_windows"
|
|
||||||
}
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+suffix+".S")
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
|
|
||||||
}
|
|
||||||
if goarch != runtime.GOARCH {
|
|
||||||
// Some educated guesses as to how to invoke helper programs.
|
|
||||||
spec.GDB = []string{"gdb-multiarch"}
|
|
||||||
if goarch == "arm" && goos == "linux" {
|
|
||||||
spec.Emulator = []string{"qemu-arm"}
|
|
||||||
}
|
|
||||||
if goarch == "arm64" && goos == "linux" {
|
|
||||||
spec.Emulator = []string{"qemu-aarch64"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if goos != runtime.GOOS {
|
|
||||||
if goos == "windows" {
|
|
||||||
spec.Emulator = []string{"wine"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &spec, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LookupGDB looks up a gdb executable.
|
|
||||||
func (spec *TargetSpec) LookupGDB() (string, error) {
|
|
||||||
if len(spec.GDB) == 0 {
|
|
||||||
return "", errors.New("gdb not configured in the target specification")
|
|
||||||
}
|
|
||||||
for _, d := range spec.GDB {
|
|
||||||
_, err := exec.LookPath(d)
|
|
||||||
if err == nil {
|
|
||||||
return d, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
package compileopts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadTarget(t *testing.T) {
|
|
||||||
_, err := LoadTarget(&Options{Target: "arduino"})
|
|
||||||
if err != nil {
|
|
||||||
t.Error("LoadTarget test failed:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = LoadTarget(&Options{Target: "notexist"})
|
|
||||||
if err == nil {
|
|
||||||
t.Error("LoadTarget should have failed with non existing target")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
t.Error("LoadTarget failed for wrong reason:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOverrideProperties(t *testing.T) {
|
|
||||||
baseAutoStackSize := true
|
|
||||||
base := &TargetSpec{
|
|
||||||
GOOS: "baseGoos",
|
|
||||||
CPU: "baseCpu",
|
|
||||||
CFlags: []string{"-base-foo", "-base-bar"},
|
|
||||||
BuildTags: []string{"bt1", "bt2"},
|
|
||||||
Emulator: []string{"be1", "be2"},
|
|
||||||
DefaultStackSize: 42,
|
|
||||||
AutoStackSize: &baseAutoStackSize,
|
|
||||||
}
|
|
||||||
childAutoStackSize := false
|
|
||||||
child := &TargetSpec{
|
|
||||||
GOOS: "",
|
|
||||||
CPU: "chlidCpu",
|
|
||||||
CFlags: []string{"-child-foo", "-child-bar"},
|
|
||||||
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.CFlags, []string{"-base-foo", "-base-bar", "-child-foo", "-child-bar"}) {
|
|
||||||
t.Errorf("Overriding failed : got %v", base.CFlags)
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file defines alias functions for functions that are normally defined in
|
|
||||||
// Go assembly.
|
|
||||||
//
|
|
||||||
// The Go toolchain defines many performance critical functions in assembly
|
|
||||||
// instead of plain Go. This is a problem for TinyGo as it currently (as of
|
|
||||||
// august 2021) is not able to compile these assembly files and even if it
|
|
||||||
// could, it would not be able to make use of them for many targets that are
|
|
||||||
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
|
|
||||||
// functions are aliased to their generic Go implementation.
|
|
||||||
// This results in slower than possible implementations, but at least they are
|
|
||||||
// usable.
|
|
||||||
|
|
||||||
import "tinygo.org/x/go-llvm"
|
|
||||||
|
|
||||||
var stdlibAliases = map[string]string{
|
|
||||||
// crypto packages
|
|
||||||
"crypto/md5.block": "crypto/md5.blockGeneric",
|
|
||||||
"crypto/sha1.block": "crypto/sha1.blockGeneric",
|
|
||||||
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
|
|
||||||
"crypto/sha256.block": "crypto/sha256.blockGeneric",
|
|
||||||
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
|
|
||||||
|
|
||||||
// math package
|
|
||||||
"math.Asin": "math.asin",
|
|
||||||
"math.Asinh": "math.asinh",
|
|
||||||
"math.Acos": "math.acos",
|
|
||||||
"math.Acosh": "math.acosh",
|
|
||||||
"math.Atan": "math.atan",
|
|
||||||
"math.Atanh": "math.atanh",
|
|
||||||
"math.Atan2": "math.atan2",
|
|
||||||
"math.Cbrt": "math.cbrt",
|
|
||||||
"math.Ceil": "math.ceil",
|
|
||||||
"math.archCeil": "math.ceil",
|
|
||||||
"math.Cos": "math.cos",
|
|
||||||
"math.Cosh": "math.cosh",
|
|
||||||
"math.Erf": "math.erf",
|
|
||||||
"math.Erfc": "math.erfc",
|
|
||||||
"math.Exp": "math.exp",
|
|
||||||
"math.archExp": "math.exp",
|
|
||||||
"math.Expm1": "math.expm1",
|
|
||||||
"math.Exp2": "math.exp2",
|
|
||||||
"math.archExp2": "math.exp2",
|
|
||||||
"math.Floor": "math.floor",
|
|
||||||
"math.archFloor": "math.floor",
|
|
||||||
"math.Frexp": "math.frexp",
|
|
||||||
"math.Hypot": "math.hypot",
|
|
||||||
"math.archHypot": "math.hypot",
|
|
||||||
"math.Ldexp": "math.ldexp",
|
|
||||||
"math.Log": "math.log",
|
|
||||||
"math.archLog": "math.log",
|
|
||||||
"math.Log1p": "math.log1p",
|
|
||||||
"math.Log10": "math.log10",
|
|
||||||
"math.Log2": "math.log2",
|
|
||||||
"math.Max": "math.max",
|
|
||||||
"math.archMax": "math.max",
|
|
||||||
"math.Min": "math.min",
|
|
||||||
"math.archMin": "math.min",
|
|
||||||
"math.Mod": "math.mod",
|
|
||||||
"math.Modf": "math.modf",
|
|
||||||
"math.archModf": "math.modf",
|
|
||||||
"math.Pow": "math.pow",
|
|
||||||
"math.Remainder": "math.remainder",
|
|
||||||
"math.Sin": "math.sin",
|
|
||||||
"math.Sinh": "math.sinh",
|
|
||||||
"math.Sqrt": "math.sqrt",
|
|
||||||
"math.archSqrt": "math.sqrt",
|
|
||||||
"math.Tan": "math.tan",
|
|
||||||
"math.Tanh": "math.tanh",
|
|
||||||
"math.Trunc": "math.trunc",
|
|
||||||
"math.archTrunc": "math.trunc",
|
|
||||||
}
|
|
||||||
|
|
||||||
// createAlias implements the function (in the builder) as a call to the alias
|
|
||||||
// function.
|
|
||||||
func (b *builder) createAlias(alias llvm.Value) {
|
|
||||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
|
||||||
b.llvmFn.SetUnnamedAddr(true)
|
|
||||||
|
|
||||||
if b.Debug {
|
|
||||||
if b.fn.Syntax() != nil {
|
|
||||||
// Create debug info file if present.
|
|
||||||
b.difunc = b.attachDebugInfo(b.fn)
|
|
||||||
}
|
|
||||||
pos := b.program.Fset.Position(b.fn.Pos())
|
|
||||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
|
||||||
}
|
|
||||||
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
|
|
||||||
b.SetInsertPointAtEnd(entryBlock)
|
|
||||||
if b.llvmFn.Type() != alias.Type() {
|
|
||||||
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
|
|
||||||
b.CreateUnreachable()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result := b.CreateCall(alias, b.llvmFn.Params(), "")
|
|
||||||
if result.Type().TypeKind() == llvm.VoidTypeKind {
|
|
||||||
b.CreateRetVoid()
|
|
||||||
} else {
|
|
||||||
b.CreateRet(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+93
-217
@@ -4,45 +4,60 @@ package compiler
|
|||||||
// required by the Go programming language.
|
// required by the Go programming language.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
|
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||||
// slice. This is required by the Go language spec: an index out of bounds must
|
// slice. This is required by the Go language spec: an index out of bounds must
|
||||||
// cause a panic.
|
// cause a panic.
|
||||||
// The caller should make sure that index is at least as big as arrayLen.
|
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
|
||||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
|
if frame.fn.IsNoBounds() {
|
||||||
if b.info.nobounds {
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend arrayLen if it's too small.
|
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||||
if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||||
// The index is bigger than the array length type, so extend it.
|
// correctly extend that type.
|
||||||
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
|
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||||
|
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||||
|
} else {
|
||||||
|
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||||
}
|
}
|
||||||
|
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||||
|
// The index is bigger than the array length type, so extend it.
|
||||||
|
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
|
||||||
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
|
||||||
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
|
|
||||||
// Now do the bounds check: index >= arrayLen
|
// Now do the bounds check: index >= arrayLen
|
||||||
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||||
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
|
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||||
|
|
||||||
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
|
c.createRuntimeCall("lookuppanic", nil, "")
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Ok: this is a valid pointer.
|
||||||
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
|
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||||
// sure it is within bounds.
|
// sure it is within bounds.
|
||||||
//
|
//
|
||||||
// This function is both used for slicing a slice (low and high have their
|
// This function is both used for slicing a slice (low and high have their
|
||||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||||
// 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 (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
||||||
if b.info.nobounds {
|
if frame.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
|
||||||
@@ -56,218 +71,79 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
|||||||
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
||||||
capacityType = high.Type()
|
capacityType = high.Type()
|
||||||
}
|
}
|
||||||
if max.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
|
||||||
capacityType = max.Type()
|
|
||||||
}
|
|
||||||
if capacityType != capacity.Type() {
|
if capacityType != capacity.Type() {
|
||||||
capacity = b.CreateZExt(capacity, capacityType, "")
|
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend low and high to be the same size as capacity.
|
// Extend low and high to be the same size as capacity.
|
||||||
low = b.extendInteger(low, lowType, capacityType)
|
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||||
high = b.extendInteger(high, highType, capacityType)
|
if lowType.Info()&types.IsUnsigned != 0 {
|
||||||
max = b.extendInteger(max, maxType, capacityType)
|
low = c.builder.CreateZExt(low, capacityType, "")
|
||||||
|
} else {
|
||||||
|
low = c.builder.CreateSExt(low, capacityType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||||
|
if highType.Info()&types.IsUnsigned != 0 {
|
||||||
|
high = c.builder.CreateZExt(high, capacityType, "")
|
||||||
|
} else {
|
||||||
|
high = c.builder.CreateSExt(high, capacityType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||||
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||||
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
|
|
||||||
// Now do the bounds check: low > high || high > capacity
|
// Now do the bounds check: low > high || high > capacity
|
||||||
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||||
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
||||||
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
||||||
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||||
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
|
||||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
|
c.createRuntimeCall("slicepanic", nil, "")
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Ok: this is a valid pointer.
|
||||||
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
|
||||||
// conversions. This conversion was added in Go 1.17. For details, see:
|
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||||
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
|
|
||||||
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
|
|
||||||
// From the spec:
|
|
||||||
// > If the length of the slice is less than the length of the array, a
|
|
||||||
// > run-time panic occurs.
|
|
||||||
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
|
||||||
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
|
||||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
|
|
||||||
// function must panic if the ptr/len parameters are invalid.
|
|
||||||
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
|
|
||||||
// From the documentation of unsafe.Slice:
|
|
||||||
// > At run time, if len is negative, or if ptr is nil and len is not
|
|
||||||
// > zero, a run-time panic occurs.
|
|
||||||
// However, in practice, it is also necessary to check that the length is
|
|
||||||
// not too big that a GEP wouldn't be possible without wrapping the pointer.
|
|
||||||
// These two checks (non-negative and not too big) can be merged into one
|
|
||||||
// using an unsiged greater than.
|
|
||||||
|
|
||||||
// Make sure the len value is at least as big as a uintptr.
|
|
||||||
len = b.extendInteger(len, lenType, b.uintptrType)
|
|
||||||
|
|
||||||
// Determine the maximum slice size, and therefore the maximum value of the
|
|
||||||
// len parameter.
|
|
||||||
maxSize := b.maxSliceSize(ptr.Type().ElementType())
|
|
||||||
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
|
|
||||||
|
|
||||||
// Do the check. By using unsigned greater than for the length check, signed
|
|
||||||
// negative values are also checked (which are very large numbers when
|
|
||||||
// interpreted as signed values).
|
|
||||||
zero := llvm.ConstInt(len.Type(), 0, false)
|
|
||||||
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
|
|
||||||
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
|
|
||||||
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
|
||||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
|
||||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
|
||||||
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
|
||||||
// check that the value is not too big for runtime.chanMake.
|
|
||||||
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
|
||||||
if b.info.nobounds {
|
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
|
||||||
// checking.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure bufSize is at least as big as maxBufSize (an uintptr).
|
|
||||||
bufSize = b.extendInteger(bufSize, bufSizeType, b.uintptrType)
|
|
||||||
|
|
||||||
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
|
|
||||||
// uintptr if uintptrs were signed.
|
|
||||||
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
|
|
||||||
if elementSize > maxBufSize.ZExtValue() {
|
|
||||||
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Avoid divide-by-zero.
|
|
||||||
if elementSize == 0 {
|
|
||||||
elementSize = 1
|
|
||||||
}
|
|
||||||
// Make the maxBufSize actually the maximum allowed value (in number of
|
|
||||||
// elements in the channel buffer).
|
|
||||||
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
|
|
||||||
|
|
||||||
// Make sure maxBufSize has the same type as bufSize.
|
|
||||||
if maxBufSize.Type() != bufSize.Type() {
|
|
||||||
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the check for a too large (or negative) buffer size.
|
|
||||||
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
|
|
||||||
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createNilCheck checks whether the given pointer is nil, and panics if it is.
|
|
||||||
// It has no effect in well-behaved programs, but makes sure no uncaught nil
|
|
||||||
// pointer dereferences exist in valid Go code.
|
// pointer dereferences exist in valid Go code.
|
||||||
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
|
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||||
// Check whether we need to emit this check at all.
|
// Check whether this is a nil pointer.
|
||||||
if !ptr.IsAGlobalValue().IsNil() {
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||||
return
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||||
}
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
|
|
||||||
switch inst := inst.(type) {
|
|
||||||
case *ssa.Alloc:
|
|
||||||
// An alloc is never nil.
|
|
||||||
return
|
|
||||||
case *ssa.FreeVar:
|
|
||||||
// A free variable is allocated in a parent function and is thus never
|
|
||||||
// nil.
|
|
||||||
return
|
|
||||||
case *ssa.IndexAddr:
|
|
||||||
// This pointer is the result of an index operation into a slice or
|
|
||||||
// array. Such slices/arrays are already bounds checked so the pointer
|
|
||||||
// must be a valid (non-nil) pointer. No nil checking is necessary.
|
|
||||||
return
|
|
||||||
case *ssa.Convert:
|
|
||||||
// This is a pointer that comes from a conversion from unsafe.Pointer.
|
|
||||||
// Don't do nil checking because this is unsafe code and the code should
|
|
||||||
// know what it is doing.
|
|
||||||
// Note: all *ssa.Convert instructions that result in a pointer must
|
|
||||||
// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
|
|
||||||
if inst.X.Type() == types.Typ[types.UnsafePointer] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare against nil.
|
// Compare against nil.
|
||||||
// We previously used a hack to make sure this wouldn't break escape
|
var isnil llvm.Value
|
||||||
// analysis, but this is not necessary anymore since
|
if ptr.Type().PointerAddressSpace() == 0 {
|
||||||
// https://reviews.llvm.org/D60047 has been merged.
|
// Do the nil check using the isnil builtin, which marks the parameter
|
||||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
// as nocapture.
|
||||||
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
// The reason it has to go through a builtin, is that a regular icmp
|
||||||
|
// instruction may capture the pointer in LLVM semantics, see
|
||||||
// Emit the nil check in IR.
|
// https://reviews.llvm.org/D60047 for details. Pointer capturing
|
||||||
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
|
// unfortunately breaks escape analysis, so we use this trick to let the
|
||||||
}
|
// functionattr pass know that this pointer doesn't really escape.
|
||||||
|
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
|
||||||
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
|
||||||
// This function assumes that the shift value is signed.
|
|
||||||
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
|
||||||
if b.info.nobounds {
|
|
||||||
// Function disabled bounds checking - skip shift check.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// isNegative = shift < 0
|
|
||||||
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
|
|
||||||
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
|
|
||||||
// will be emitted. This follows the Go specification which says that a divide
|
|
||||||
// by zero must cause a run time panic.
|
|
||||||
func (b *builder) createDivideByZeroCheck(y llvm.Value) {
|
|
||||||
if b.info.nobounds {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// isZero = y == 0
|
|
||||||
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
|
|
||||||
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRuntimeAssert is a common function to create a new branch on an assert
|
|
||||||
// bool, calling an assert func if the assert value is true (1).
|
|
||||||
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
|
|
||||||
// Check whether we can resolve this check at compile time.
|
|
||||||
if !assert.IsAConstantInt().IsNil() {
|
|
||||||
val := assert.ZExtValue()
|
|
||||||
if val == 0 {
|
|
||||||
// Everything is constant so the check does not have to be emitted
|
|
||||||
// in IR. This avoids emitting some redundant IR.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
|
||||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
|
|
||||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
|
||||||
|
|
||||||
// Now branch to the out-of-bounds or the regular block.
|
|
||||||
b.CreateCondBr(assert, faultBlock, nextBlock)
|
|
||||||
|
|
||||||
// Fail: the assert triggered so panic.
|
|
||||||
b.SetInsertPointAtEnd(faultBlock)
|
|
||||||
b.createRuntimeCall(assertFunc, nil, "")
|
|
||||||
b.CreateUnreachable()
|
|
||||||
|
|
||||||
// Ok: assert didn't trigger so continue normally.
|
|
||||||
b.SetInsertPointAtEnd(nextBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
// extendInteger extends the value to at least targetType using a zero or sign
|
|
||||||
// extend. The resulting value is not truncated: it may still be bigger than
|
|
||||||
// targetType.
|
|
||||||
func (b *builder) extendInteger(value llvm.Value, valueType types.Type, targetType llvm.Type) llvm.Value {
|
|
||||||
if value.Type().IntTypeWidth() < targetType.IntTypeWidth() {
|
|
||||||
if valueType.Underlying().(*types.Basic).Info()&types.IsUnsigned != 0 {
|
|
||||||
// Unsigned, so zero-extend to the target type.
|
|
||||||
value = b.CreateZExt(value, targetType, "")
|
|
||||||
} else {
|
} else {
|
||||||
// Signed, so sign-extend to the target type.
|
// Do the nil check using a regular icmp. This can happen with function
|
||||||
value = b.CreateSExt(value, targetType, "")
|
// pointers on AVR, which don't benefit from escape analysis anyway.
|
||||||
|
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||||
|
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||||
}
|
}
|
||||||
}
|
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
||||||
return value
|
|
||||||
|
// Fail: this is a nil pointer, exit with a panic.
|
||||||
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||||
|
c.createRuntimeCall("nilpanic", nil, "")
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Ok: this is a valid pointer.
|
||||||
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,83 +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") {
|
|
||||||
if strings.HasPrefix(b.Triple, "thumb") {
|
|
||||||
// Work around a bug in LLVM, at least LLVM 11:
|
|
||||||
// https://reviews.llvm.org/D95891
|
|
||||||
// Check for thumbv6m, thumbv7, thumbv7em, 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+52
-209
@@ -1,9 +1,6 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -13,102 +10,59 @@ import (
|
|||||||
|
|
||||||
// The maximum number of arguments that can be expanded from a single struct. If
|
// The maximum number of arguments that can be expanded from a single struct. If
|
||||||
// a struct contains more fields, it is passed as a struct without expanding.
|
// a struct contains more fields, it is passed as a struct without expanding.
|
||||||
const maxFieldsPerParam = 3
|
const MaxFieldsPerParam = 3
|
||||||
|
|
||||||
// paramInfo contains some information collected about a function parameter,
|
// Shortcut: create a call to runtime.<fnName> with the given arguments.
|
||||||
// useful while declaring or defining a function.
|
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||||
type paramInfo struct {
|
runtimePkg := c.ir.Program.ImportedPackage("runtime")
|
||||||
llvmType llvm.Type
|
member := runtimePkg.Members[fnName]
|
||||||
name string // name, possibly with suffixes for e.g. struct fields
|
if member == nil {
|
||||||
flags paramFlags
|
panic("trying to call runtime." + fnName)
|
||||||
|
}
|
||||||
|
fn := c.ir.GetFunction(member.(*ssa.Function))
|
||||||
|
if !fn.IsExported() {
|
||||||
|
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||||
|
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||||
|
}
|
||||||
|
return c.createCall(fn.LLVMFn, args, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// paramFlags identifies parameter attributes for flags. Most importantly, it
|
// Create a call to the given function with the arguments possibly expanded.
|
||||||
// determines which parameters are dereferenceable_or_null and which aren't.
|
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
||||||
type paramFlags uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Parameter may have the deferenceable_or_null attribute. This attribute
|
|
||||||
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
|
|
||||||
paramIsDeferenceableOrNull = 1 << iota
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
|
|
||||||
llvmFn := b.getFunction(fn)
|
|
||||||
if llvmFn.IsNil() {
|
|
||||||
panic("trying to call non-existent function: " + fn.RelString(nil))
|
|
||||||
}
|
|
||||||
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
|
||||||
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
|
||||||
return b.createCall(llvmFn, args, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createCall creates a call to the given function with the arguments possibly
|
|
||||||
// expanded.
|
|
||||||
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
|
||||||
expanded := make([]llvm.Value, 0, len(args))
|
expanded := make([]llvm.Value, 0, len(args))
|
||||||
for _, arg := range args {
|
for _, arg := range args {
|
||||||
fragments := b.expandFormalParam(arg)
|
fragments := c.expandFormalParam(arg)
|
||||||
expanded = append(expanded, fragments...)
|
expanded = append(expanded, fragments...)
|
||||||
}
|
}
|
||||||
return b.CreateCall(fn, expanded, name)
|
return c.builder.CreateCall(fn, expanded, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand an argument type to a list that can be used in a function call
|
// Expand an argument type to a list that can be used in a function call
|
||||||
// parameter list.
|
// paramter list.
|
||||||
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
fields := c.flattenAggregateType(t)
|
||||||
if len(fieldInfos) <= maxFieldsPerParam {
|
if len(fields) <= MaxFieldsPerParam {
|
||||||
// managed to expand this parameter
|
|
||||||
return fieldInfos
|
|
||||||
}
|
|
||||||
// failed to expand this parameter: too many fields
|
|
||||||
}
|
|
||||||
// TODO: split small arrays
|
|
||||||
return []paramInfo{
|
|
||||||
{
|
|
||||||
llvmType: t,
|
|
||||||
name: name,
|
|
||||||
flags: getTypeFlags(goType),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expandFormalParamOffsets returns a list of offsets from the start of an
|
|
||||||
// object of type t after it would have been split up by expandFormalParam. This
|
|
||||||
// is useful for debug information, where it is necessary to know the offset
|
|
||||||
// from the start of the combined object.
|
|
||||||
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
|
||||||
switch t.TypeKind() {
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
fields := b.flattenAggregateTypeOffsets(t)
|
|
||||||
if len(fields) <= maxFieldsPerParam {
|
|
||||||
return fields
|
return fields
|
||||||
} else {
|
} else {
|
||||||
// failed to lower
|
// failed to lower
|
||||||
return []uint64{0}
|
return []llvm.Type{t}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// TODO: split small arrays
|
// TODO: split small arrays
|
||||||
return []uint64{0}
|
return []llvm.Type{t}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandFormalParam splits a formal param value into pieces, so it can be
|
// Equivalent of expandFormalParamType for parameter values.
|
||||||
// passed directly as part of a function call. For example, it splits up small
|
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||||
// structs into individual fields. It is the equivalent of expandFormalParamType
|
|
||||||
// for parameter values.
|
|
||||||
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
|
||||||
switch v.Type().TypeKind() {
|
switch v.Type().TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
|
fieldTypes := c.flattenAggregateType(v.Type())
|
||||||
if len(fieldInfos) <= maxFieldsPerParam {
|
if len(fieldTypes) <= MaxFieldsPerParam {
|
||||||
fields := b.flattenAggregate(v)
|
fields := c.flattenAggregate(v)
|
||||||
if len(fields) != len(fieldInfos) {
|
if len(fields) != len(fieldTypes) {
|
||||||
panic("type and value param lowering don't match")
|
panic("type and value param lowering don't match")
|
||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
@@ -124,133 +78,29 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
|||||||
|
|
||||||
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
||||||
// with the passed in type if this is not possible.
|
// with the passed in type if this is not possible.
|
||||||
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
|
||||||
typeFlags := getTypeFlags(goType)
|
|
||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
var paramInfos []paramInfo
|
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
|
||||||
for i, subfield := range t.StructElementTypes() {
|
for _, subfield := range t.StructElementTypes() {
|
||||||
if c.targetData.TypeAllocSize(subfield) == 0 {
|
subfields := c.flattenAggregateType(subfield)
|
||||||
continue
|
fields = append(fields, subfields...)
|
||||||
}
|
|
||||||
suffix := strconv.Itoa(i)
|
|
||||||
if goType != nil {
|
|
||||||
// Try to come up with a good suffix for this struct field,
|
|
||||||
// depending on which Go type it's based on.
|
|
||||||
switch goType := goType.Underlying().(type) {
|
|
||||||
case *types.Interface:
|
|
||||||
suffix = []string{"typecode", "value"}[i]
|
|
||||||
case *types.Slice:
|
|
||||||
suffix = []string{"data", "len", "cap"}[i]
|
|
||||||
case *types.Struct:
|
|
||||||
suffix = goType.Field(i).Name()
|
|
||||||
case *types.Basic:
|
|
||||||
switch goType.Kind() {
|
|
||||||
case types.Complex64, types.Complex128:
|
|
||||||
suffix = []string{"r", "i"}[i]
|
|
||||||
case types.String:
|
|
||||||
suffix = []string{"data", "len"}[i]
|
|
||||||
}
|
|
||||||
case *types.Signature:
|
|
||||||
suffix = []string{"context", "funcptr"}[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
|
||||||
for i := range subInfos {
|
|
||||||
subInfos[i].flags |= typeFlags
|
|
||||||
}
|
|
||||||
paramInfos = append(paramInfos, subInfos...)
|
|
||||||
}
|
|
||||||
return paramInfos
|
|
||||||
default:
|
|
||||||
return []paramInfo{
|
|
||||||
{
|
|
||||||
llvmType: t,
|
|
||||||
name: name,
|
|
||||||
flags: typeFlags,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTypeFlags returns the type flags for a given type. It will not recurse
|
|
||||||
// into sub-types (such as in structs).
|
|
||||||
func getTypeFlags(t types.Type) paramFlags {
|
|
||||||
if t == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
switch t.Underlying().(type) {
|
|
||||||
case *types.Pointer:
|
|
||||||
// Pointers in Go must either point to an object or be nil.
|
|
||||||
return paramIsDeferenceableOrNull
|
|
||||||
case *types.Chan, *types.Map:
|
|
||||||
// Channels and maps are implemented as pointers pointing to some
|
|
||||||
// object, and follow the same rules as *types.Pointer.
|
|
||||||
return paramIsDeferenceableOrNull
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractSubfield extracts a field from a struct, or returns null if this is
|
|
||||||
// not a struct and thus no subfield can be obtained.
|
|
||||||
func extractSubfield(t types.Type, field int) types.Type {
|
|
||||||
if t == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch t := t.Underlying().(type) {
|
|
||||||
case *types.Struct:
|
|
||||||
return t.Field(field).Type()
|
|
||||||
case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
|
|
||||||
// These Go types are (sometimes) implemented as LLVM structs but can't
|
|
||||||
// really be split further up in Go (with the possible exception of
|
|
||||||
// complex numbers).
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
// This should be unreachable.
|
|
||||||
panic("cannot split subfield: " + t.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// flattenAggregateTypeOffset returns the offsets from the start of an object of
|
|
||||||
// type t if this object were flattened like in flattenAggregate. Used together
|
|
||||||
// with flattenAggregate to know the start indices of each value in the
|
|
||||||
// non-flattened object.
|
|
||||||
//
|
|
||||||
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
|
|
||||||
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
|
||||||
switch t.TypeKind() {
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
var fields []uint64
|
|
||||||
for fieldIndex, field := range t.StructElementTypes() {
|
|
||||||
if c.targetData.TypeAllocSize(field) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
|
||||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
|
||||||
for i := range suboffsets {
|
|
||||||
suboffsets[i] += offset
|
|
||||||
}
|
|
||||||
fields = append(fields, suboffsets...)
|
|
||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
default:
|
default:
|
||||||
return []uint64{0}
|
return []llvm.Type{t}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// flattenAggregate breaks down a struct into its elementary values for argument
|
// Break down a struct into its elementary types for argument passing. The value
|
||||||
// passing. It is the value equivalent of flattenAggregateType
|
// equivalent of flattenAggregateType
|
||||||
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||||
switch v.Type().TypeKind() {
|
switch v.Type().TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
var fields []llvm.Value
|
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
||||||
for i, field := range v.Type().StructElementTypes() {
|
for i := range v.Type().StructElementTypes() {
|
||||||
if b.targetData.TypeAllocSize(field) == 0 {
|
subfield := c.builder.CreateExtractValue(v, i, "")
|
||||||
continue
|
subfields := c.flattenAggregate(subfield)
|
||||||
}
|
|
||||||
subfield := b.CreateExtractValue(v, i, "")
|
|
||||||
subfields := b.flattenAggregate(subfield)
|
|
||||||
fields = append(fields, subfields...)
|
fields = append(fields, subfields...)
|
||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
@@ -259,32 +109,25 @@ func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// collapseFormalParam combines an aggregate object back into the original
|
// Collapse a list of fields into its original value.
|
||||||
// value. This is used to join multiple LLVM parameters into a single Go value
|
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
|
||||||
// in the function entry block.
|
param, remaining := c.collapseFormalParamInternal(t, fields)
|
||||||
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
|
|
||||||
param, remaining := b.collapseFormalParamInternal(t, fields)
|
|
||||||
if len(remaining) != 0 {
|
if len(remaining) != 0 {
|
||||||
panic("failed to expand back all fields")
|
panic("failed to expand back all fields")
|
||||||
}
|
}
|
||||||
return param
|
return param
|
||||||
}
|
}
|
||||||
|
|
||||||
// collapseFormalParamInternal is an implementation detail of
|
// Returns (value, remainingFields). Used by collapseFormalParam.
|
||||||
// collapseFormalParam: it works by recursing until there are no fields left.
|
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||||
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
|
||||||
switch t.TypeKind() {
|
switch t.TypeKind() {
|
||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
flattened := b.flattenAggregateType(t, "", nil)
|
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||||
if len(flattened) <= maxFieldsPerParam {
|
value := c.getZeroValue(t)
|
||||||
value := llvm.ConstNull(t)
|
|
||||||
for i, subtyp := range t.StructElementTypes() {
|
for i, subtyp := range t.StructElementTypes() {
|
||||||
if b.targetData.TypeAllocSize(subtyp) == 0 {
|
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||||
continue
|
|
||||||
}
|
|
||||||
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
|
||||||
fields = remaining
|
fields = remaining
|
||||||
value = b.CreateInsertValue(value, structField, i, "")
|
value = c.builder.CreateInsertValue(value, structField, i, "")
|
||||||
}
|
}
|
||||||
return value, fields
|
return value, fields
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+51
-237
@@ -6,266 +6,80 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
// emitMakeChan returns a new channel value for the given channel type.
|
||||||
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
|
func (c *Compiler) emitMakeChan(expr *ssa.MakeChan) (llvm.Value, error) {
|
||||||
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
|
chanType := c.mod.GetTypeByName("runtime.channel")
|
||||||
bufSize := b.getValue(expr.Size)
|
size := c.targetData.TypeAllocSize(chanType)
|
||||||
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
|
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||||
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
ptr := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "chan.alloc")
|
||||||
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
|
ptr = c.builder.CreateBitCast(ptr, llvm.PointerType(chanType, 0), "chan")
|
||||||
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
return ptr, nil
|
||||||
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
|
|
||||||
}
|
|
||||||
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanSend emits a pseudo chan send operation. It is lowered to the
|
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||||
// actual channel send operation during goroutine lowering.
|
// channel send operation during goroutine lowering.
|
||||||
func (b *builder) createChanSend(instr *ssa.Send) {
|
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||||
ch := b.getValue(instr.Chan)
|
valueType := c.getLLVMType(instr.X.Type())
|
||||||
chanValue := b.getValue(instr.X)
|
ch := c.getValue(frame, instr.Chan)
|
||||||
|
chanValue := c.getValue(frame, instr.X)
|
||||||
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
|
|
||||||
// store value-to-send
|
// store value-to-send
|
||||||
valueType := b.getLLVMType(instr.X.Type())
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
if isZeroSize {
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
c.builder.CreateStore(chanValue, valueAlloca)
|
||||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
} else {
|
|
||||||
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
|
||||||
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}, "")
|
c.createRuntimeCall("chanSend", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
|
|
||||||
// End the lifetime of the allocas.
|
// Make sure CoroSplit includes the alloca in the coroutine frame.
|
||||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
// This is a bit dirty, but it works (at least in LLVM 8).
|
||||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
valueSizeI64 := llvm.ConstInt(c.ctx.Int64Type(), c.targetData.TypeAllocSize(chanValue.Type()), false)
|
||||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
c.builder.CreateCall(c.getLifetimeEndFunc(), []llvm.Value{valueSizeI64, valueAllocaCast}, "")
|
||||||
if !isZeroSize {
|
|
||||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
// emitChanRecv 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 (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||||
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
|
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||||
ch := b.getValue(unop.X)
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
|
ch := c.getValue(frame, unop.X)
|
||||||
|
coroutine := c.createRuntimeCall("getCoroutine", nil, "")
|
||||||
|
|
||||||
// Allocate memory to receive into.
|
// Allocate memory to receive into.
|
||||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
c.builder.SetInsertPointBefore(coroutine.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
valueAlloca := c.builder.CreateAlloca(valueType, "chan.value")
|
||||||
if isZeroSize {
|
c.builder.SetInsertPointBefore(coroutine)
|
||||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
c.builder.SetInsertPointAtEnd(coroutine.InstructionParent())
|
||||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
valueAllocaCast := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "chan.value.i8ptr")
|
||||||
} else {
|
|
||||||
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}, "")
|
c.createRuntimeCall("chanRecv", []llvm.Value{coroutine, ch, valueAllocaCast, valueSize}, "")
|
||||||
var received llvm.Value
|
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||||
if isZeroSize {
|
|
||||||
received = llvm.ConstNull(valueType)
|
|
||||||
} else {
|
|
||||||
received = b.CreateLoad(valueAlloca, "chan.received")
|
|
||||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
|
||||||
}
|
|
||||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
|
||||||
|
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
commaOk := c.createRuntimeCall("getTaskPromiseData", []llvm.Value{coroutine}, "chan.commaOk.wide")
|
||||||
tuple = b.CreateInsertValue(tuple, received, 0, "")
|
commaOk = c.builder.CreateTrunc(commaOk, c.ctx.Int1Type(), "chan.commaOk")
|
||||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||||
|
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||||
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||||
return tuple
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
return received
|
return received
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanClose closes the given channel.
|
// emitChanClose closes the given channel.
|
||||||
func (b *builder) createChanClose(ch llvm.Value) {
|
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
valueType := c.getLLVMType(param.Type().(*types.Chan).Elem())
|
||||||
}
|
valueSize := llvm.ConstInt(c.uintptrType, c.targetData.TypeAllocSize(valueType), false)
|
||||||
|
ch := c.getValue(frame, param)
|
||||||
// createSelect emits all IR necessary for a select statements. That's a
|
c.createRuntimeCall("chanClose", []llvm.Value{ch, valueSize}, "")
|
||||||
// non-trivial amount of code because select is very complex to implement.
|
|
||||||
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
|
||||||
if len(expr.States) == 0 {
|
|
||||||
// Shortcuts for some simple selects.
|
|
||||||
llvmType := b.getLLVMType(expr.Type())
|
|
||||||
if expr.Blocking {
|
|
||||||
// Blocks forever:
|
|
||||||
// select {}
|
|
||||||
b.createRuntimeCall("deadlock", nil, "")
|
|
||||||
return llvm.Undef(llvmType)
|
|
||||||
} else {
|
|
||||||
// No-op:
|
|
||||||
// select {
|
|
||||||
// default:
|
|
||||||
// }
|
|
||||||
retval := llvm.Undef(llvmType)
|
|
||||||
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
|
|
||||||
return retval // {-1, false}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This code create a (stack-allocated) slice containing all the select
|
|
||||||
// cases and then calls runtime.chanSelect to perform the actual select
|
|
||||||
// statement.
|
|
||||||
// Simple selects (blocking and with just one case) are already transformed
|
|
||||||
// into regular chan operations during SSA construction so we don't have to
|
|
||||||
// optimize such small selects.
|
|
||||||
|
|
||||||
// Go through all the cases. Create the selectStates slice and and
|
|
||||||
// determine the receive buffer size and alignment.
|
|
||||||
recvbufSize := uint64(0)
|
|
||||||
recvbufAlign := 0
|
|
||||||
var selectStates []llvm.Value
|
|
||||||
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
|
|
||||||
for _, state := range expr.States {
|
|
||||||
ch := b.getValue(state.Chan)
|
|
||||||
selectState := llvm.ConstNull(chanSelectStateType)
|
|
||||||
selectState = b.CreateInsertValue(selectState, ch, 0, "")
|
|
||||||
switch state.Dir {
|
|
||||||
case types.RecvOnly:
|
|
||||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
|
||||||
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
|
|
||||||
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
|
||||||
recvbufSize = size
|
|
||||||
}
|
|
||||||
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
|
||||||
recvbufAlign = align
|
|
||||||
}
|
|
||||||
case types.SendOnly:
|
|
||||||
// Store this value in an alloca and put a pointer to this alloca
|
|
||||||
// in the send state.
|
|
||||||
sendValue := b.getValue(state.Send)
|
|
||||||
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
|
||||||
b.CreateStore(sendValue, alloca)
|
|
||||||
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
|
|
||||||
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
selectStates = append(selectStates, selectState)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a receive buffer, where the received value will be stored.
|
|
||||||
recvbuf := llvm.Undef(b.i8ptrType)
|
|
||||||
if recvbufSize != 0 {
|
|
||||||
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
|
|
||||||
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
|
||||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
|
||||||
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
}, "select.recvbuf")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the states slice (allocated on the stack).
|
|
||||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
|
||||||
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
|
|
||||||
for i, state := range selectStates {
|
|
||||||
// Set each slice element to the appropriate channel.
|
|
||||||
gep := b.CreateGEP(statesAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
|
||||||
}, "")
|
|
||||||
b.CreateStore(state, gep)
|
|
||||||
}
|
|
||||||
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
}, "select.states")
|
|
||||||
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
|
||||||
|
|
||||||
// Do the select in the runtime.
|
|
||||||
var results llvm.Value
|
|
||||||
if expr.Blocking {
|
|
||||||
// Stack-allocate operation structures.
|
|
||||||
// If these were simply created as a slice, they would heap-allocate.
|
|
||||||
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
|
||||||
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
|
||||||
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
|
||||||
chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
|
||||||
}, "select.block")
|
|
||||||
|
|
||||||
results = b.createRuntimeCall("chanSelect", []llvm.Value{
|
|
||||||
recvbuf,
|
|
||||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
|
||||||
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
|
||||||
}, "select.result")
|
|
||||||
|
|
||||||
// Terminate the lifetime of the operation structures.
|
|
||||||
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
|
||||||
} else {
|
|
||||||
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
|
|
||||||
recvbuf,
|
|
||||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
|
||||||
}, "select.result")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate the lifetime of the states alloca.
|
|
||||||
b.emitLifetimeEnd(statesI8, statesSize)
|
|
||||||
|
|
||||||
// The result value does not include all the possible received values,
|
|
||||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
|
||||||
// instruction will treat a *ssa.Select specially and load it there inline.
|
|
||||||
// Store the receive alloca in a sidetable until we hit this extract
|
|
||||||
// instruction.
|
|
||||||
if b.selectRecvBuf == nil {
|
|
||||||
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
|
||||||
}
|
|
||||||
b.selectRecvBuf[expr] = recvbuf
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
|
||||||
|
|
||||||
// getChanSelectResult returns the special values from a *ssa.Extract expression
|
|
||||||
// when extracting a value from a select statement (*ssa.Select). Because
|
|
||||||
// *ssa.Select cannot load all values in advance, it does this later in the
|
|
||||||
// *ssa.Extract expression.
|
|
||||||
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
|
|
||||||
if expr.Index == 0 {
|
|
||||||
// index
|
|
||||||
value := b.getValue(expr.Tuple)
|
|
||||||
index := b.CreateExtractValue(value, expr.Index, "")
|
|
||||||
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
|
|
||||||
index = b.CreateSExt(index, b.intType, "")
|
|
||||||
}
|
|
||||||
return index
|
|
||||||
} else if expr.Index == 1 {
|
|
||||||
// comma-ok
|
|
||||||
value := b.getValue(expr.Tuple)
|
|
||||||
return b.CreateExtractValue(value, expr.Index, "")
|
|
||||||
} else {
|
|
||||||
// Select statements are (index, ok, ...) where ... is a number of
|
|
||||||
// received values, depending on how many receive statements there
|
|
||||||
// are. They are all combined into one alloca (because only one
|
|
||||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
|
||||||
// it to the correct type, and dereference it.
|
|
||||||
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
|
||||||
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
|
|
||||||
ptr := b.CreateBitCast(recvbuf, typ, "")
|
|
||||||
return b.CreateLoad(ptr, "")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1402
-1680
File diff suppressed because it is too large
Load Diff
@@ -1,216 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pass -update to go test to update the output of the test files.
|
|
||||||
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
file string
|
|
||||||
target string
|
|
||||||
scheduler string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic tests for the compiler. Build some Go files and compare the output with
|
|
||||||
// the expected LLVM IR for regression testing.
|
|
||||||
func TestCompiler(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Check LLVM version.
|
|
||||||
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not parse LLVM version:", llvm.Version)
|
|
||||||
}
|
|
||||||
if llvmMajor < 11 {
|
|
||||||
// It is likely this version needs to be bumped in the future.
|
|
||||||
// The goal is to at least test the LLVM version that's used by default
|
|
||||||
// in TinyGo and (if possible without too many workarounds) also some
|
|
||||||
// previous versions.
|
|
||||||
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []testCase{
|
|
||||||
{"basic.go", "", ""},
|
|
||||||
{"pointer.go", "", ""},
|
|
||||||
{"slice.go", "", ""},
|
|
||||||
{"string.go", "", ""},
|
|
||||||
{"float.go", "", ""},
|
|
||||||
{"interface.go", "", ""},
|
|
||||||
{"func.go", "", "coroutines"},
|
|
||||||
{"pragma.go", "", ""},
|
|
||||||
{"goroutine.go", "wasm", "asyncify"},
|
|
||||||
{"goroutine.go", "wasm", "coroutines"},
|
|
||||||
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
|
||||||
{"channel.go", "", ""},
|
|
||||||
{"intrinsics.go", "cortex-m-qemu", ""},
|
|
||||||
{"intrinsics.go", "wasm", ""},
|
|
||||||
{"gc.go", "", ""},
|
|
||||||
}
|
|
||||||
|
|
||||||
_, minor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not read Go version:", err)
|
|
||||||
}
|
|
||||||
if minor >= 17 {
|
|
||||||
tests = append(tests, testCase{"go1.17.go", "", ""})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tests {
|
|
||||||
name := tc.file
|
|
||||||
targetString := "wasm"
|
|
||||||
if tc.target != "" {
|
|
||||||
targetString = tc.target
|
|
||||||
name += "-" + tc.target
|
|
||||||
}
|
|
||||||
if tc.scheduler != "" {
|
|
||||||
name += "-" + tc.scheduler
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
options := &compileopts.Options{
|
|
||||||
Target: targetString,
|
|
||||||
}
|
|
||||||
target, err := compileopts.LoadTarget(options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to load target:", err)
|
|
||||||
}
|
|
||||||
if tc.scheduler != "" {
|
|
||||||
options.Scheduler = tc.scheduler
|
|
||||||
}
|
|
||||||
config := &compileopts.Config{
|
|
||||||
Options: 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(),
|
|
||||||
DefaultStackSize: config.Target.DefaultStackSize,
|
|
||||||
}
|
|
||||||
machine, err := NewTargetMachine(compilerConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load entire program AST into memory.
|
|
||||||
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
|
|
||||||
Sizes: Sizes(machine),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
err = lprogram.Parse()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not parse test case %s: %s", tc.file, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile AST to IR.
|
|
||||||
program := lprogram.LoadSSA()
|
|
||||||
pkg := lprogram.MainPkg()
|
|
||||||
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
|
||||||
if errs != nil {
|
|
||||||
for _, err := range errs {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize IR a little.
|
|
||||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
|
||||||
defer funcPasses.Dispose()
|
|
||||||
funcPasses.AddInstructionCombiningPass()
|
|
||||||
funcPasses.InitializeFunc()
|
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
||||||
funcPasses.RunFunc(fn)
|
|
||||||
}
|
|
||||||
funcPasses.FinalizeFunc()
|
|
||||||
|
|
||||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
|
||||||
if tc.target != "" {
|
|
||||||
outFilePrefix += "-" + tc.target
|
|
||||||
}
|
|
||||||
if tc.scheduler != "" {
|
|
||||||
outFilePrefix += "-" + tc.scheduler
|
|
||||||
}
|
|
||||||
outPath := "./testdata/" + outFilePrefix + ".ll"
|
|
||||||
|
|
||||||
// Update test if needed. Do not check the result.
|
|
||||||
if *flagUpdate {
|
|
||||||
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
|
|
||||||
if err != nil {
|
|
||||||
t.Error("failed to write updated output file:", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
expected, err := ioutil.ReadFile(outPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to read golden file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fuzzyEqualIR(mod.String(), string(expected)) {
|
|
||||||
t.Errorf("output does not match expected output:\n%s", mod.String())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
|
||||||
// equal. That means, only relevant lines are compared (excluding comments
|
|
||||||
// etc.).
|
|
||||||
func fuzzyEqualIR(s1, s2 string) bool {
|
|
||||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
|
||||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
|
||||||
if len(lines1) != len(lines2) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, line1 := range lines1 {
|
|
||||||
line2 := lines2[i]
|
|
||||||
if line1 != line2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
|
||||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
|
||||||
// stripped out.
|
|
||||||
func filterIrrelevantIRLines(lines []string) []string {
|
|
||||||
var out []string
|
|
||||||
for _, line := range lines {
|
|
||||||
line = strings.Split(line, ";")[0] // strip out comments/info
|
|
||||||
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
|
|
||||||
if line == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "source_filename = ") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, line)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
+115
-267
@@ -14,9 +14,7 @@ package compiler
|
|||||||
// frames.
|
// frames.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
"github.com/tinygo-org/tinygo/ir"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -24,100 +22,65 @@ import (
|
|||||||
// deferInitFunc sets up this function for future deferred calls. It must be
|
// deferInitFunc sets up this function for future deferred calls. It must be
|
||||||
// called from within the entry block when this function contains deferred
|
// called from within the entry block when this function contains deferred
|
||||||
// calls.
|
// calls.
|
||||||
func (b *builder) deferInitFunc() {
|
func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||||
// Some setup.
|
// Some setup.
|
||||||
b.deferFuncs = make(map[*ssa.Function]int)
|
frame.deferFuncs = make(map[*ir.Function]int)
|
||||||
b.deferInvokeFuncs = make(map[string]int)
|
frame.deferInvokeFuncs = make(map[string]int)
|
||||||
b.deferClosureFuncs = make(map[*ssa.Function]int)
|
frame.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(c.mod.GetTypeByName("runtime._defer"), 0)
|
||||||
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
|
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
||||||
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
|
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isInLoop checks if there is a path from a basic block to itself.
|
// emitDefer emits a single defer instruction, to be run when this function
|
||||||
func isInLoop(start *ssa.BasicBlock) bool {
|
|
||||||
// Use a breadth-first search to scan backwards through the block graph.
|
|
||||||
queue := []*ssa.BasicBlock{start}
|
|
||||||
checked := map[*ssa.BasicBlock]struct{}{}
|
|
||||||
|
|
||||||
for len(queue) > 0 {
|
|
||||||
// pop a block off of the queue
|
|
||||||
block := queue[len(queue)-1]
|
|
||||||
queue = queue[:len(queue)-1]
|
|
||||||
|
|
||||||
// Search through predecessors.
|
|
||||||
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
|
|
||||||
// Defers are often placed near the start of the function.
|
|
||||||
for _, pred := range block.Preds {
|
|
||||||
if pred == start {
|
|
||||||
// cycle found
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := checked[pred]; ok {
|
|
||||||
// block already checked
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// add to queue and checked map
|
|
||||||
queue = append(queue, pred)
|
|
||||||
checked[pred] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// createDefer emits a single defer instruction, to be run when this function
|
|
||||||
// returns.
|
// returns.
|
||||||
func (b *builder) createDefer(instr *ssa.Defer) {
|
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||||
// The pointer to the previous defer struct, which we will replace to
|
// The pointer to the previous defer struct, which we will replace to
|
||||||
// make a linked list.
|
// make a linked list.
|
||||||
next := b.CreateLoad(b.deferPtr, "defer.next")
|
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
||||||
|
|
||||||
var values []llvm.Value
|
var values []llvm.Value
|
||||||
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
|
||||||
if instr.Call.IsInvoke() {
|
if instr.Call.IsInvoke() {
|
||||||
// Method call on an interface.
|
// Method call on an interface.
|
||||||
|
|
||||||
// Get callback type number.
|
// Get callback type number.
|
||||||
methodName := instr.Call.Method.FullName()
|
methodName := instr.Call.Method.FullName()
|
||||||
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
|
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
|
||||||
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
|
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
|
||||||
}
|
}
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
|
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), 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, followed by the call parameters).
|
// runtime._defer fields, followed by the call parameters).
|
||||||
itf := b.getValue(instr.Call.Value) // interface
|
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
values = []llvm.Value{callback, next, receiverValue}
|
||||||
values = []llvm.Value{callback, next, typecode, receiverValue}
|
valueTypes = append(valueTypes, c.i8ptrType)
|
||||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
val := b.getValue(arg)
|
val := c.getValue(frame, arg)
|
||||||
values = append(values, val)
|
values = append(values, val)
|
||||||
valueTypes = append(valueTypes, val.Type())
|
valueTypes = append(valueTypes, val.Type())
|
||||||
}
|
}
|
||||||
|
|
||||||
} 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 := c.ir.GetFunction(callee)
|
||||||
b.deferFuncs[callee] = len(b.allDeferFuncs)
|
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, callee)
|
if _, ok := frame.deferFuncs[fn]; !ok {
|
||||||
|
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
|
||||||
|
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
|
||||||
}
|
}
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
|
callback := llvm.ConstInt(c.uintptrType, uint64(frame.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).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam := b.getValue(param)
|
llvmParam := c.getValue(frame, param)
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -129,110 +92,52 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
// pointer.
|
// pointer.
|
||||||
// TODO: ignore this closure entirely and put pointers to the free
|
// TODO: ignore this closure entirely and put pointers to the free
|
||||||
// variables directly in the defer struct, avoiding a memory allocation.
|
// variables directly in the defer struct, avoiding a memory allocation.
|
||||||
closure := b.getValue(instr.Call.Value)
|
closure := c.getValue(frame, instr.Call.Value)
|
||||||
context := b.CreateExtractValue(closure, 0, "")
|
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||||
|
|
||||||
// Get the callback number.
|
// Get the callback number.
|
||||||
fn := makeClosure.Fn.(*ssa.Function)
|
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
|
||||||
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
if _, ok := frame.deferClosureFuncs[fn]; !ok {
|
||||||
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
|
||||||
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
|
||||||
}
|
}
|
||||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
|
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[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, followed by all parameters including the
|
// runtime._defer fields, followed by all parameters including the
|
||||||
// context pointer).
|
// context pointer).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam := b.getValue(param)
|
llvmParam := c.getValue(frame, param)
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
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)
|
c.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.
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFrame := llvm.ConstNull(deferFrameType)
|
deferFrame := c.getZeroValue(deferFrameType)
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
|
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put this struct in an allocation.
|
// Put this struct in an alloca.
|
||||||
var alloca llvm.Value
|
alloca := c.builder.CreateAlloca(deferFrameType, "defer.alloca")
|
||||||
if !isInLoop(instr.Block()) {
|
c.builder.CreateStore(deferFrame, alloca)
|
||||||
// This can safely use a stack allocation.
|
|
||||||
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
|
|
||||||
} else {
|
|
||||||
// This may be hit a variable number of times, so use a heap allocation.
|
|
||||||
size := b.targetData.TypeAllocSize(deferFrameType)
|
|
||||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
|
||||||
nilPtr := llvm.ConstNull(b.i8ptrType)
|
|
||||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
|
||||||
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
|
||||||
}
|
|
||||||
if b.NeedsStackObjects {
|
|
||||||
b.trackPointer(alloca)
|
|
||||||
}
|
|
||||||
b.CreateStore(deferFrame, alloca)
|
|
||||||
|
|
||||||
// Push it on top of the linked list by replacing deferPtr.
|
// Push it on top of the linked list by replacing deferPtr.
|
||||||
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||||
b.CreateStore(allocaCast, b.deferPtr)
|
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRunDefers emits code to run all deferred functions.
|
// emitRunDefers emits code to run all deferred functions.
|
||||||
func (b *builder) createRunDefers() {
|
func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||||
// Add a loop like the following:
|
// Add a loop like the following:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
// _stack := stack
|
// _stack := stack
|
||||||
@@ -249,208 +154,151 @@ func (b *builder) createRunDefers() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// Create loop.
|
// Create loop.
|
||||||
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
|
loophead := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||||
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
|
loop := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||||
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
|
unreachable := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||||
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
|
end := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||||
b.CreateBr(loophead)
|
c.builder.CreateBr(loophead)
|
||||||
|
|
||||||
// Create loop head:
|
// Create loop head:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
b.SetInsertPointAtEnd(loophead)
|
c.builder.SetInsertPointAtEnd(loophead)
|
||||||
deferData := b.CreateLoad(b.deferPtr, "")
|
deferData := c.builder.CreateLoad(frame.deferPtr, "")
|
||||||
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
||||||
b.CreateCondBr(stackIsNil, end, loop)
|
c.builder.CreateCondBr(stackIsNil, end, loop)
|
||||||
|
|
||||||
// Create loop body:
|
// Create loop body:
|
||||||
// _stack := stack
|
// _stack := stack
|
||||||
// stack = stack.next
|
// stack = stack.next
|
||||||
// switch stack.callback {
|
// switch stack.callback {
|
||||||
b.SetInsertPointAtEnd(loop)
|
c.builder.SetInsertPointAtEnd(loop)
|
||||||
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
|
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||||
}, "stack.next.gep")
|
}, "stack.next.gep")
|
||||||
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
|
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||||
b.CreateStore(nextStack, b.deferPtr)
|
c.builder.CreateStore(nextStack, frame.deferPtr)
|
||||||
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||||
}, "callback.gep")
|
}, "callback.gep")
|
||||||
callback := b.CreateLoad(gep, "callback")
|
callback := c.builder.CreateLoad(gep, "callback")
|
||||||
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
|
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
|
||||||
|
|
||||||
for i, callback := range b.allDeferFuncs {
|
for i, callback := range frame.allDeferFuncs {
|
||||||
// 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 := llvm.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||||
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
||||||
b.SetInsertPointAtEnd(block)
|
c.builder.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.
|
||||||
|
if !callback.IsInvoke() {
|
||||||
|
panic("expected an invoke call, not a direct call")
|
||||||
|
}
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0), c.i8ptrType}
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
|
||||||
//Expect funcValue to be passed through the defer frame.
|
|
||||||
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
|
||||||
} else {
|
|
||||||
//Expect typecode
|
|
||||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||||
}
|
}
|
||||||
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
|
||||||
|
|
||||||
// Extract the params from the struct (including receiver).
|
// Extract the params from the struct (including receiver).
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||||
forwardParam := b.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fnPtr llvm.Value
|
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
|
||||||
// Isolate the func value.
|
|
||||||
funcValue := forwardParams[0]
|
|
||||||
forwardParams = forwardParams[1:]
|
|
||||||
|
|
||||||
//Get function pointer and context
|
|
||||||
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
|
|
||||||
fnPtr = fp
|
|
||||||
|
|
||||||
//Pass context
|
|
||||||
forwardParams = append(forwardParams, context)
|
|
||||||
} else {
|
|
||||||
// Move typecode from the start to the end of the list of
|
|
||||||
// parameters.
|
|
||||||
forwardParams = append(forwardParams[1:], forwardParams[0])
|
|
||||||
fnPtr = b.getInvokeFunction(callback)
|
|
||||||
|
|
||||||
// Add the context parameter. An interface call cannot also be a
|
// Add the context parameter. An interface call cannot also be a
|
||||||
// closure but we have to supply the parameter anyway for platforms
|
// closure but we have to supply the parameter anyway for platforms
|
||||||
// with a strict calling convention.
|
// with a strict calling convention.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
}
|
|
||||||
|
|
||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
b.createCall(fnPtr, forwardParams, "")
|
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||||
|
c.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.
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
for _, param := range getParams(callback.Signature) {
|
for _, param := range callback.Params {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||||
}
|
}
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct.
|
// Extract the params from the struct.
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := range getParams(callback.Signature) {
|
for i := range callback.Params {
|
||||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||||
forwardParam := b.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
|
|
||||||
// These parameters should not be supplied when calling into an external C/ASM function.
|
|
||||||
if !b.getFunctionInfo(callback).exported {
|
|
||||||
// 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(c.i8ptrType))
|
||||||
|
|
||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
}
|
|
||||||
|
|
||||||
// Call real function.
|
// Call real function.
|
||||||
b.createCall(b.getFunction(callback), forwardParams, "")
|
c.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 := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.mod.GetTypeByName("runtime._defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||||
}
|
}
|
||||||
valueTypes = append(valueTypes, b.i8ptrType) // closure
|
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct.
|
// Extract the params from the struct.
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||||
forwardParam := b.CreateLoad(gep, "param")
|
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent coroutine handle.
|
// Parent coroutine handle.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
// Call deferred function.
|
// Call deferred function.
|
||||||
b.createCall(b.getFunction(fn), forwardParams, "")
|
c.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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Branch back to the start of the loop.
|
// Branch back to the start of the loop.
|
||||||
b.CreateBr(loophead)
|
c.builder.CreateBr(loophead)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create default unreachable block:
|
// Create default unreachable block:
|
||||||
// default:
|
// default:
|
||||||
// unreachable
|
// unreachable
|
||||||
// }
|
// }
|
||||||
b.SetInsertPointAtEnd(unreachable)
|
c.builder.SetInsertPointAtEnd(unreachable)
|
||||||
b.CreateUnreachable()
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
// End of loop.
|
// End of loop.
|
||||||
b.SetInsertPointAtEnd(end)
|
c.builder.SetInsertPointAtEnd(end)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-39
@@ -1,54 +1,18 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
// This file contains some utility functions related to error handling.
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// makeError makes it easy to create an error from a token.Pos with a message.
|
func (c *Compiler) 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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addError adds a new compiler diagnostic with the given location and message.
|
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||||
func (c *compilerContext) addError(pos token.Pos, msg string) {
|
|
||||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPosition returns the position information for the given value, as far as
|
|
||||||
// it is available.
|
|
||||||
func getPosition(val llvm.Value) token.Position {
|
|
||||||
if !val.IsAInstruction().IsNil() {
|
|
||||||
loc := val.InstructionDebugLoc()
|
|
||||||
if loc.IsNil() {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
file := loc.LocationScope().ScopeFile()
|
|
||||||
return token.Position{
|
|
||||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
|
||||||
Line: int(loc.LocationLine()),
|
|
||||||
Column: int(loc.LocationColumn()),
|
|
||||||
}
|
|
||||||
} else if !val.IsAFunction().IsNil() {
|
|
||||||
loc := val.Subprogram()
|
|
||||||
if loc.IsNil() {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
file := loc.ScopeFile()
|
|
||||||
return token.Position{
|
|
||||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
|
||||||
Line: int(loc.SubprogramLine()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file lowers func values into their final form. This is necessary for
|
||||||
|
// funcValueSwitch, which needs full program analysis.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// funcSignatureInfo keeps information about a single signature and its uses.
|
||||||
|
type funcSignatureInfo struct {
|
||||||
|
sig llvm.Value // *uint8 to identify the signature
|
||||||
|
funcValueWithSignatures []llvm.Value // slice of runtime.funcValueWithSignature
|
||||||
|
}
|
||||||
|
|
||||||
|
// funcWithUses keeps information about a single function used as func value and
|
||||||
|
// the assigned function ID. More commonly used functions are assigned a lower
|
||||||
|
// ID.
|
||||||
|
type funcWithUses struct {
|
||||||
|
funcPtr llvm.Value
|
||||||
|
useCount int // how often this function is used in a func value
|
||||||
|
id int // assigned ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slice to sort functions by their use counts, or else their name if they're
|
||||||
|
// used equally often.
|
||||||
|
type funcWithUsesList []*funcWithUses
|
||||||
|
|
||||||
|
func (l funcWithUsesList) Len() int { return len(l) }
|
||||||
|
func (l funcWithUsesList) Less(i, j int) bool {
|
||||||
|
if l[i].useCount != l[j].useCount {
|
||||||
|
// return the reverse: we want the highest use counts sorted first
|
||||||
|
return l[i].useCount > l[j].useCount
|
||||||
|
}
|
||||||
|
iName := l[i].funcPtr.Name()
|
||||||
|
jName := l[j].funcPtr.Name()
|
||||||
|
return iName < jName
|
||||||
|
}
|
||||||
|
func (l funcWithUsesList) Swap(i, j int) {
|
||||||
|
l[i], l[j] = l[j], l[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// LowerFuncValue lowers the runtime.funcValueWithSignature type and
|
||||||
|
// runtime.getFuncPtr function to their final form.
|
||||||
|
func (c *Compiler) LowerFuncValues() {
|
||||||
|
if c.funcImplementation() != funcValueSwitch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all func values used in the program with their signatures.
|
||||||
|
funcValueWithSignaturePtr := llvm.PointerType(c.mod.GetTypeByName("runtime.funcValueWithSignature"), 0)
|
||||||
|
signatures := map[string]*funcSignatureInfo{}
|
||||||
|
for global := c.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
|
if global.Type() != funcValueWithSignaturePtr {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sig := llvm.ConstExtractValue(global.Initializer(), []uint32{1})
|
||||||
|
name := sig.Name()
|
||||||
|
if info, ok := signatures[name]; ok {
|
||||||
|
info.funcValueWithSignatures = append(info.funcValueWithSignatures, global)
|
||||||
|
} else {
|
||||||
|
signatures[name] = &funcSignatureInfo{
|
||||||
|
sig: sig,
|
||||||
|
funcValueWithSignatures: []llvm.Value{global},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the signatures, for deterministic execution.
|
||||||
|
names := make([]string, 0, len(signatures))
|
||||||
|
for name := range signatures {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
sort.Strings(names)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
info := signatures[name]
|
||||||
|
functions := make(funcWithUsesList, len(info.funcValueWithSignatures))
|
||||||
|
for i, use := range info.funcValueWithSignatures {
|
||||||
|
var useCount int
|
||||||
|
for _, use2 := range getUses(use) {
|
||||||
|
useCount += len(getUses(use2))
|
||||||
|
}
|
||||||
|
functions[i] = &funcWithUses{
|
||||||
|
funcPtr: llvm.ConstExtractValue(use.Initializer(), []uint32{0}).Operand(0),
|
||||||
|
useCount: useCount,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Sort(functions)
|
||||||
|
|
||||||
|
for i, fn := range functions {
|
||||||
|
fn.id = i + 1
|
||||||
|
for _, ptrtoint := range getUses(fn.funcPtr) {
|
||||||
|
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, funcValueWithSignatureConstant := range getUses(ptrtoint) {
|
||||||
|
for _, funcValueWithSignatureGlobal := range getUses(funcValueWithSignatureConstant) {
|
||||||
|
for _, use := range getUses(funcValueWithSignatureGlobal) {
|
||||||
|
if ptrtoint.IsAConstantExpr().IsNil() || ptrtoint.Opcode() != llvm.PtrToInt {
|
||||||
|
panic("expected const ptrtoint")
|
||||||
|
}
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstInt(c.uintptrType, uint64(fn.id), false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, getFuncPtrCall := range getUses(info.sig) {
|
||||||
|
if getFuncPtrCall.IsACallInst().IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if getFuncPtrCall.CalledValue().Name() != "runtime.getFuncPtr" {
|
||||||
|
panic("expected all call uses to be runtime.getFuncPtr")
|
||||||
|
}
|
||||||
|
funcID := getFuncPtrCall.Operand(1)
|
||||||
|
switch len(functions) {
|
||||||
|
case 0:
|
||||||
|
// There are no functions used in a func value that implement
|
||||||
|
// this signature. The only possible value is a nil value.
|
||||||
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
|
panic("expected inttoptr")
|
||||||
|
}
|
||||||
|
nilptr := llvm.ConstPointerNull(inttoptr.Type())
|
||||||
|
inttoptr.ReplaceAllUsesWith(nilptr)
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||||
|
case 1:
|
||||||
|
// There is exactly one function with this signature that is
|
||||||
|
// used in a func value. The func value itself can be either nil
|
||||||
|
// or this one function.
|
||||||
|
c.builder.SetInsertPointBefore(getFuncPtrCall)
|
||||||
|
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||||
|
isnil := c.builder.CreateICmp(llvm.IntEQ, funcID, zero, "")
|
||||||
|
funcPtrNil := llvm.ConstPointerNull(functions[0].funcPtr.Type())
|
||||||
|
funcPtr := c.builder.CreateSelect(isnil, funcPtrNil, functions[0].funcPtr, "")
|
||||||
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
|
panic("expected inttoptr")
|
||||||
|
}
|
||||||
|
inttoptr.ReplaceAllUsesWith(funcPtr)
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||||
|
default:
|
||||||
|
// There are multiple functions used in a func value that
|
||||||
|
// implement this signature.
|
||||||
|
// What we'll do is transform the following:
|
||||||
|
// rawPtr := runtime.getFuncPtr(fn)
|
||||||
|
// if func.rawPtr == nil {
|
||||||
|
// runtime.nilpanic()
|
||||||
|
// }
|
||||||
|
// result := func.rawPtr(...args, func.context)
|
||||||
|
// into this:
|
||||||
|
// if false {
|
||||||
|
// runtime.nilpanic()
|
||||||
|
// }
|
||||||
|
// var result // Phi
|
||||||
|
// switch fn.id {
|
||||||
|
// case 0:
|
||||||
|
// runtime.nilpanic()
|
||||||
|
// case 1:
|
||||||
|
// result = call first implementation...
|
||||||
|
// case 2:
|
||||||
|
// result = call second implementation...
|
||||||
|
// default:
|
||||||
|
// unreachable
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Remove some casts, checks, and the old call which we're going
|
||||||
|
// to replace.
|
||||||
|
var funcCall llvm.Value
|
||||||
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
|
if inttoptr.IsAIntToPtrInst().IsNil() {
|
||||||
|
panic("expected inttoptr")
|
||||||
|
}
|
||||||
|
for _, ptrUse := range getUses(inttoptr) {
|
||||||
|
if !ptrUse.IsABitCastInst().IsNil() {
|
||||||
|
for _, bitcastUse := range getUses(ptrUse) {
|
||||||
|
if bitcastUse.IsACallInst().IsNil() || bitcastUse.CalledValue().Name() != "runtime.isnil" {
|
||||||
|
panic("expected a call to runtime.isnil")
|
||||||
|
}
|
||||||
|
bitcastUse.ReplaceAllUsesWith(llvm.ConstInt(c.ctx.Int1Type(), 0, false))
|
||||||
|
bitcastUse.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
ptrUse.EraseFromParentAsInstruction()
|
||||||
|
} else if !ptrUse.IsACallInst().IsNil() && ptrUse.CalledValue() == inttoptr {
|
||||||
|
if !funcCall.IsNil() {
|
||||||
|
panic("multiple calls on a single runtime.getFuncPtr")
|
||||||
|
}
|
||||||
|
funcCall = ptrUse
|
||||||
|
} else {
|
||||||
|
panic("unexpected getFuncPtrCall")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if funcCall.IsNil() {
|
||||||
|
panic("expected exactly one call use of a runtime.getFuncPtr")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The block that cannot be reached with correct funcValues (to
|
||||||
|
// help the optimizer).
|
||||||
|
c.builder.SetInsertPointBefore(funcCall)
|
||||||
|
defaultBlock := llvm.AddBasicBlock(funcCall.InstructionParent().Parent(), "func.default")
|
||||||
|
c.builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Create the switch.
|
||||||
|
c.builder.SetInsertPointBefore(funcCall)
|
||||||
|
sw := c.builder.CreateSwitch(funcID, defaultBlock, len(functions)+1)
|
||||||
|
|
||||||
|
// Split right after the switch. We will need to insert a few
|
||||||
|
// basic blocks in this gap.
|
||||||
|
nextBlock := c.splitBasicBlock(sw, llvm.NextBasicBlock(sw.InstructionParent()), "func.next")
|
||||||
|
|
||||||
|
// The 0 case, which is actually a nil check.
|
||||||
|
nilBlock := llvm.InsertBasicBlock(nextBlock, "func.nil")
|
||||||
|
c.builder.SetInsertPointAtEnd(nilBlock)
|
||||||
|
c.createRuntimeCall("nilpanic", nil, "")
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
sw.AddCase(llvm.ConstInt(c.uintptrType, 0, false), nilBlock)
|
||||||
|
|
||||||
|
// Gather the list of parameters for every call we're going to
|
||||||
|
// make.
|
||||||
|
callParams := make([]llvm.Value, funcCall.OperandsCount()-1)
|
||||||
|
for i := range callParams {
|
||||||
|
callParams[i] = funcCall.Operand(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the call produces a value, we need to get it using a PHI
|
||||||
|
// node.
|
||||||
|
phiBlocks := make([]llvm.BasicBlock, len(functions))
|
||||||
|
phiValues := make([]llvm.Value, len(functions))
|
||||||
|
for i, fn := range functions {
|
||||||
|
// Insert a switch case.
|
||||||
|
bb := llvm.InsertBasicBlock(nextBlock, "func.call"+strconv.Itoa(fn.id))
|
||||||
|
c.builder.SetInsertPointAtEnd(bb)
|
||||||
|
result := c.builder.CreateCall(fn.funcPtr, callParams, "")
|
||||||
|
c.builder.CreateBr(nextBlock)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(fn.id), false), bb)
|
||||||
|
phiBlocks[i] = bb
|
||||||
|
phiValues[i] = result
|
||||||
|
}
|
||||||
|
// Create the PHI node so that the call result flows into the
|
||||||
|
// next block (after the split). This is only necessary when the
|
||||||
|
// call produced a value.
|
||||||
|
if funcCall.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
c.builder.SetInsertPointBefore(nextBlock.FirstInstruction())
|
||||||
|
phi := c.builder.CreatePHI(funcCall.Type(), "")
|
||||||
|
phi.AddIncoming(phiValues, phiBlocks)
|
||||||
|
funcCall.ReplaceAllUsesWith(phi)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, remove the old instructions.
|
||||||
|
funcCall.EraseFromParentAsInstruction()
|
||||||
|
for _, inttoptr := range getUses(getFuncPtrCall) {
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
getFuncPtrCall.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
-64
@@ -10,33 +10,57 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createFuncValue creates a function value from a raw function pointer with no
|
type funcValueImplementation int
|
||||||
// context.
|
|
||||||
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
const (
|
||||||
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
|
funcValueNone funcValueImplementation = iota
|
||||||
|
|
||||||
|
// A func value is implemented as a pair of pointers:
|
||||||
|
// {context, function pointer}
|
||||||
|
// where the context may be a pointer to a heap-allocated struct containing
|
||||||
|
// the free variables, or it may be undef if the function being pointed to
|
||||||
|
// doesn't need a context. The function pointer is a regular function
|
||||||
|
// pointer.
|
||||||
|
funcValueDoubleword
|
||||||
|
|
||||||
|
// As funcValueDoubleword, but with the function pointer replaced by a
|
||||||
|
// unique ID per function signature. Function values are called by using a
|
||||||
|
// switch statement and choosing which function to call.
|
||||||
|
funcValueSwitch
|
||||||
|
)
|
||||||
|
|
||||||
|
// funcImplementation picks an appropriate func value implementation for the
|
||||||
|
// target.
|
||||||
|
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||||
|
if c.GOARCH == "wasm" {
|
||||||
|
return funcValueSwitch
|
||||||
|
} else {
|
||||||
|
return funcValueDoubleword
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createFuncValue creates a function value from a raw function pointer with no
|
// createFuncValue creates a function value from a raw function pointer with no
|
||||||
// context.
|
// context.
|
||||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
func (c *Compiler) createFuncValue(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 funcValueDoubleword:
|
||||||
// Closure is: {context, function pointer}
|
// Closure is: {context, function pointer}
|
||||||
funcValueScalar = llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
|
funcValueScalar = funcPtr
|
||||||
case "switch":
|
case funcValueSwitch:
|
||||||
|
sigGlobal := c.getFuncSignature(sig)
|
||||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||||
if funcValueWithSignatureGlobal.IsNil() {
|
if funcValueWithSignatureGlobal.IsNil() {
|
||||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
funcValueWithSignatureType := c.mod.GetTypeByName("runtime.funcValueWithSignature")
|
||||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||||
c.getFuncSignatureID(sig),
|
sigGlobal,
|
||||||
})
|
})
|
||||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||||
funcValueWithSignatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
|
||||||
}
|
}
|
||||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||||
default:
|
default:
|
||||||
@@ -44,61 +68,51 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
|
|||||||
}
|
}
|
||||||
funcValueType := c.getFuncType(sig)
|
funcValueType := c.getFuncType(sig)
|
||||||
funcValue := llvm.Undef(funcValueType)
|
funcValue := llvm.Undef(funcValueType)
|
||||||
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
|
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||||
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||||
return funcValue
|
return funcValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFuncSignatureID returns a new external global for a given signature. This
|
// getFuncSignature returns a global for identification of a particular function
|
||||||
// global reference is not real, it is only used during func lowering to assign
|
// signature. It is used in runtime.funcValueWithSignature and in calls to
|
||||||
// signature types to functions and will then be removed.
|
// getFuncPtr.
|
||||||
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
|
func (c *Compiler) getFuncSignature(sig *types.Signature) llvm.Value {
|
||||||
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
|
typeCodeName := getTypeCodeName(sig)
|
||||||
|
sigGlobalName := "reflect/types.type:" + typeCodeName
|
||||||
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||||
if sigGlobal.IsNil() {
|
if sigGlobal.IsNil() {
|
||||||
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||||
|
sigGlobal.SetInitializer(llvm.Undef(c.ctx.Int8Type()))
|
||||||
sigGlobal.SetGlobalConstant(true)
|
sigGlobal.SetGlobalConstant(true)
|
||||||
|
sigGlobal.SetLinkage(llvm.InternalLinkage)
|
||||||
}
|
}
|
||||||
return sigGlobal
|
return sigGlobal
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||||
// a cheap operation.
|
// a cheap operation.
|
||||||
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||||
return b.CreateExtractValue(funcValue, 1, "")
|
return c.builder.CreateExtractValue(funcValue, 1, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractFuncContext extracts the context pointer from this function value. It
|
// extractFuncContext extracts the context pointer from this function value. It
|
||||||
// is a cheap operation.
|
// is a cheap operation.
|
||||||
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||||
return b.CreateExtractValue(funcValue, 0, "")
|
return c.builder.CreateExtractValue(funcValue, 0, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeFuncValue extracts the context and the function pointer from this func
|
// decodeFuncValue extracts the context and the function pointer from this func
|
||||||
// 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 (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value, err error) {
|
||||||
context = b.CreateExtractValue(funcValue, 0, "")
|
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||||
switch b.FuncImplementation {
|
switch c.funcImplementation() {
|
||||||
case "doubleword":
|
case funcValueDoubleword:
|
||||||
bitcast := b.CreateExtractValue(funcValue, 1, "")
|
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||||
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
|
case funcValueSwitch:
|
||||||
funcPtr = bitcast.Operand(0)
|
llvmSig := c.getRawFuncType(sig)
|
||||||
return
|
sigGlobal := c.getFuncSignature(sig)
|
||||||
}
|
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||||
llvmSig := b.getRawFuncType(sig)
|
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||||
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
|
|
||||||
case "switch":
|
|
||||||
if !funcValue.IsAConstant().IsNil() {
|
|
||||||
// If this is a constant func value, the underlying function is
|
|
||||||
// known and can be returned directly.
|
|
||||||
funcValueWithSignatureGlobal := llvm.ConstExtractValue(funcValue, []uint32{1}).Operand(0)
|
|
||||||
funcPtr = llvm.ConstExtractValue(funcValueWithSignatureGlobal.Initializer(), []uint32{0}).Operand(0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
llvmSig := b.getRawFuncType(sig)
|
|
||||||
sigGlobal := b.getFuncSignatureID(sig)
|
|
||||||
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
|
||||||
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
|
|
||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
}
|
}
|
||||||
@@ -106,19 +120,20 @@ 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 *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||||
switch c.FuncImplementation {
|
switch c.funcImplementation() {
|
||||||
case "doubleword":
|
case funcValueDoubleword:
|
||||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
|
rawPtr := c.getRawFuncType(typ)
|
||||||
case "switch":
|
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||||
return c.getLLVMRuntimeType("funcValue")
|
case funcValueSwitch:
|
||||||
|
return c.mod.GetTypeByName("runtime.funcValue")
|
||||||
default:
|
default:
|
||||||
panic("unimplemented func value variant")
|
panic("unimplemented func value variant")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||||
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||||
// Get the return type.
|
// Get the return type.
|
||||||
var returnType llvm.Type
|
var returnType llvm.Type
|
||||||
switch typ.Results().Len() {
|
switch typ.Results().Len() {
|
||||||
@@ -148,15 +163,11 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
|||||||
// The receiver is not an interface, but a i8* type.
|
// The receiver is not an interface, but a i8* type.
|
||||||
recv = c.i8ptrType
|
recv = c.i8ptrType
|
||||||
}
|
}
|
||||||
for _, info := range c.expandFormalParamType(recv, "", nil) {
|
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||||
paramTypes = append(paramTypes, info.llvmType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for i := 0; i < typ.Params().Len(); i++ {
|
for i := 0; i < typ.Params().Len(); i++ {
|
||||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||||
paramTypes = append(paramTypes, info.llvmType)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// All functions take these parameters at the end.
|
// All functions take these parameters at the end.
|
||||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||||
@@ -168,24 +179,24 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
|||||||
|
|
||||||
// parseMakeClosure makes a function value (with context) from the given
|
// parseMakeClosure makes a function value (with context) from the given
|
||||||
// closure expression.
|
// closure expression.
|
||||||
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
func (c *Compiler) parseMakeClosure(frame *Frame, 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 := c.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))
|
||||||
for i, binding := range expr.Bindings {
|
for i, binding := range expr.Bindings {
|
||||||
// The context stores the bound variables.
|
// The context stores the bound variables.
|
||||||
llvmBoundVar := b.getValue(binding)
|
llvmBoundVar := c.getValue(frame, binding)
|
||||||
boundVars[i] = llvmBoundVar
|
boundVars[i] = llvmBoundVar
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the bound variables in a single object, allocating it on the heap
|
// Store the bound variables in a single object, allocating it on the heap
|
||||||
// if necessary.
|
// if necessary.
|
||||||
context := b.emitPointerPack(boundVars)
|
context := c.emitPointerPack(boundVars)
|
||||||
|
|
||||||
// Create the closure.
|
// Create the closure.
|
||||||
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
|
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||||
}
|
}
|
||||||
|
|||||||
-112
@@ -1,112 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file provides IR transformations necessary for precise and portable
|
|
||||||
// garbage collectors.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/token"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
|
|
||||||
// one of the expressions that need this.
|
|
||||||
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
|
|
||||||
// There are uses of this expression, Make sure the pointers
|
|
||||||
// are tracked during GC.
|
|
||||||
switch expr := expr.(type) {
|
|
||||||
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
|
|
||||||
// These values are always of pointer type in IR.
|
|
||||||
b.trackPointer(value)
|
|
||||||
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
|
|
||||||
if !value.IsNil() {
|
|
||||||
b.trackValue(value)
|
|
||||||
}
|
|
||||||
case *ssa.Select:
|
|
||||||
if alloca, ok := b.selectRecvBuf[expr]; ok {
|
|
||||||
if alloca.IsAUndefValue().IsNil() {
|
|
||||||
b.trackPointer(alloca)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ssa.UnOp:
|
|
||||||
switch expr.Op {
|
|
||||||
case token.MUL:
|
|
||||||
// Pointer dereference.
|
|
||||||
b.trackValue(value)
|
|
||||||
case token.ARROW:
|
|
||||||
// Channel receive operator.
|
|
||||||
// It's not necessary to look at commaOk here, because in that
|
|
||||||
// case it's just an aggregate and trackValue will extract the
|
|
||||||
// pointer in there (if there is one).
|
|
||||||
b.trackValue(value)
|
|
||||||
}
|
|
||||||
case *ssa.BinOp:
|
|
||||||
switch expr.Op {
|
|
||||||
case token.ADD:
|
|
||||||
// String concatenation.
|
|
||||||
b.trackValue(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
|
|
||||||
// individual pointers
|
|
||||||
func (b *builder) trackValue(value llvm.Value) {
|
|
||||||
typ := value.Type()
|
|
||||||
switch typ.TypeKind() {
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
b.trackPointer(value)
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
if !typeHasPointers(typ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
numElements := typ.StructElementTypesCount()
|
|
||||||
for i := 0; i < numElements; i++ {
|
|
||||||
subValue := b.CreateExtractValue(value, i, "")
|
|
||||||
b.trackValue(subValue)
|
|
||||||
}
|
|
||||||
case llvm.ArrayTypeKind:
|
|
||||||
if !typeHasPointers(typ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
numElements := typ.ArrayLength()
|
|
||||||
for i := 0; i < numElements; i++ {
|
|
||||||
subValue := b.CreateExtractValue(value, i, "")
|
|
||||||
b.trackValue(subValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
|
|
||||||
// first if needed. The input value must be of LLVM pointer type.
|
|
||||||
func (b *builder) trackPointer(value llvm.Value) {
|
|
||||||
if value.Type() != b.i8ptrType {
|
|
||||||
value = b.CreateBitCast(value, b.i8ptrType, "")
|
|
||||||
}
|
|
||||||
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeHasPointers returns whether this type is a pointer or contains pointers.
|
|
||||||
// If the type is an aggregate type, it will check whether there is a pointer
|
|
||||||
// inside.
|
|
||||||
func typeHasPointers(t llvm.Type) bool {
|
|
||||||
switch t.TypeKind() {
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
return true
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
for _, subType := range t.StructElementTypes() {
|
|
||||||
if typeHasPointers(subType) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
case llvm.ArrayTypeKind:
|
|
||||||
if typeHasPointers(t.ElementType()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,585 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file lowers goroutine pseudo-functions into coroutines scheduled by a
|
||||||
|
// scheduler at runtime. It uses coroutine support in LLVM for this
|
||||||
|
// transformation: https://llvm.org/docs/Coroutines.html
|
||||||
|
//
|
||||||
|
// For example, take the following code:
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// go foo()
|
||||||
|
// time.Sleep(2 * time.Second)
|
||||||
|
// println("some other operation")
|
||||||
|
// i := bar()
|
||||||
|
// println("done", *i)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func foo() {
|
||||||
|
// for {
|
||||||
|
// println("foo!")
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func bar() *int {
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It is transformed by the IR generator in compiler.go into the following
|
||||||
|
// pseudo-Go code:
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// fn := runtime.makeGoroutine(foo)
|
||||||
|
// fn()
|
||||||
|
// time.Sleep(2 * time.Second)
|
||||||
|
// println("some other operation")
|
||||||
|
// i := bar() // imagine an 'await' keyword in front of this call
|
||||||
|
// println("done", *i)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func foo() {
|
||||||
|
// for {
|
||||||
|
// println("foo!")
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func bar() *int {
|
||||||
|
// time.Sleep(time.Second)
|
||||||
|
// println("blocking operation completed)
|
||||||
|
// return new(int)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The pass in this file transforms this code even further, to the following
|
||||||
|
// async/await style pseudocode:
|
||||||
|
//
|
||||||
|
// func main(parent) {
|
||||||
|
// hdl := llvm.makeCoroutine()
|
||||||
|
// foo(nil) // do not pass the parent coroutine: this is an independent goroutine
|
||||||
|
// runtime.sleepTask(hdl, 2 * time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
|
// llvm.suspend(hdl) // suspend point
|
||||||
|
// println("some other operation")
|
||||||
|
// var i *int // allocate space on the stack for the return value
|
||||||
|
// runtime.setTaskPromisePtr(hdl, &i) // store return value alloca in our coroutine promise
|
||||||
|
// bar(hdl) // await, pass a continuation (hdl) to bar
|
||||||
|
// llvm.suspend(hdl) // suspend point, wait for the callee to re-activate
|
||||||
|
// println("done", *i)
|
||||||
|
// runtime.activateTask(parent) // re-activate the parent (nop, there is no parent)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func foo(parent) {
|
||||||
|
// hdl := llvm.makeCoroutine()
|
||||||
|
// for {
|
||||||
|
// println("foo!")
|
||||||
|
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
|
// llvm.suspend(hdl) // suspend point
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func bar(parent) {
|
||||||
|
// hdl := llvm.makeCoroutine()
|
||||||
|
// runtime.sleepTask(hdl, time.Second) // ask the scheduler to re-activate this coroutine at the right time
|
||||||
|
// llvm.suspend(hdl) // suspend point
|
||||||
|
// println("blocking operation completed)
|
||||||
|
// runtime.activateTask(parent) // re-activate the parent coroutine before returning
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The real LLVM code is more complicated, but this is the general idea.
|
||||||
|
//
|
||||||
|
// The LLVM coroutine passes will then process this file further transforming
|
||||||
|
// these three functions into coroutines. Most of the actual work is done by the
|
||||||
|
// scheduler, which runs in the background scheduling all coroutines.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type asyncFunc struct {
|
||||||
|
taskHandle llvm.Value
|
||||||
|
cleanupBlock llvm.BasicBlock
|
||||||
|
suspendBlock llvm.BasicBlock
|
||||||
|
unreachableBlock llvm.BasicBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// LowerGoroutines is a pass called during optimization that transforms the IR
|
||||||
|
// into one where all blocking functions are turned into goroutines and blocking
|
||||||
|
// calls into await calls.
|
||||||
|
func (c *Compiler) LowerGoroutines() error {
|
||||||
|
needsScheduler, err := c.markAsyncFunctions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
uses := getUses(c.mod.NamedFunction("runtime.callMain"))
|
||||||
|
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||||
|
panic("expected exactly 1 call of runtime.callMain, check the entry point")
|
||||||
|
}
|
||||||
|
mainCall := uses[0]
|
||||||
|
|
||||||
|
// Replace call of runtime.callMain() with a real call to main.main(),
|
||||||
|
// optionally followed by a call to runtime.scheduler().
|
||||||
|
c.builder.SetInsertPointBefore(mainCall)
|
||||||
|
realMain := c.mod.NamedFunction(c.ir.MainPkg().Pkg.Path() + ".main")
|
||||||
|
c.builder.CreateCall(realMain, []llvm.Value{llvm.Undef(c.i8ptrType), llvm.ConstPointerNull(c.i8ptrType)}, "")
|
||||||
|
if needsScheduler {
|
||||||
|
c.createRuntimeCall("scheduler", nil, "")
|
||||||
|
}
|
||||||
|
mainCall.EraseFromParentAsInstruction()
|
||||||
|
|
||||||
|
if !needsScheduler {
|
||||||
|
go_scheduler := c.mod.NamedFunction("go_scheduler")
|
||||||
|
if !go_scheduler.IsNil() {
|
||||||
|
// This is the WebAssembly backend.
|
||||||
|
// There is no need to export the go_scheduler function, but it is
|
||||||
|
// still exported. Make sure it is optimized away.
|
||||||
|
go_scheduler.SetLinkage(llvm.InternalLinkage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// main.main was set to external linkage during IR construction. Set it to
|
||||||
|
// internal linkage to enable interprocedural optimizations.
|
||||||
|
realMain.SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.alloc").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.free").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.sleepTask").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.setTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.getTaskPromisePtr").SetLinkage(llvm.InternalLinkage)
|
||||||
|
c.mod.NamedFunction("runtime.scheduler").SetLinkage(llvm.InternalLinkage)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// markAsyncFunctions does the bulk of the work of lowering goroutines. It
|
||||||
|
// determines whether a scheduler is needed, and if it is, it transforms
|
||||||
|
// blocking operations into goroutines and blocking calls into await calls.
|
||||||
|
//
|
||||||
|
// It does the following operations:
|
||||||
|
// * Find all blocking functions.
|
||||||
|
// * Determine whether a scheduler is necessary. If not, it skips the
|
||||||
|
// following operations.
|
||||||
|
// * Transform call instructions into await calls.
|
||||||
|
// * Transform return instructions into final suspends.
|
||||||
|
// * Set up the coroutine frames for async functions.
|
||||||
|
// * Transform blocking calls into their async equivalents.
|
||||||
|
func (c *Compiler) markAsyncFunctions() (needsScheduler bool, err error) {
|
||||||
|
var worklist []llvm.Value
|
||||||
|
|
||||||
|
sleep := c.mod.NamedFunction("time.Sleep")
|
||||||
|
if !sleep.IsNil() {
|
||||||
|
worklist = append(worklist, sleep)
|
||||||
|
}
|
||||||
|
deadlockStub := c.mod.NamedFunction("runtime.deadlockStub")
|
||||||
|
if !deadlockStub.IsNil() {
|
||||||
|
worklist = append(worklist, deadlockStub)
|
||||||
|
}
|
||||||
|
chanSend := c.mod.NamedFunction("runtime.chanSend")
|
||||||
|
if !chanSend.IsNil() {
|
||||||
|
worklist = append(worklist, chanSend)
|
||||||
|
}
|
||||||
|
chanRecv := c.mod.NamedFunction("runtime.chanRecv")
|
||||||
|
if !chanRecv.IsNil() {
|
||||||
|
worklist = append(worklist, chanRecv)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(worklist) == 0 {
|
||||||
|
// There are no blocking operations, so no need to transform anything.
|
||||||
|
return false, c.lowerMakeGoroutineCalls()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all async functions.
|
||||||
|
// Keep reducing this worklist by marking a function as recursively async
|
||||||
|
// from the worklist and pushing all its parents that are non-async.
|
||||||
|
// This is somewhat similar to a worklist in a mark-sweep garbage collector:
|
||||||
|
// the work items are then grey objects.
|
||||||
|
asyncFuncs := make(map[llvm.Value]*asyncFunc)
|
||||||
|
asyncList := make([]llvm.Value, 0, 4)
|
||||||
|
for len(worklist) != 0 {
|
||||||
|
// Pick the topmost.
|
||||||
|
f := worklist[len(worklist)-1]
|
||||||
|
worklist = worklist[:len(worklist)-1]
|
||||||
|
if _, ok := asyncFuncs[f]; ok {
|
||||||
|
continue // already processed
|
||||||
|
}
|
||||||
|
// Add to set of async functions.
|
||||||
|
asyncFuncs[f] = &asyncFunc{}
|
||||||
|
asyncList = append(asyncList, f)
|
||||||
|
|
||||||
|
// Add all callees to the worklist.
|
||||||
|
for _, use := range getUses(f) {
|
||||||
|
if use.IsConstant() && use.Opcode() == llvm.BitCast {
|
||||||
|
bitcastUses := getUses(use)
|
||||||
|
for _, call := range bitcastUses {
|
||||||
|
if call.IsACallInst().IsNil() || call.CalledValue().Name() != "runtime.makeGoroutine" {
|
||||||
|
return false, errors.New("async function " + f.Name() + " incorrectly used in bitcast, expected runtime.makeGoroutine")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This is a go statement. Do not mark the parent as async, as
|
||||||
|
// starting a goroutine is not a blocking operation.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if use.IsACallInst().IsNil() {
|
||||||
|
// Not a call instruction. Maybe a store to a global? In any
|
||||||
|
// case, this requires support for async calls across function
|
||||||
|
// pointers which is not yet supported.
|
||||||
|
return false, errors.New("async function " + f.Name() + " used as function pointer")
|
||||||
|
}
|
||||||
|
parent := use.InstructionParent().Parent()
|
||||||
|
for i := 0; i < use.OperandsCount()-1; i++ {
|
||||||
|
if use.Operand(i) == f {
|
||||||
|
return false, errors.New("async function " + f.Name() + " used as function pointer in " + parent.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
worklist = append(worklist, parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether a scheduler is needed.
|
||||||
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||||
|
if c.GOOS == "js" && strings.HasPrefix(c.Triple, "wasm") {
|
||||||
|
// JavaScript always needs a scheduler, as in general no blocking
|
||||||
|
// operations are possible. Blocking operations block the browser UI,
|
||||||
|
// which is very bad.
|
||||||
|
needsScheduler = true
|
||||||
|
} else {
|
||||||
|
// Only use a scheduler when an async goroutine is started. When the
|
||||||
|
// goroutine is not async (does not do any blocking operation), no
|
||||||
|
// scheduler is necessary as it can be called directly.
|
||||||
|
for _, use := range getUses(makeGoroutine) {
|
||||||
|
// Input param must be const bitcast of function.
|
||||||
|
bitcast := use.Operand(0)
|
||||||
|
if !bitcast.IsConstant() || bitcast.Opcode() != llvm.BitCast {
|
||||||
|
panic("expected const bitcast operand of runtime.makeGoroutine")
|
||||||
|
}
|
||||||
|
goroutine := bitcast.Operand(0)
|
||||||
|
if _, ok := asyncFuncs[goroutine]; ok {
|
||||||
|
needsScheduler = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !needsScheduler {
|
||||||
|
// No scheduler is needed. Do not transform all functions here.
|
||||||
|
// However, make sure that all go calls (which are all non-async) are
|
||||||
|
// transformed into regular calls.
|
||||||
|
return false, c.lowerMakeGoroutineCalls()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a few LLVM intrinsics for coroutine support.
|
||||||
|
|
||||||
|
coroIdType := llvm.FunctionType(c.ctx.TokenType(), []llvm.Type{c.ctx.Int32Type(), c.i8ptrType, c.i8ptrType, c.i8ptrType}, false)
|
||||||
|
coroIdFunc := llvm.AddFunction(c.mod, "llvm.coro.id", coroIdType)
|
||||||
|
|
||||||
|
coroSizeType := llvm.FunctionType(c.ctx.Int32Type(), nil, false)
|
||||||
|
coroSizeFunc := llvm.AddFunction(c.mod, "llvm.coro.size.i32", coroSizeType)
|
||||||
|
|
||||||
|
coroBeginType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||||
|
coroBeginFunc := llvm.AddFunction(c.mod, "llvm.coro.begin", coroBeginType)
|
||||||
|
|
||||||
|
coroSuspendType := llvm.FunctionType(c.ctx.Int8Type(), []llvm.Type{c.ctx.TokenType(), c.ctx.Int1Type()}, false)
|
||||||
|
coroSuspendFunc := llvm.AddFunction(c.mod, "llvm.coro.suspend", coroSuspendType)
|
||||||
|
|
||||||
|
coroEndType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType, c.ctx.Int1Type()}, false)
|
||||||
|
coroEndFunc := llvm.AddFunction(c.mod, "llvm.coro.end", coroEndType)
|
||||||
|
|
||||||
|
coroFreeType := llvm.FunctionType(c.i8ptrType, []llvm.Type{c.ctx.TokenType(), c.i8ptrType}, false)
|
||||||
|
coroFreeFunc := llvm.AddFunction(c.mod, "llvm.coro.free", coroFreeType)
|
||||||
|
|
||||||
|
// Transform all async functions into coroutines.
|
||||||
|
for _, f := range asyncList {
|
||||||
|
if f == sleep || f == deadlockStub || f == chanSend || f == chanRecv {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
frame := asyncFuncs[f]
|
||||||
|
frame.cleanupBlock = c.ctx.AddBasicBlock(f, "task.cleanup")
|
||||||
|
frame.suspendBlock = c.ctx.AddBasicBlock(f, "task.suspend")
|
||||||
|
frame.unreachableBlock = c.ctx.AddBasicBlock(f, "task.unreachable")
|
||||||
|
|
||||||
|
// Scan for async calls and return instructions that need to have
|
||||||
|
// suspend points inserted.
|
||||||
|
var asyncCalls []llvm.Value
|
||||||
|
var returns []llvm.Value
|
||||||
|
for bb := f.EntryBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
|
if !inst.IsACallInst().IsNil() {
|
||||||
|
callee := inst.CalledValue()
|
||||||
|
if _, ok := asyncFuncs[callee]; !ok || callee == sleep || callee == deadlockStub || callee == chanSend || callee == chanRecv {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
asyncCalls = append(asyncCalls, inst)
|
||||||
|
} else if !inst.IsAReturnInst().IsNil() {
|
||||||
|
returns = append(returns, inst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coroutine setup.
|
||||||
|
c.builder.SetInsertPointBefore(f.EntryBasicBlock().FirstInstruction())
|
||||||
|
taskState := c.builder.CreateAlloca(c.mod.GetTypeByName("runtime.taskState"), "task.state")
|
||||||
|
stateI8 := c.builder.CreateBitCast(taskState, c.i8ptrType, "task.state.i8")
|
||||||
|
id := c.builder.CreateCall(coroIdFunc, []llvm.Value{
|
||||||
|
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||||
|
stateI8,
|
||||||
|
llvm.ConstNull(c.i8ptrType),
|
||||||
|
llvm.ConstNull(c.i8ptrType),
|
||||||
|
}, "task.token")
|
||||||
|
size := c.builder.CreateCall(coroSizeFunc, nil, "task.size")
|
||||||
|
if c.targetData.TypeAllocSize(size.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {
|
||||||
|
size = c.builder.CreateTrunc(size, c.uintptrType, "task.size.uintptr")
|
||||||
|
} else if c.targetData.TypeAllocSize(size.Type()) < c.targetData.TypeAllocSize(c.uintptrType) {
|
||||||
|
size = c.builder.CreateZExt(size, c.uintptrType, "task.size.uintptr")
|
||||||
|
}
|
||||||
|
data := c.createRuntimeCall("alloc", []llvm.Value{size}, "task.data")
|
||||||
|
frame.taskHandle = c.builder.CreateCall(coroBeginFunc, []llvm.Value{id, data}, "task.handle")
|
||||||
|
|
||||||
|
// Modify async calls so this function suspends right after the child
|
||||||
|
// returns, because the child is probably not finished yet. Wait until
|
||||||
|
// the child reactivates the parent.
|
||||||
|
for _, inst := range asyncCalls {
|
||||||
|
inst.SetOperand(inst.OperandsCount()-2, frame.taskHandle)
|
||||||
|
|
||||||
|
// Split this basic block.
|
||||||
|
await := c.splitBasicBlock(inst, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.await")
|
||||||
|
|
||||||
|
// Allocate space for the return value.
|
||||||
|
var retvalAlloca llvm.Value
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
c.builder.SetInsertPointBefore(inst.InstructionParent().Parent().EntryBasicBlock().FirstInstruction())
|
||||||
|
retvalAlloca = c.builder.CreateAlloca(inst.Type(), "coro.retvalAlloca")
|
||||||
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
data := c.builder.CreateBitCast(retvalAlloca, c.i8ptrType, "")
|
||||||
|
c.createRuntimeCall("setTaskPromisePtr", []llvm.Value{frame.taskHandle, data}, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Suspend.
|
||||||
|
c.builder.SetInsertPointAtEnd(inst.InstructionParent())
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
|
}, "")
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), await)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
|
||||||
|
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
// Load the return value from the alloca. The callee has
|
||||||
|
// written the return value to it.
|
||||||
|
c.builder.SetInsertPointBefore(await.FirstInstruction())
|
||||||
|
retval := c.builder.CreateLoad(retvalAlloca, "coro.retval")
|
||||||
|
inst.ReplaceAllUsesWith(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace return instructions with suspend points that should
|
||||||
|
// reactivate the parent coroutine.
|
||||||
|
for _, inst := range returns {
|
||||||
|
// These properties were added by the functionattrs pass. Remove
|
||||||
|
// them, because now we start using the parameter.
|
||||||
|
// https://llvm.org/docs/Passes.html#functionattrs-deduce-function-attributes
|
||||||
|
for _, kind := range []string{"nocapture", "readnone"} {
|
||||||
|
kindID := llvm.AttributeKindID(kind)
|
||||||
|
f.RemoveEnumAttributeAtIndex(f.ParamsCount(), kindID)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.builder.SetInsertPointBefore(inst)
|
||||||
|
|
||||||
|
parentHandle := f.LastParam()
|
||||||
|
|
||||||
|
// Store return values.
|
||||||
|
switch inst.OperandsCount() {
|
||||||
|
case 0:
|
||||||
|
// Nothing to return.
|
||||||
|
case 1:
|
||||||
|
// Return this value by writing to the pointer stored in the
|
||||||
|
// parent handle. The parent coroutine has made an alloca that
|
||||||
|
// we can write to to store our return value.
|
||||||
|
returnValuePtr := c.createRuntimeCall("getTaskPromisePtr", []llvm.Value{parentHandle}, "coro.parentData")
|
||||||
|
alloca := c.builder.CreateBitCast(returnValuePtr, llvm.PointerType(inst.Operand(0).Type(), 0), "coro.parentAlloca")
|
||||||
|
c.builder.CreateStore(inst.Operand(0), alloca)
|
||||||
|
default:
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reactivate the parent coroutine. This adds it back to the run
|
||||||
|
// queue, so it is started again by the scheduler when possible
|
||||||
|
// (possibly right after the following suspend).
|
||||||
|
c.createRuntimeCall("activateTask", []llvm.Value{parentHandle}, "")
|
||||||
|
|
||||||
|
// Suspend this coroutine.
|
||||||
|
// It would look like this is unnecessary, but if this
|
||||||
|
// suspend point is left out, it leads to undefined
|
||||||
|
// behavior somehow (with the unreachable instruction).
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 1, false),
|
||||||
|
}, "ret")
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
inst.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coroutine cleanup. Free resources associated with this coroutine.
|
||||||
|
c.builder.SetInsertPointAtEnd(frame.cleanupBlock)
|
||||||
|
mem := c.builder.CreateCall(coroFreeFunc, []llvm.Value{id, frame.taskHandle}, "task.data.free")
|
||||||
|
c.createRuntimeCall("free", []llvm.Value{mem}, "")
|
||||||
|
c.builder.CreateBr(frame.suspendBlock)
|
||||||
|
|
||||||
|
// Coroutine suspend. A call to llvm.coro.suspend() will branch here.
|
||||||
|
c.builder.SetInsertPointAtEnd(frame.suspendBlock)
|
||||||
|
c.builder.CreateCall(coroEndFunc, []llvm.Value{frame.taskHandle, llvm.ConstInt(c.ctx.Int1Type(), 0, false)}, "unused")
|
||||||
|
returnType := f.Type().ElementType().ReturnType()
|
||||||
|
if returnType.TypeKind() == llvm.VoidTypeKind {
|
||||||
|
c.builder.CreateRetVoid()
|
||||||
|
} else {
|
||||||
|
c.builder.CreateRet(llvm.Undef(returnType))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coroutine exit. All final suspends (return instructions) will branch
|
||||||
|
// here.
|
||||||
|
c.builder.SetInsertPointAtEnd(frame.unreachableBlock)
|
||||||
|
c.builder.CreateUnreachable()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace calls to runtime.getCoroutineCall with the coroutine of this
|
||||||
|
// frame.
|
||||||
|
for _, getCoroutineCall := range getUses(c.mod.NamedFunction("runtime.getCoroutine")) {
|
||||||
|
frame := asyncFuncs[getCoroutineCall.InstructionParent().Parent()]
|
||||||
|
getCoroutineCall.ReplaceAllUsesWith(frame.taskHandle)
|
||||||
|
getCoroutineCall.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform calls to time.Sleep() into coroutine suspend points.
|
||||||
|
for _, sleepCall := range getUses(sleep) {
|
||||||
|
// sleepCall must be a call instruction.
|
||||||
|
frame := asyncFuncs[sleepCall.InstructionParent().Parent()]
|
||||||
|
duration := sleepCall.Operand(0)
|
||||||
|
|
||||||
|
// Set task state to TASK_STATE_SLEEP and set the duration.
|
||||||
|
c.builder.SetInsertPointBefore(sleepCall)
|
||||||
|
c.createRuntimeCall("sleepTask", []llvm.Value{frame.taskHandle, duration}, "")
|
||||||
|
|
||||||
|
// Yield to scheduler.
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
|
}, "")
|
||||||
|
wakeup := c.splitBasicBlock(sleepCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup")
|
||||||
|
c.builder.SetInsertPointBefore(sleepCall)
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
sleepCall.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform calls to runtime.deadlockStub into coroutine suspends (without
|
||||||
|
// resume).
|
||||||
|
for _, deadlockCall := range getUses(deadlockStub) {
|
||||||
|
// deadlockCall must be a call instruction.
|
||||||
|
frame := asyncFuncs[deadlockCall.InstructionParent().Parent()]
|
||||||
|
|
||||||
|
// Exit coroutine.
|
||||||
|
c.builder.SetInsertPointBefore(deadlockCall)
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 1, false), // final suspend
|
||||||
|
}, "")
|
||||||
|
c.splitBasicBlock(deadlockCall, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.wakeup.dead")
|
||||||
|
c.builder.SetInsertPointBefore(deadlockCall)
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), frame.unreachableBlock)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
deadlockCall.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform calls to runtime.chanSend into channel send operations.
|
||||||
|
for _, sendOp := range getUses(chanSend) {
|
||||||
|
// sendOp must be a call instruction.
|
||||||
|
frame := asyncFuncs[sendOp.InstructionParent().Parent()]
|
||||||
|
|
||||||
|
// Yield to scheduler.
|
||||||
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(sendOp))
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
|
}, "")
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.sent")
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform calls to runtime.chanRecv into channel receive operations.
|
||||||
|
for _, recvOp := range getUses(chanRecv) {
|
||||||
|
// recvOp must be a call instruction.
|
||||||
|
frame := asyncFuncs[recvOp.InstructionParent().Parent()]
|
||||||
|
|
||||||
|
// Yield to scheduler.
|
||||||
|
c.builder.SetInsertPointBefore(llvm.NextInstruction(recvOp))
|
||||||
|
continuePoint := c.builder.CreateCall(coroSuspendFunc, []llvm.Value{
|
||||||
|
llvm.ConstNull(c.ctx.TokenType()),
|
||||||
|
llvm.ConstInt(c.ctx.Int1Type(), 0, false),
|
||||||
|
}, "")
|
||||||
|
sw := c.builder.CreateSwitch(continuePoint, frame.suspendBlock, 2)
|
||||||
|
wakeup := c.splitBasicBlock(sw, llvm.NextBasicBlock(c.builder.GetInsertBlock()), "task.received")
|
||||||
|
c.builder.SetInsertPointAtEnd(recvOp.InstructionParent())
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 0, false), wakeup)
|
||||||
|
sw.AddCase(llvm.ConstInt(c.ctx.Int8Type(), 1, false), frame.cleanupBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, c.lowerMakeGoroutineCalls()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower runtime.makeGoroutine calls to regular call instructions. This is done
|
||||||
|
// after the regular goroutine transformations. The started goroutines are
|
||||||
|
// either non-blocking (in which case they can be called directly) or blocking,
|
||||||
|
// in which case they will ask the scheduler themselves to be rescheduled.
|
||||||
|
func (c *Compiler) lowerMakeGoroutineCalls() error {
|
||||||
|
// The following Go code:
|
||||||
|
// go startedGoroutine()
|
||||||
|
//
|
||||||
|
// Is translated to the following during IR construction, to preserve the
|
||||||
|
// fact that this function should be called as a new goroutine.
|
||||||
|
// %0 = call i8* @runtime.makeGoroutine(i8* bitcast (void (i8*, i8*)* @main.startedGoroutine to i8*), i8* undef, i8* null)
|
||||||
|
// %1 = bitcast i8* %0 to void (i8*, i8*)*
|
||||||
|
// call void %1(i8* undef, i8* undef)
|
||||||
|
//
|
||||||
|
// This function rewrites it to a direct call:
|
||||||
|
// call void @main.startedGoroutine(i8* undef, i8* null)
|
||||||
|
|
||||||
|
makeGoroutine := c.mod.NamedFunction("runtime.makeGoroutine")
|
||||||
|
for _, goroutine := range getUses(makeGoroutine) {
|
||||||
|
bitcastIn := goroutine.Operand(0)
|
||||||
|
origFunc := bitcastIn.Operand(0)
|
||||||
|
uses := getUses(goroutine)
|
||||||
|
if len(uses) != 1 || uses[0].IsABitCastInst().IsNil() {
|
||||||
|
return errors.New("expected exactly 1 bitcast use of runtime.makeGoroutine")
|
||||||
|
}
|
||||||
|
bitcastOut := uses[0]
|
||||||
|
uses = getUses(bitcastOut)
|
||||||
|
if len(uses) != 1 || uses[0].IsACallInst().IsNil() {
|
||||||
|
return errors.New("expected exactly 1 call use of runtime.makeGoroutine bitcast")
|
||||||
|
}
|
||||||
|
realCall := uses[0]
|
||||||
|
|
||||||
|
// Create call instruction.
|
||||||
|
var params []llvm.Value
|
||||||
|
for i := 0; i < realCall.OperandsCount()-1; i++ {
|
||||||
|
params = append(params, realCall.Operand(i))
|
||||||
|
}
|
||||||
|
params[len(params)-1] = llvm.ConstPointerNull(c.i8ptrType) // parent coroutine handle (must be nil)
|
||||||
|
c.builder.SetInsertPointBefore(realCall)
|
||||||
|
c.builder.CreateCall(origFunc, params, "")
|
||||||
|
realCall.EraseFromParentAsInstruction()
|
||||||
|
bitcastOut.EraseFromParentAsInstruction()
|
||||||
|
goroutine.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file implements the 'go' keyword to start a new goroutine. See
|
|
||||||
// goroutine-lowering.go for more details.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createGo emits code to start a new goroutine.
|
|
||||||
func (b *builder) createGo(instr *ssa.Go) {
|
|
||||||
// Get all function parameters to pass to the goroutine.
|
|
||||||
var params []llvm.Value
|
|
||||||
for _, param := range instr.Call.Args {
|
|
||||||
params = append(params, b.getValue(param))
|
|
||||||
}
|
|
||||||
|
|
||||||
var prefix string
|
|
||||||
var funcPtr llvm.Value
|
|
||||||
hasContext := false
|
|
||||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
|
||||||
// Static callee is known. This makes it easier to start a new
|
|
||||||
// goroutine.
|
|
||||||
var context llvm.Value
|
|
||||||
switch value := instr.Call.Value.(type) {
|
|
||||||
case *ssa.Function:
|
|
||||||
// Goroutine call is regular function call. No context is necessary.
|
|
||||||
if b.Scheduler == "coroutines" {
|
|
||||||
// The context parameter is assumed to be always present in the
|
|
||||||
// coroutines scheduler.
|
|
||||||
context = llvm.Undef(b.i8ptrType)
|
|
||||||
}
|
|
||||||
case *ssa.MakeClosure:
|
|
||||||
// A goroutine call on a func value, but the callee is trivial to find. For
|
|
||||||
// example: immediately applied functions.
|
|
||||||
funcValue := b.getValue(value)
|
|
||||||
context = b.extractFuncContext(funcValue)
|
|
||||||
default:
|
|
||||||
panic("StaticCallee returned an unexpected value")
|
|
||||||
}
|
|
||||||
if !context.IsNil() {
|
|
||||||
params = append(params, context) // context parameter
|
|
||||||
hasContext = true
|
|
||||||
}
|
|
||||||
funcPtr = b.getFunction(callee)
|
|
||||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
|
||||||
// We cheat. None of the builtins do any long or blocking operation, so
|
|
||||||
// we might as well run these builtins right away without the program
|
|
||||||
// noticing the difference.
|
|
||||||
// Possible exceptions:
|
|
||||||
// - copy: this is a possibly long operation, but not a blocking
|
|
||||||
// operation. Semantically it makes no difference to run it right
|
|
||||||
// away (not in a goroutine). However, in practice it makes no sense
|
|
||||||
// to run copy in a goroutine as there is no way to (safely) know
|
|
||||||
// when it is finished.
|
|
||||||
// - panic: the error message would appear in the parent goroutine.
|
|
||||||
// But because `go panic("err")` would halt the program anyway
|
|
||||||
// (there is no recover), panicking right away would give the same
|
|
||||||
// behavior as creating a goroutine, switching the scheduler to that
|
|
||||||
// goroutine, and panicking there. So this optimization seems
|
|
||||||
// correct.
|
|
||||||
// - recover: because it runs in a new goroutine, it is never a
|
|
||||||
// deferred function. Thus this is a no-op.
|
|
||||||
if builtin.Name() == "recover" {
|
|
||||||
// This is a no-op, even in a deferred function:
|
|
||||||
// go recover()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var argTypes []types.Type
|
|
||||||
var argValues []llvm.Value
|
|
||||||
for _, arg := range instr.Call.Args {
|
|
||||||
argTypes = append(argTypes, arg.Type())
|
|
||||||
argValues = append(argValues, b.getValue(arg))
|
|
||||||
}
|
|
||||||
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
|
|
||||||
return
|
|
||||||
} else if instr.Call.IsInvoke() {
|
|
||||||
// This is a method call on an interface value.
|
|
||||||
itf := b.getValue(instr.Call.Value)
|
|
||||||
itfTypeCode := b.CreateExtractValue(itf, 0, "")
|
|
||||||
itfValue := b.CreateExtractValue(itf, 1, "")
|
|
||||||
funcPtr = b.getInvokeFunction(&instr.Call)
|
|
||||||
params = append([]llvm.Value{itfValue}, params...) // start with receiver
|
|
||||||
params = append(params, itfTypeCode) // end with typecode
|
|
||||||
} else {
|
|
||||||
// This is a function pointer.
|
|
||||||
// At the moment, two extra params are passed to the newly started
|
|
||||||
// goroutine:
|
|
||||||
// * The function context, for closures.
|
|
||||||
// * The function pointer (for tasks).
|
|
||||||
var context llvm.Value
|
|
||||||
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
|
|
||||||
params = append(params, context) // context parameter
|
|
||||||
hasContext = true
|
|
||||||
switch b.Scheduler {
|
|
||||||
case "none", "coroutines":
|
|
||||||
// There are no additional parameters needed for the goroutine start operation.
|
|
||||||
case "tasks", "asyncify":
|
|
||||||
// Add the function pointer as a parameter to start the goroutine.
|
|
||||||
params = append(params, funcPtr)
|
|
||||||
default:
|
|
||||||
panic("unknown scheduler type")
|
|
||||||
}
|
|
||||||
prefix = b.fn.RelString(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
paramBundle := b.emitPointerPack(params)
|
|
||||||
var callee, stackSize llvm.Value
|
|
||||||
switch b.Scheduler {
|
|
||||||
case "none", "tasks", "asyncify":
|
|
||||||
callee = b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
|
|
||||||
if b.AutomaticStackSize {
|
|
||||||
// The stack size is not known until after linking. Call a dummy
|
|
||||||
// function that will be replaced with a load from a special ELF
|
|
||||||
// section that contains the stack size (and is modified after
|
|
||||||
// linking).
|
|
||||||
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.
|
|
||||||
if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
|
|
||||||
b.addError(instr.Pos(), "default stack size for goroutines is not set")
|
|
||||||
}
|
|
||||||
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
|
||||||
}
|
|
||||||
case "coroutines":
|
|
||||||
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
|
||||||
// There is no goroutine stack size: coroutines are used instead of
|
|
||||||
// stacks.
|
|
||||||
stackSize = llvm.Undef(b.uintptrType)
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
|
||||||
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
|
||||||
// implementation of goroutines. For example, to call a function like this:
|
|
||||||
//
|
|
||||||
// func add(x, y int) int { ... }
|
|
||||||
//
|
|
||||||
// It creates a wrapper like this:
|
|
||||||
//
|
|
||||||
// func add$gowrapper(ptr *unsafe.Pointer) {
|
|
||||||
// args := (*struct{
|
|
||||||
// x, y int
|
|
||||||
// })(ptr)
|
|
||||||
// add(args.x, args.y)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This is useful because the task-based goroutine start implementation only
|
|
||||||
// allows a single (pointer) argument to the newly started goroutine. Also, it
|
|
||||||
// ignores the return value because newly started goroutines do not have a
|
|
||||||
// return value.
|
|
||||||
//
|
|
||||||
// The hasContext parameter indicates whether the context parameter (the second
|
|
||||||
// to last parameter of the function) is used for this wrapper. If hasContext is
|
|
||||||
// false, the parameter bundle is assumed to have no context parameter and undef
|
|
||||||
// is passed instead.
|
|
||||||
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
|
|
||||||
var wrapper llvm.Value
|
|
||||||
|
|
||||||
builder := c.ctx.NewBuilder()
|
|
||||||
defer builder.Dispose()
|
|
||||||
|
|
||||||
var deadlock llvm.Value
|
|
||||||
if c.Scheduler == "asyncify" {
|
|
||||||
deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
|
|
||||||
}
|
|
||||||
|
|
||||||
if !fn.IsAFunction().IsNil() {
|
|
||||||
// See whether this wrapper has already been created. If so, return it.
|
|
||||||
name := fn.Name()
|
|
||||||
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
|
||||||
if !wrapper.IsNil() {
|
|
||||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the wrapper.
|
|
||||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
|
||||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
|
||||||
c.addStandardAttributes(wrapper)
|
|
||||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
wrapper.SetUnnamedAddr(true)
|
|
||||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
|
||||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
||||||
builder.SetInsertPointAtEnd(entry)
|
|
||||||
|
|
||||||
if c.Debug {
|
|
||||||
pos := c.program.Fset.Position(pos)
|
|
||||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
||||||
File: c.getDIFile(pos.Filename),
|
|
||||||
Parameters: nil, // do not show parameters in debugger
|
|
||||||
Flags: 0, // ?
|
|
||||||
})
|
|
||||||
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
|
||||||
Name: "<goroutine wrapper>",
|
|
||||||
File: c.getDIFile(pos.Filename),
|
|
||||||
Line: pos.Line,
|
|
||||||
Type: diFuncType,
|
|
||||||
LocalToUnit: true,
|
|
||||||
IsDefinition: true,
|
|
||||||
ScopeLine: 0,
|
|
||||||
Flags: llvm.FlagPrototyped,
|
|
||||||
Optimized: true,
|
|
||||||
})
|
|
||||||
wrapper.SetSubprogram(difunc)
|
|
||||||
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the list of params for the call.
|
|
||||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
|
||||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip parentHandle parameter
|
|
||||||
if !hasContext {
|
|
||||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
|
||||||
}
|
|
||||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
|
||||||
if !hasContext {
|
|
||||||
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
|
|
||||||
}
|
|
||||||
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy parentHandle parameter
|
|
||||||
|
|
||||||
// Create the call.
|
|
||||||
builder.CreateCall(fn, params, "")
|
|
||||||
|
|
||||||
if c.Scheduler == "asyncify" {
|
|
||||||
builder.CreateCall(deadlock, []llvm.Value{
|
|
||||||
llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType),
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// For a function pointer like this:
|
|
||||||
//
|
|
||||||
// var funcPtr func(x, y int) int
|
|
||||||
//
|
|
||||||
// A wrapper like the following is created:
|
|
||||||
//
|
|
||||||
// func .gowrapper(ptr *unsafe.Pointer) {
|
|
||||||
// args := (*struct{
|
|
||||||
// x, y int
|
|
||||||
// fn func(x, y int) int
|
|
||||||
// })(ptr)
|
|
||||||
// args.fn(x, y)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// With a bit of luck, identical wrapper functions like these can be
|
|
||||||
// merged into one.
|
|
||||||
|
|
||||||
// Create the wrapper.
|
|
||||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
|
||||||
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
|
||||||
c.addStandardAttributes(wrapper)
|
|
||||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
wrapper.SetUnnamedAddr(true)
|
|
||||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
|
||||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
|
||||||
builder.SetInsertPointAtEnd(entry)
|
|
||||||
|
|
||||||
if c.Debug {
|
|
||||||
pos := c.program.Fset.Position(pos)
|
|
||||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
||||||
File: c.getDIFile(pos.Filename),
|
|
||||||
Parameters: nil, // do not show parameters in debugger
|
|
||||||
Flags: 0, // ?
|
|
||||||
})
|
|
||||||
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
|
||||||
Name: "<goroutine wrapper>",
|
|
||||||
File: c.getDIFile(pos.Filename),
|
|
||||||
Line: pos.Line,
|
|
||||||
Type: diFuncType,
|
|
||||||
LocalToUnit: true,
|
|
||||||
IsDefinition: true,
|
|
||||||
ScopeLine: 0,
|
|
||||||
Flags: llvm.FlagPrototyped,
|
|
||||||
Optimized: true,
|
|
||||||
})
|
|
||||||
wrapper.SetSubprogram(difunc)
|
|
||||||
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the list of parameters, with the extra parameters at the end.
|
|
||||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
|
||||||
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
|
|
||||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
|
||||||
|
|
||||||
// Get the function pointer.
|
|
||||||
fnPtr := params[len(params)-1]
|
|
||||||
|
|
||||||
// The last parameter in the packed object has somewhat of a dual role.
|
|
||||||
// Inside the parameter bundle it's the function pointer, stored right
|
|
||||||
// after the context pointer. But in the IR call instruction, it's the
|
|
||||||
// parentHandle function that's always undef outside of the coroutines
|
|
||||||
// scheduler. Thus, make the parameter undef here.
|
|
||||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
|
||||||
|
|
||||||
// Create the call.
|
|
||||||
builder.CreateCall(fnPtr, params, "")
|
|
||||||
|
|
||||||
if c.Scheduler == "asyncify" {
|
|
||||||
builder.CreateCall(deadlock, []llvm.Value{
|
|
||||||
llvm.Undef(c.i8ptrType), llvm.Undef(c.i8ptrType),
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Scheduler == "asyncify" {
|
|
||||||
// The goroutine was terminated via deadlock.
|
|
||||||
builder.CreateUnreachable()
|
|
||||||
} else {
|
|
||||||
// Finish the function. Every basic block must end in a terminator, and
|
|
||||||
// because goroutines never return a value we can simply return void.
|
|
||||||
builder.CreateRetVoid()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a ptrtoint of the wrapper, not the function itself.
|
|
||||||
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
|
||||||
}
|
|
||||||
+31
-123
@@ -13,6 +13,18 @@ import (
|
|||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This is a compiler builtin, which reads the given register by name:
|
||||||
|
//
|
||||||
|
// func ReadRegister(name string) uintptr
|
||||||
|
//
|
||||||
|
// The register name must be a constant, for example "sp".
|
||||||
|
func (c *Compiler) emitReadRegister(args []ssa.Value) (llvm.Value, error) {
|
||||||
|
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||||
|
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||||
|
target := llvm.InlineAsm(fnType, "mov $0, "+regname, "=r", false, false, 0)
|
||||||
|
return c.builder.CreateCall(target, nil, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
// This is a compiler builtin, which emits a piece of inline assembly with no
|
// This is a compiler builtin, which emits a piece of inline assembly with no
|
||||||
// operands or return values. It is useful for trivial instructions, like wfi in
|
// operands or return values. It is useful for trivial instructions, like wfi in
|
||||||
// ARM or sleep in AVR.
|
// ARM or sleep in AVR.
|
||||||
@@ -20,18 +32,18 @@ import (
|
|||||||
// func Asm(asm string)
|
// func Asm(asm string)
|
||||||
//
|
//
|
||||||
// The provided assembly must be a constant.
|
// The provided assembly must be a constant.
|
||||||
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
|
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
|
||||||
// Magic function: insert inline assembly instead of calling it.
|
// Magic function: insert inline assembly instead of calling it.
|
||||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
|
||||||
asm := constant.StringVal(args[0].(*ssa.Const).Value)
|
asm := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
||||||
return b.CreateCall(target, nil, ""), nil
|
return c.builder.CreateCall(target, nil, ""), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a compiler builtin, which allows assembly to be called in a flexible
|
// This is a compiler builtin, which allows assembly to be called in a flexible
|
||||||
// way.
|
// way.
|
||||||
//
|
//
|
||||||
// func AsmFull(asm string, regs map[string]interface{}) uintptr
|
// func AsmFull(asm string, regs map[string]interface{})
|
||||||
//
|
//
|
||||||
// The asm parameter must be a constant string. The regs parameter must be
|
// The asm parameter must be a constant string. The regs parameter must be
|
||||||
// provided immediately. For example:
|
// provided immediately. For example:
|
||||||
@@ -42,27 +54,27 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
|
|||||||
// "value": 1
|
// "value": 1
|
||||||
// "result": &dest,
|
// "result": &dest,
|
||||||
// })
|
// })
|
||||||
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
|
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||||
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||||
registers := map[string]llvm.Value{}
|
registers := map[string]llvm.Value{}
|
||||||
if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
|
registerMap := instr.Args[1].(*ssa.MakeMap)
|
||||||
for _, r := range *registerMap.Referrers() {
|
for _, r := range *registerMap.Referrers() {
|
||||||
switch r := r.(type) {
|
switch r := r.(type) {
|
||||||
case *ssa.DebugRef:
|
case *ssa.DebugRef:
|
||||||
// ignore
|
// ignore
|
||||||
case *ssa.MapUpdate:
|
case *ssa.MapUpdate:
|
||||||
if r.Block() != registerMap.Block() {
|
if r.Block() != registerMap.Block() {
|
||||||
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||||
}
|
}
|
||||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||||
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
|
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||||
|
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
|
||||||
case *ssa.Call:
|
case *ssa.Call:
|
||||||
if r.Common() == instr {
|
if r.Common() == instr {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: handle dollar signs in asm string
|
// TODO: handle dollar signs in asm string
|
||||||
@@ -71,22 +83,13 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
|||||||
argTypes := []llvm.Type{}
|
argTypes := []llvm.Type{}
|
||||||
args := []llvm.Value{}
|
args := []llvm.Value{}
|
||||||
constraints := []string{}
|
constraints := []string{}
|
||||||
hasOutput := false
|
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||||
asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string {
|
|
||||||
hasOutput = true
|
|
||||||
return "$0"
|
|
||||||
})
|
|
||||||
if hasOutput {
|
|
||||||
constraints = append(constraints, "=&r")
|
|
||||||
registerNumbers[""] = 0
|
|
||||||
}
|
|
||||||
asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string {
|
|
||||||
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
||||||
// instructions.
|
// instructions.
|
||||||
name := s[1 : len(s)-1]
|
name := s[1 : len(s)-1]
|
||||||
if _, ok := registers[name]; !ok {
|
if _, ok := registers[name]; !ok {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = b.makeError(instr.Pos(), "unknown register name: "+name)
|
err = c.makeError(instr.Pos(), "unknown register name: "+name)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
@@ -100,7 +103,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
|||||||
case llvm.PointerTypeKind:
|
case llvm.PointerTypeKind:
|
||||||
constraints = append(constraints, "*m")
|
constraints = append(constraints, "*m")
|
||||||
default:
|
default:
|
||||||
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,21 +112,9 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Value{}, err
|
return llvm.Value{}, err
|
||||||
}
|
}
|
||||||
var outputType llvm.Type
|
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
|
||||||
if hasOutput {
|
|
||||||
outputType = b.uintptrType
|
|
||||||
} else {
|
|
||||||
outputType = b.ctx.VoidType()
|
|
||||||
}
|
|
||||||
fnType := llvm.FunctionType(outputType, argTypes, false)
|
|
||||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||||
result := b.CreateCall(target, args, "")
|
return c.builder.CreateCall(target, args, ""), nil
|
||||||
if hasOutput {
|
|
||||||
return result, nil
|
|
||||||
} else {
|
|
||||||
// Make sure we return something valid.
|
|
||||||
return llvm.ConstInt(b.uintptrType, 0, false), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||||
@@ -137,7 +128,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
|||||||
//
|
//
|
||||||
// The num parameter must be a constant. All other parameters may be any scalar
|
// The num parameter must be a constant. All other parameters may be any scalar
|
||||||
// value supported by LLVM inline assembly.
|
// value supported by LLVM inline assembly.
|
||||||
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
|
||||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||||
llvmArgs := []llvm.Value{}
|
llvmArgs := []llvm.Value{}
|
||||||
argTypes := []llvm.Type{}
|
argTypes := []llvm.Type{}
|
||||||
@@ -150,7 +141,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
|||||||
} else {
|
} else {
|
||||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||||
}
|
}
|
||||||
llvmValue := b.getValue(arg)
|
llvmValue := c.getValue(frame, arg)
|
||||||
llvmArgs = append(llvmArgs, llvmValue)
|
llvmArgs = append(llvmArgs, llvmValue)
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
argTypes = append(argTypes, llvmValue.Type())
|
||||||
}
|
}
|
||||||
@@ -158,90 +149,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
|||||||
// clobbered. r0 is used as an output register so doesn't have to be
|
// clobbered. r0 is used as an output register so doesn't have to be
|
||||||
// marked as clobbered.
|
// marked as clobbered.
|
||||||
constraints += ",~{r1},~{r2},~{r3}"
|
constraints += ",~{r1},~{r2},~{r3}"
|
||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||||
return b.CreateCall(target, llvmArgs, ""), nil
|
return c.builder.CreateCall(target, llvmArgs, ""), nil
|
||||||
}
|
|
||||||
|
|
||||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
|
||||||
// be one of:
|
|
||||||
//
|
|
||||||
// func SVCall0(num uintptr) uintptr
|
|
||||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
|
||||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
|
||||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
|
||||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
|
||||||
//
|
|
||||||
// The num parameter must be a constant. All other parameters may be any scalar
|
|
||||||
// value supported by LLVM inline assembly.
|
|
||||||
// Same as emitSVCall but for AArch64
|
|
||||||
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
|
|
||||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
|
||||||
llvmArgs := []llvm.Value{}
|
|
||||||
argTypes := []llvm.Type{}
|
|
||||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
|
||||||
constraints := "={x0}"
|
|
||||||
for i, arg := range args[1:] {
|
|
||||||
arg = arg.(*ssa.MakeInterface).X
|
|
||||||
if i == 0 {
|
|
||||||
constraints += ",0"
|
|
||||||
} else {
|
|
||||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
|
||||||
}
|
|
||||||
llvmValue := b.getValue(arg)
|
|
||||||
llvmArgs = append(llvmArgs, llvmValue)
|
|
||||||
argTypes = append(argTypes, llvmValue.Type())
|
|
||||||
}
|
|
||||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
|
||||||
// clobbered. x0 is used as an output register so doesn't have to be
|
|
||||||
// marked as clobbered.
|
|
||||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
|
||||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
|
||||||
return b.CreateCall(target, llvmArgs, ""), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a compiler builtin which emits CSR instructions. It can be one of:
|
|
||||||
//
|
|
||||||
// func (csr CSR) Get() uintptr
|
|
||||||
// func (csr CSR) Set(uintptr)
|
|
||||||
// func (csr CSR) SetBits(uintptr) uintptr
|
|
||||||
// func (csr CSR) ClearBits(uintptr) uintptr
|
|
||||||
//
|
|
||||||
// The csr parameter (method receiver) must be a constant. Other parameter can
|
|
||||||
// be any value.
|
|
||||||
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
|
|
||||||
csrConst, ok := call.Args[0].(*ssa.Const)
|
|
||||||
if !ok {
|
|
||||||
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
|
|
||||||
}
|
|
||||||
csr := csrConst.Uint64()
|
|
||||||
switch name := call.StaticCallee().Name(); name {
|
|
||||||
case "Get":
|
|
||||||
// Note that this instruction may have side effects, and thus must be
|
|
||||||
// marked as such.
|
|
||||||
fnType := llvm.FunctionType(b.uintptrType, nil, false)
|
|
||||||
asm := fmt.Sprintf("csrr $0, %d", csr)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
|
|
||||||
return b.CreateCall(target, nil, ""), nil
|
|
||||||
case "Set":
|
|
||||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
|
|
||||||
asm := fmt.Sprintf("csrw %d, $0", csr)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
|
|
||||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
|
||||||
case "SetBits":
|
|
||||||
// Note: it may be possible to optimize this to csrrsi in many cases.
|
|
||||||
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
|
|
||||||
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
|
|
||||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
|
||||||
case "ClearBits":
|
|
||||||
// Note: it may be possible to optimize this to csrrci in many cases.
|
|
||||||
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
|
|
||||||
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
|
|
||||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
|
|
||||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
|
||||||
default:
|
|
||||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,708 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
// This file provides function to lower interface intrinsics to their final LLVM
|
||||||
|
// form, optimizing them in the process.
|
||||||
|
//
|
||||||
|
// During SSA construction, the following pseudo-calls are created:
|
||||||
|
// runtime.typeAssert(typecode, assertedType)
|
||||||
|
// runtime.interfaceImplements(typecode, interfaceMethodSet)
|
||||||
|
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
|
||||||
|
// See src/runtime/interface.go for details.
|
||||||
|
// These calls are to declared but not defined functions, so the optimizer will
|
||||||
|
// leave them alone.
|
||||||
|
//
|
||||||
|
// This pass lowers the above functions to their final form:
|
||||||
|
//
|
||||||
|
// typeAssert:
|
||||||
|
// Replaced with an icmp instruction so it can be directly used in a type
|
||||||
|
// switch. This is very easy to optimize for LLVM: it will often translate a
|
||||||
|
// type switch into a regular switch statement.
|
||||||
|
// When this type assert is not possible (the type is never used in an
|
||||||
|
// interface), this call is replaced with a constant false to optimize the
|
||||||
|
// type assert away completely.
|
||||||
|
//
|
||||||
|
// interfaceImplements:
|
||||||
|
// This call is translated into a call that checks whether the underlying
|
||||||
|
// type is one of the types implementing this interface.
|
||||||
|
// When there is only one type implementing this interface, the check is
|
||||||
|
// replaced with a simple icmp instruction, just like a type assert.
|
||||||
|
// When there is no type at all that implements this interface, it is
|
||||||
|
// replaced with a constant false to optimize it completely.
|
||||||
|
//
|
||||||
|
// interfaceMethod:
|
||||||
|
// This call is replaced with a call to a function that calls the
|
||||||
|
// appropriate method depending on the underlying type.
|
||||||
|
// When there is only one type implementing this interface, this call is
|
||||||
|
// translated into a direct call of that method.
|
||||||
|
// When there is no type implementing this interface, this code is marked
|
||||||
|
// unreachable as there is no way such an interface could be constructed.
|
||||||
|
//
|
||||||
|
// Note that this way of implementing interfaces is very different from how the
|
||||||
|
// main Go compiler implements them. For more details on how the main Go
|
||||||
|
// compiler does it: https://research.swtch.com/interfaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// signatureInfo is a Go signature of an interface method. It does not represent
|
||||||
|
// any method in particular.
|
||||||
|
type signatureInfo struct {
|
||||||
|
name string
|
||||||
|
methods []*methodInfo
|
||||||
|
interfaces []*interfaceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodName takes a method name like "func String()" and returns only the
|
||||||
|
// name, which is "String" in this case.
|
||||||
|
func (s *signatureInfo) methodName() string {
|
||||||
|
if !strings.HasPrefix(s.name, "func ") {
|
||||||
|
panic("signature must start with \"func \"")
|
||||||
|
}
|
||||||
|
methodName := s.name[len("func "):]
|
||||||
|
if openingParen := strings.IndexByte(methodName, '('); openingParen < 0 {
|
||||||
|
panic("no opening paren in signature name")
|
||||||
|
} else {
|
||||||
|
return methodName[:openingParen]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methodInfo describes a single method on a concrete type.
|
||||||
|
type methodInfo struct {
|
||||||
|
*signatureInfo
|
||||||
|
function llvm.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeInfo describes a single concrete Go type, which can be a basic or a named
|
||||||
|
// type. If it is a named type, it may have methods.
|
||||||
|
type typeInfo struct {
|
||||||
|
name string
|
||||||
|
typecode llvm.Value
|
||||||
|
methodSet llvm.Value
|
||||||
|
num uint64 // the type number after lowering
|
||||||
|
countMakeInterfaces int // how often this type is used in an interface
|
||||||
|
countTypeAsserts int // how often a type assert happens on this method
|
||||||
|
methods []*methodInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMethod looks up the method on this type with the given signature and
|
||||||
|
// returns it. The method must exist on this type, otherwise getMethod will
|
||||||
|
// panic.
|
||||||
|
func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||||
|
for _, method := range t.methods {
|
||||||
|
if method.signatureInfo == signature {
|
||||||
|
return method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("could not find method")
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
|
||||||
|
// first.
|
||||||
|
type typeInfoSlice []*typeInfo
|
||||||
|
|
||||||
|
func (t typeInfoSlice) Len() int { return len(t) }
|
||||||
|
func (t typeInfoSlice) Less(i, j int) bool {
|
||||||
|
// Try to sort the most commonly used types first.
|
||||||
|
if t[i].countTypeAsserts != t[j].countTypeAsserts {
|
||||||
|
return t[i].countTypeAsserts < t[j].countTypeAsserts
|
||||||
|
}
|
||||||
|
if t[i].countMakeInterfaces != t[j].countMakeInterfaces {
|
||||||
|
return t[i].countMakeInterfaces < t[j].countMakeInterfaces
|
||||||
|
}
|
||||||
|
return t[i].name < t[j].name
|
||||||
|
}
|
||||||
|
func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
|
||||||
|
|
||||||
|
// interfaceInfo keeps information about a Go interface type, including all
|
||||||
|
// methods it has.
|
||||||
|
type interfaceInfo struct {
|
||||||
|
name string // name with $interface suffix
|
||||||
|
signatures []*signatureInfo // method set
|
||||||
|
types typeInfoSlice // types this interface implements
|
||||||
|
assertFunc llvm.Value // runtime.interfaceImplements replacement
|
||||||
|
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
|
||||||
|
}
|
||||||
|
|
||||||
|
// id removes the $interface suffix from the name and returns the clean
|
||||||
|
// interface name including import path.
|
||||||
|
func (itf *interfaceInfo) id() string {
|
||||||
|
if !strings.HasSuffix(itf.name, "$interface") {
|
||||||
|
panic("interface type does not have $interface suffix: " + itf.name)
|
||||||
|
}
|
||||||
|
return itf.name[:len(itf.name)-len("$interface")]
|
||||||
|
}
|
||||||
|
|
||||||
|
// lowerInterfacesPass keeps state related to the interface lowering pass. The
|
||||||
|
// pass has been implemented as an object type because of its complexity, but
|
||||||
|
// should be seen as a regular function call (see LowerInterfaces).
|
||||||
|
type lowerInterfacesPass struct {
|
||||||
|
*Compiler
|
||||||
|
types map[string]*typeInfo
|
||||||
|
signatures map[string]*signatureInfo
|
||||||
|
interfaces map[string]*interfaceInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lower all interface functions. They are emitted by the compiler as
|
||||||
|
// higher-level intrinsics that need some lowering before LLVM can work on them.
|
||||||
|
// This is done so that a few cleanup passes can run before assigning the final
|
||||||
|
// type codes.
|
||||||
|
func (c *Compiler) LowerInterfaces() {
|
||||||
|
p := &lowerInterfacesPass{
|
||||||
|
Compiler: c,
|
||||||
|
types: make(map[string]*typeInfo),
|
||||||
|
signatures: make(map[string]*signatureInfo),
|
||||||
|
interfaces: make(map[string]*interfaceInfo),
|
||||||
|
}
|
||||||
|
p.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// run runs the pass itself.
|
||||||
|
func (p *lowerInterfacesPass) run() {
|
||||||
|
// Collect all type codes.
|
||||||
|
typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
|
||||||
|
typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
|
||||||
|
var typesInInterfaces []llvm.Value
|
||||||
|
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
|
switch global.Type() {
|
||||||
|
case typecodeIDPtr:
|
||||||
|
// Retrieve Go type information based on an opaque global variable.
|
||||||
|
// Only the name of the global is relevant, the object itself is
|
||||||
|
// discarded afterwards.
|
||||||
|
name := global.Name()
|
||||||
|
t := &typeInfo{
|
||||||
|
name: name,
|
||||||
|
typecode: global,
|
||||||
|
}
|
||||||
|
p.types[name] = t
|
||||||
|
case typeInInterfacePtr:
|
||||||
|
// Count per type how often it is put in an interface. Also, collect
|
||||||
|
// all methods this type has (if it is named).
|
||||||
|
typesInInterfaces = append(typesInInterfaces, global)
|
||||||
|
initializer := global.Initializer()
|
||||||
|
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||||
|
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
|
||||||
|
t := p.types[typecode.Name()]
|
||||||
|
p.addTypeMethods(t, methodSet)
|
||||||
|
|
||||||
|
// Count the number of MakeInterface instructions, for sorting the
|
||||||
|
// typecodes later.
|
||||||
|
t.countMakeInterfaces += len(getUses(global))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count per type how often it is type asserted on (e.g. in a switch
|
||||||
|
// statement).
|
||||||
|
typeAssert := p.mod.NamedFunction("runtime.typeAssert")
|
||||||
|
typeAssertUses := getUses(typeAssert)
|
||||||
|
for _, use := range typeAssertUses {
|
||||||
|
typecode := use.Operand(1)
|
||||||
|
name := typecode.Name()
|
||||||
|
p.types[name].countTypeAsserts++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all interface method calls.
|
||||||
|
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
|
||||||
|
interfaceMethodUses := getUses(interfaceMethod)
|
||||||
|
for _, use := range interfaceMethodUses {
|
||||||
|
methodSet := use.Operand(1).Operand(0)
|
||||||
|
name := methodSet.Name()
|
||||||
|
if _, ok := p.interfaces[name]; !ok {
|
||||||
|
p.addInterface(methodSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all interface type asserts.
|
||||||
|
interfaceImplements := p.mod.NamedFunction("runtime.interfaceImplements")
|
||||||
|
interfaceImplementsUses := getUses(interfaceImplements)
|
||||||
|
for _, use := range interfaceImplementsUses {
|
||||||
|
methodSet := use.Operand(1).Operand(0)
|
||||||
|
name := methodSet.Name()
|
||||||
|
if _, ok := p.interfaces[name]; !ok {
|
||||||
|
p.addInterface(methodSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the interfaces that are implemented per type.
|
||||||
|
for _, t := range p.types {
|
||||||
|
// This type has no methods, so don't spend time calculating them.
|
||||||
|
if len(t.methods) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pre-calculate a set of signatures that this type has, for easy
|
||||||
|
// lookup/check.
|
||||||
|
typeSignatureSet := make(map[*signatureInfo]struct{})
|
||||||
|
for _, method := range t.methods {
|
||||||
|
typeSignatureSet[method.signatureInfo] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A set of interfaces, mapped from the name to the info.
|
||||||
|
// When the name maps to a nil pointer, one of the methods of this type
|
||||||
|
// exists in the given interface but not all of them so this type
|
||||||
|
// doesn't implement the interface.
|
||||||
|
satisfiesInterfaces := make(map[string]*interfaceInfo)
|
||||||
|
|
||||||
|
for _, method := range t.methods {
|
||||||
|
for _, itf := range method.interfaces {
|
||||||
|
if _, ok := satisfiesInterfaces[itf.name]; ok {
|
||||||
|
// interface already checked with a different method
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// check whether this interface satisfies this type
|
||||||
|
satisfies := true
|
||||||
|
for _, itfSignature := range itf.signatures {
|
||||||
|
if _, ok := typeSignatureSet[itfSignature]; !ok {
|
||||||
|
satisfiesInterfaces[itf.name] = nil // does not satisfy
|
||||||
|
satisfies = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !satisfies {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
satisfiesInterfaces[itf.name] = itf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this type to all interfaces that satisfy this type.
|
||||||
|
for _, itf := range satisfiesInterfaces {
|
||||||
|
if itf == nil {
|
||||||
|
// Interface does not implement this type, but one of the
|
||||||
|
// methods on this type also exists on the interface.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
itf.types = append(itf.types, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort all types added to the interfaces, to check for more common types
|
||||||
|
// first.
|
||||||
|
for _, itf := range p.interfaces {
|
||||||
|
sort.Sort(itf.types)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all interface methods with their uses, if possible.
|
||||||
|
for _, use := range interfaceMethodUses {
|
||||||
|
typecode := use.Operand(0)
|
||||||
|
signature := p.signatures[use.Operand(2).Name()]
|
||||||
|
|
||||||
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
|
itf := p.interfaces[methodSet.Name()]
|
||||||
|
if len(itf.types) == 0 {
|
||||||
|
// This method call is impossible: no type implements this
|
||||||
|
// interface. In fact, the previous type assert that got this
|
||||||
|
// interface value should already have returned false.
|
||||||
|
// Replace the function pointer with undef (which will then be
|
||||||
|
// called), indicating to the optimizer this code is unreachable.
|
||||||
|
use.ReplaceAllUsesWith(llvm.Undef(p.uintptrType))
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else if len(itf.types) == 1 {
|
||||||
|
// There is only one implementation of the given type.
|
||||||
|
// Call that function directly.
|
||||||
|
p.replaceInvokeWithCall(use, itf.types[0], signature)
|
||||||
|
} else {
|
||||||
|
// There are multiple types implementing this interface, thus there
|
||||||
|
// are multiple possible functions to call. Delegate calling the
|
||||||
|
// right function to a special wrapper function.
|
||||||
|
inttoptrs := getUses(use)
|
||||||
|
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||||
|
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
inttoptr := inttoptrs[0]
|
||||||
|
calls := getUses(inttoptr)
|
||||||
|
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
|
||||||
|
panic("expected exactly one call use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
call := calls[0]
|
||||||
|
|
||||||
|
// Set up parameters for the call. First copy the regular params...
|
||||||
|
params := make([]llvm.Value, call.OperandsCount())
|
||||||
|
paramTypes := make([]llvm.Type, len(params))
|
||||||
|
for i := 0; i < len(params)-1; i++ {
|
||||||
|
params[i] = call.Operand(i)
|
||||||
|
paramTypes[i] = params[i].Type()
|
||||||
|
}
|
||||||
|
// then add the typecode to the end of the list.
|
||||||
|
params[len(params)-1] = typecode
|
||||||
|
paramTypes[len(params)-1] = p.uintptrType
|
||||||
|
|
||||||
|
// Create a function that redirects the call to the destination
|
||||||
|
// call, after selecting the right concrete type.
|
||||||
|
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
|
||||||
|
|
||||||
|
// Replace the old lookup/inttoptr/call with the new call.
|
||||||
|
p.builder.SetInsertPointBefore(call)
|
||||||
|
retval := p.builder.CreateCall(redirector, params, "")
|
||||||
|
if retval.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
call.ReplaceAllUsesWith(retval)
|
||||||
|
}
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all typeasserts on interface types with matches on their concrete
|
||||||
|
// types, if possible.
|
||||||
|
for _, use := range interfaceImplementsUses {
|
||||||
|
actualType := use.Operand(0)
|
||||||
|
|
||||||
|
methodSet := use.Operand(1).Operand(0) // global variable
|
||||||
|
itf := p.interfaces[methodSet.Name()]
|
||||||
|
if len(itf.types) == 0 {
|
||||||
|
// There are no types implementing this interface, so this assert
|
||||||
|
// can never succeed.
|
||||||
|
// Signal this to the optimizer by branching on constant false. It
|
||||||
|
// should remove the "then" block.
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else if len(itf.types) == 1 {
|
||||||
|
// There is only one type implementing this interface.
|
||||||
|
// Transform this interface assert into comparison against a
|
||||||
|
// constant.
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
assertedType := p.builder.CreatePtrToInt(itf.types[0].typecode, p.uintptrType, "typeassert.typecode")
|
||||||
|
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
} else {
|
||||||
|
// There are multiple possible types implementing this interface.
|
||||||
|
// Create a function that does a type switch on all available types
|
||||||
|
// that implement this interface.
|
||||||
|
fn := p.getInterfaceImplementsFunc(itf)
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok")
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a slice of types sorted by frequency of use.
|
||||||
|
typeSlice := make(typeInfoSlice, 0, len(p.types))
|
||||||
|
for _, t := range p.types {
|
||||||
|
typeSlice = append(typeSlice, t)
|
||||||
|
}
|
||||||
|
sort.Sort(sort.Reverse(typeSlice))
|
||||||
|
|
||||||
|
// A type code must fit in 16 bits.
|
||||||
|
if len(typeSlice) >= 1<<16 {
|
||||||
|
panic("typecode does not fit in a uint16: too many types in this program")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign a type code for each type.
|
||||||
|
p.assignTypeCodes(typeSlice)
|
||||||
|
|
||||||
|
// Replace each use of a runtime.typeInInterface with the constant type
|
||||||
|
// code.
|
||||||
|
for _, global := range typesInInterfaces {
|
||||||
|
for _, use := range getUses(global) {
|
||||||
|
t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
|
||||||
|
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
||||||
|
use.ReplaceAllUsesWith(typecode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace each type assert with an actual type comparison or (if the type
|
||||||
|
// assert is impossible) the constant false.
|
||||||
|
for _, use := range typeAssertUses {
|
||||||
|
actualType := use.Operand(0)
|
||||||
|
assertedTypeGlobal := use.Operand(1)
|
||||||
|
t := p.types[assertedTypeGlobal.Name()]
|
||||||
|
var commaOk llvm.Value
|
||||||
|
if t.countMakeInterfaces == 0 {
|
||||||
|
// impossible type assert: optimize accordingly
|
||||||
|
commaOk = llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||||
|
} else {
|
||||||
|
// regular type assert
|
||||||
|
p.builder.SetInsertPointBefore(use)
|
||||||
|
commaOk = p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(assertedTypeGlobal, p.uintptrType), actualType, "typeassert.ok")
|
||||||
|
}
|
||||||
|
use.ReplaceAllUsesWith(commaOk)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill in each helper function for type asserts on interfaces
|
||||||
|
// (interface-to-interface matches).
|
||||||
|
for _, itf := range p.interfaces {
|
||||||
|
if !itf.assertFunc.IsNil() {
|
||||||
|
p.createInterfaceImplementsFunc(itf)
|
||||||
|
}
|
||||||
|
for signature := range itf.methodFuncs {
|
||||||
|
p.createInterfaceMethodFunc(itf, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace all ptrtoint typecode placeholders with their final type code
|
||||||
|
// numbers.
|
||||||
|
for _, typ := range p.types {
|
||||||
|
for _, use := range getUses(typ.typecode) {
|
||||||
|
if !use.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
||||||
|
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove stray runtime.typeInInterface globals. Required for the following
|
||||||
|
// cleanup.
|
||||||
|
for _, global := range typesInInterfaces {
|
||||||
|
global.EraseFromParentAsGlobal()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove method sets of types. Unnecessary, but cleans up the IR for
|
||||||
|
// inspection.
|
||||||
|
for _, typ := range p.types {
|
||||||
|
if !typ.methodSet.IsNil() {
|
||||||
|
typ.methodSet.EraseFromParentAsGlobal()
|
||||||
|
typ.methodSet = llvm.Value{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addTypeMethods reads the method set of the given type info struct. It
|
||||||
|
// retrieves the signatures and the references to the method functions
|
||||||
|
// themselves for later type<->interface matching.
|
||||||
|
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
|
||||||
|
if !t.methodSet.IsNil() || methodSet.IsNull() {
|
||||||
|
// no methods or methods already read
|
||||||
|
return
|
||||||
|
}
|
||||||
|
methodSet = methodSet.Operand(0) // get global from GEP
|
||||||
|
|
||||||
|
// This type has methods, collect all methods of this type.
|
||||||
|
t.methodSet = methodSet
|
||||||
|
set := methodSet.Initializer() // get value from global
|
||||||
|
for i := 0; i < set.Type().ArrayLength(); i++ {
|
||||||
|
methodData := llvm.ConstExtractValue(set, []uint32{uint32(i)})
|
||||||
|
signatureName := llvm.ConstExtractValue(methodData, []uint32{0}).Name()
|
||||||
|
function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0)
|
||||||
|
signature := p.getSignature(signatureName)
|
||||||
|
method := &methodInfo{
|
||||||
|
function: function,
|
||||||
|
signatureInfo: signature,
|
||||||
|
}
|
||||||
|
signature.methods = append(signature.methods, method)
|
||||||
|
t.methods = append(t.methods, method)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addInterface reads information about an interface, which is the
|
||||||
|
// fully-qualified name and the signatures of all methods it has.
|
||||||
|
func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) {
|
||||||
|
name := methodSet.Name()
|
||||||
|
t := &interfaceInfo{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
p.interfaces[name] = t
|
||||||
|
methodSet = methodSet.Initializer() // get global value from getelementptr
|
||||||
|
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||||
|
signatureName := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}).Name()
|
||||||
|
signature := p.getSignature(signatureName)
|
||||||
|
signature.interfaces = append(signature.interfaces, t)
|
||||||
|
t.signatures = append(t.signatures, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSignature returns a new *signatureInfo, creating it if it doesn't already
|
||||||
|
// exist.
|
||||||
|
func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
||||||
|
if _, ok := p.signatures[name]; !ok {
|
||||||
|
p.signatures[name] = &signatureInfo{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.signatures[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// replaceInvokeWithCall replaces a runtime.interfaceMethod + inttoptr with a
|
||||||
|
// concrete method. This can be done when only one type implements the
|
||||||
|
// interface.
|
||||||
|
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
|
||||||
|
inttoptrs := getUses(use)
|
||||||
|
if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() {
|
||||||
|
panic("expected exactly one inttoptr use of runtime.interfaceMethod")
|
||||||
|
}
|
||||||
|
inttoptr := inttoptrs[0]
|
||||||
|
function := typ.getMethod(signature).function
|
||||||
|
if inttoptr.Type() == function.Type() {
|
||||||
|
// Easy case: the types are the same. Simply replace the inttoptr
|
||||||
|
// result (which is directly called) with the actual function.
|
||||||
|
inttoptr.ReplaceAllUsesWith(function)
|
||||||
|
} else {
|
||||||
|
// Harder case: the type is not actually the same. Go through each call
|
||||||
|
// (of which there should be only one), extract the receiver params for
|
||||||
|
// this call and replace the call with a direct call to the target
|
||||||
|
// function.
|
||||||
|
for _, call := range getUses(inttoptr) {
|
||||||
|
if call.IsACallInst().IsNil() || call.CalledValue() != inttoptr {
|
||||||
|
panic("expected the inttoptr to be called as a method, this is not a method call")
|
||||||
|
}
|
||||||
|
operands := make([]llvm.Value, call.OperandsCount()-1)
|
||||||
|
for i := range operands {
|
||||||
|
operands[i] = call.Operand(i)
|
||||||
|
}
|
||||||
|
paramTypes := function.Type().ElementType().ParamTypes()
|
||||||
|
receiverParamTypes := paramTypes[:len(paramTypes)-(len(operands)-1)]
|
||||||
|
methodParamTypes := paramTypes[len(paramTypes)-(len(operands)-1):]
|
||||||
|
for i, methodParamType := range methodParamTypes {
|
||||||
|
if methodParamType != operands[i+1].Type() {
|
||||||
|
panic("expected method call param type and function param type to be the same")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.builder.SetInsertPointBefore(call)
|
||||||
|
receiverParams := p.emitPointerUnpack(operands[0], receiverParamTypes)
|
||||||
|
result := p.builder.CreateCall(function, append(receiverParams, operands[1:]...), "")
|
||||||
|
if result.Type().TypeKind() != llvm.VoidTypeKind {
|
||||||
|
call.ReplaceAllUsesWith(result)
|
||||||
|
}
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inttoptr.EraseFromParentAsInstruction()
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceImplementsFunc returns a function that checks whether a given
|
||||||
|
// interface type implements a given interface, by checking all possible types
|
||||||
|
// that implement this interface.
|
||||||
|
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
|
||||||
|
if !itf.assertFunc.IsNil() {
|
||||||
|
return itf.assertFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the function and function signature.
|
||||||
|
// TODO: debug info
|
||||||
|
fnName := itf.id() + "$typeassert"
|
||||||
|
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
|
||||||
|
itf.assertFunc = llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
|
itf.assertFunc.Param(0).SetName("actualType")
|
||||||
|
|
||||||
|
// Type asserts will be made for each type, so increment the counter for
|
||||||
|
// those.
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
typ.countTypeAsserts++
|
||||||
|
}
|
||||||
|
|
||||||
|
return itf.assertFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInterfaceImplementsFunc finishes the work of
|
||||||
|
// getInterfaceImplementsFunc, because it needs to run after types have a type
|
||||||
|
// code assigned.
|
||||||
|
//
|
||||||
|
// The type match is implemented using a big type switch over all possible
|
||||||
|
// types.
|
||||||
|
func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo) {
|
||||||
|
fn := itf.assertFunc
|
||||||
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
|
||||||
|
// TODO: debug info
|
||||||
|
|
||||||
|
// Create all used basic blocks.
|
||||||
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
|
thenBlock := llvm.AddBasicBlock(fn, "then")
|
||||||
|
elseBlock := llvm.AddBasicBlock(fn, "else")
|
||||||
|
|
||||||
|
// Add all possible types as cases.
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
actualType := fn.Param(0)
|
||||||
|
sw := p.builder.CreateSwitch(actualType, elseBlock, len(itf.types))
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), thenBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill 'then' block (type assert was successful).
|
||||||
|
p.builder.SetInsertPointAtEnd(thenBlock)
|
||||||
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
|
||||||
|
|
||||||
|
// Fill 'else' block (type asserted failed).
|
||||||
|
p.builder.SetInsertPointAtEnd(elseBlock)
|
||||||
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
||||||
|
// It only declares the function, createInterfaceMethodFunc actually defines the
|
||||||
|
// function.
|
||||||
|
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
||||||
|
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||||
|
// This function has already been created.
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
if itf.methodFuncs == nil {
|
||||||
|
// initialize the above map
|
||||||
|
itf.methodFuncs = make(map[*signatureInfo]llvm.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the function name, which is of the form:
|
||||||
|
// (main.Stringer).String
|
||||||
|
fnName := "(" + itf.id() + ")." + signature.methodName()
|
||||||
|
fnType := llvm.FunctionType(returnType, params, false)
|
||||||
|
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
|
fn.LastParam().SetName("actualType")
|
||||||
|
itf.methodFuncs[signature] = fn
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
// createInterfaceMethodFunc finishes the work of getInterfaceMethodFunc,
|
||||||
|
// because it needs to run after type codes have been assigned to concrete
|
||||||
|
// types.
|
||||||
|
//
|
||||||
|
// Matching the actual type is implemented using a big type switch over all
|
||||||
|
// possible types.
|
||||||
|
func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo) {
|
||||||
|
fn := itf.methodFuncs[signature]
|
||||||
|
fn.SetLinkage(llvm.InternalLinkage)
|
||||||
|
fn.SetUnnamedAddr(true)
|
||||||
|
|
||||||
|
// TODO: debug info
|
||||||
|
|
||||||
|
// Create entry block.
|
||||||
|
entry := llvm.AddBasicBlock(fn, "entry")
|
||||||
|
|
||||||
|
// Create default block and make it unreachable (which it is, because all
|
||||||
|
// possible types are checked).
|
||||||
|
defaultBlock := llvm.AddBasicBlock(fn, "default")
|
||||||
|
p.builder.SetInsertPointAtEnd(defaultBlock)
|
||||||
|
p.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
// Create type switch in entry block.
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
actualType := fn.LastParam()
|
||||||
|
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
|
||||||
|
|
||||||
|
// Collect the params that will be passed to the functions to call.
|
||||||
|
// These params exclude the receiver (which may actually consist of multiple
|
||||||
|
// parts).
|
||||||
|
params := make([]llvm.Value, fn.ParamsCount()-2)
|
||||||
|
for i := range params {
|
||||||
|
params[i] = fn.Param(i + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define all possible functions that can be called.
|
||||||
|
for _, typ := range itf.types {
|
||||||
|
bb := llvm.AddBasicBlock(fn, typ.name)
|
||||||
|
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
||||||
|
|
||||||
|
// The function we will redirect to when the interface has this type.
|
||||||
|
function := typ.getMethod(signature).function
|
||||||
|
|
||||||
|
p.builder.SetInsertPointAtEnd(bb)
|
||||||
|
receiver := fn.FirstParam()
|
||||||
|
if receiver.Type() != function.FirstParam().Type() {
|
||||||
|
// When the receiver is a pointer, it is not wrapped. This means the
|
||||||
|
// i8* has to be cast to the correct pointer type of the target
|
||||||
|
// function.
|
||||||
|
receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "")
|
||||||
|
}
|
||||||
|
retval := p.builder.CreateCall(function, append([]llvm.Value{receiver}, params...), "")
|
||||||
|
if retval.Type().TypeKind() == llvm.VoidTypeKind {
|
||||||
|
p.builder.CreateRetVoid()
|
||||||
|
} else {
|
||||||
|
p.builder.CreateRet(retval)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+172
-347
@@ -11,147 +11,64 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
||||||
// It tries to put the type in the interface value, but if that's not possible,
|
// It tries to put the type in the interface value, but if that's not possible,
|
||||||
// it will do an allocation of the right size and put that in the interface
|
// it will do an allocation of the right size and put that in the interface
|
||||||
// value field.
|
// value field.
|
||||||
//
|
//
|
||||||
// An interface value is a {typecode, value} tuple named runtime._interface.
|
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||||
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) (llvm.Value, error) {
|
||||||
itfValue := b.emitPointerPack([]llvm.Value{val})
|
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||||
itfTypeCodeGlobal := b.getTypeCode(typ)
|
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||||
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
|
itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
|
||||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
if err != nil {
|
||||||
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
|
return llvm.Value{}, nil
|
||||||
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
}
|
||||||
return itf
|
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||||
|
if itfConcreteTypeGlobal.IsNil() {
|
||||||
|
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
|
||||||
|
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||||
|
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||||
|
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||||
|
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||||
|
}
|
||||||
|
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||||
|
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
|
||||||
|
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||||
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
|
return itf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCode returns a reference to a type code.
|
// getTypeCode returns a reference to a type code.
|
||||||
// It returns a pointer to an external global which should be replaced with the
|
// It returns a pointer to an external global which should be replaced with the
|
||||||
// real type in the interface lowering pass.
|
// real type in the interface lowering pass.
|
||||||
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
globalName := "type:" + getTypeCodeName(typ)
|
||||||
global := c.mod.NamedGlobal(globalName)
|
global := c.mod.NamedGlobal(globalName)
|
||||||
if global.IsNil() {
|
if global.IsNil() {
|
||||||
// Create a new typecode global.
|
global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
|
||||||
global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
|
|
||||||
// Some type classes contain more information for underlying types or
|
|
||||||
// element types. Store it directly in the typecode global to make
|
|
||||||
// reflect lowering simpler.
|
|
||||||
var references llvm.Value
|
|
||||||
var length int64
|
|
||||||
var methodSet llvm.Value
|
|
||||||
var ptrTo llvm.Value
|
|
||||||
var typeAssert llvm.Value
|
|
||||||
switch typ := typ.(type) {
|
|
||||||
case *types.Named:
|
|
||||||
references = c.getTypeCode(typ.Underlying())
|
|
||||||
case *types.Chan:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Pointer:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Slice:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
case *types.Array:
|
|
||||||
references = c.getTypeCode(typ.Elem())
|
|
||||||
length = typ.Len()
|
|
||||||
case *types.Struct:
|
|
||||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
|
||||||
structGlobal := c.makeStructTypeFields(typ)
|
|
||||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
|
||||||
case *types.Interface:
|
|
||||||
methodSetGlobal := c.getInterfaceMethodSet(typ)
|
|
||||||
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
|
|
||||||
}
|
|
||||||
if _, ok := typ.Underlying().(*types.Interface); !ok {
|
|
||||||
methodSet = c.getTypeMethodSet(typ)
|
|
||||||
} else {
|
|
||||||
typeAssert = c.getInterfaceImplementsFunc(typ)
|
|
||||||
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
|
|
||||||
}
|
|
||||||
if _, ok := typ.Underlying().(*types.Pointer); !ok {
|
|
||||||
ptrTo = c.getTypeCode(types.NewPointer(typ))
|
|
||||||
}
|
|
||||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
|
||||||
if !references.IsNil() {
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
|
||||||
}
|
|
||||||
if length != 0 {
|
|
||||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
|
||||||
}
|
|
||||||
if !methodSet.IsNil() {
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
|
|
||||||
}
|
|
||||||
if !ptrTo.IsNil() {
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
|
|
||||||
}
|
|
||||||
if !typeAssert.IsNil() {
|
|
||||||
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
|
|
||||||
}
|
|
||||||
global.SetInitializer(globalValue)
|
|
||||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
}
|
}
|
||||||
return global
|
return global
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeStructTypeFields creates a new global that stores all type information
|
|
||||||
// related to this struct type, and returns the resulting global. This global is
|
|
||||||
// actually an array of all the fields in the structs.
|
|
||||||
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
|
||||||
// The global is an array of runtime.structField structs.
|
|
||||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
|
||||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
|
||||||
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
|
|
||||||
structGlobalValue := llvm.ConstNull(structGlobalType)
|
|
||||||
for i := 0; i < typ.NumFields(); i++ {
|
|
||||||
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
|
|
||||||
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
|
|
||||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
fieldName.SetUnnamedAddr(true)
|
|
||||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
})
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
|
||||||
if typ.Tag(i) != "" {
|
|
||||||
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
|
|
||||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
fieldTag.SetUnnamedAddr(true)
|
|
||||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
|
||||||
})
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
|
||||||
}
|
|
||||||
if typ.Field(i).Embedded() {
|
|
||||||
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
|
|
||||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
|
|
||||||
}
|
|
||||||
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
|
|
||||||
}
|
|
||||||
structGlobal.SetInitializer(structGlobalValue)
|
|
||||||
structGlobal.SetUnnamedAddr(true)
|
|
||||||
structGlobal.SetLinkage(llvm.PrivateLinkage)
|
|
||||||
return structGlobal
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTypeCodeName returns a name for this type that can be used in the
|
// getTypeCodeName returns a name for this type that can be used in the
|
||||||
// interface lowering pass to assign type codes as expected by the reflect
|
// interface lowering pass to assign type codes as expected by the reflect
|
||||||
// package. See getTypeCodeNum.
|
// package. See getTypeCodeNum.
|
||||||
func getTypeCodeName(t types.Type) string {
|
func getTypeCodeName(t types.Type) string {
|
||||||
|
name := ""
|
||||||
|
if named, ok := t.(*types.Named); ok {
|
||||||
|
name = "~" + named.String() + ":"
|
||||||
|
t = t.Underlying()
|
||||||
|
}
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
case *types.Named:
|
|
||||||
return "named:" + t.String()
|
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
var kind string
|
var kind string
|
||||||
switch t.Kind() {
|
switch t.Kind() {
|
||||||
@@ -194,25 +111,21 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
default:
|
default:
|
||||||
panic("unknown basic type: " + t.Name())
|
panic("unknown basic type: " + t.Name())
|
||||||
}
|
}
|
||||||
return "basic:" + kind
|
return "basic:" + name + kind
|
||||||
case *types.Chan:
|
case *types.Chan:
|
||||||
return "chan:" + getTypeCodeName(t.Elem())
|
return "chan:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
methods := make([]string, t.NumMethods())
|
methods := make([]string, t.NumMethods())
|
||||||
for i := 0; i < t.NumMethods(); i++ {
|
for i := 0; i < t.NumMethods(); i++ {
|
||||||
name := t.Method(i).Name()
|
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||||
if !token.IsExported(name) {
|
|
||||||
name = t.Method(i).Pkg().Path() + "." + name
|
|
||||||
}
|
}
|
||||||
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
|
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
|
||||||
}
|
|
||||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
keyType := getTypeCodeName(t.Key())
|
keyType := getTypeCodeName(t.Key())
|
||||||
elemType := getTypeCodeName(t.Elem())
|
elemType := getTypeCodeName(t.Elem())
|
||||||
return "map:" + "{" + keyType + "," + elemType + "}"
|
return "map:" + name + "{" + keyType + "," + elemType + "}"
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
return "pointer:" + getTypeCodeName(t.Elem())
|
return "pointer:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Signature:
|
case *types.Signature:
|
||||||
params := make([]string, t.Params().Len())
|
params := make([]string, t.Params().Len())
|
||||||
for i := 0; i < t.Params().Len(); i++ {
|
for i := 0; i < t.Params().Len(); i++ {
|
||||||
@@ -222,22 +135,19 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
for i := 0; i < t.Results().Len(); i++ {
|
for i := 0; i < t.Results().Len(); i++ {
|
||||||
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
results[i] = getTypeCodeName(t.Results().At(i).Type())
|
||||||
}
|
}
|
||||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
|
||||||
case *types.Slice:
|
case *types.Slice:
|
||||||
return "slice:" + getTypeCodeName(t.Elem())
|
return "slice:" + name + getTypeCodeName(t.Elem())
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
elems := make([]string, t.NumFields())
|
elems := make([]string, t.NumFields())
|
||||||
|
if t.NumFields() > 2 && t.Field(0).Name() == "C union" {
|
||||||
|
// TODO: report this as a normal error instead of panicking.
|
||||||
|
panic("cgo unions are not allowed in interfaces")
|
||||||
|
}
|
||||||
for i := 0; i < t.NumFields(); i++ {
|
for i := 0; i < t.NumFields(); i++ {
|
||||||
embedded := ""
|
elems[i] = getTypeCodeName(t.Field(i).Type())
|
||||||
if t.Field(i).Embedded() {
|
|
||||||
embedded = "#"
|
|
||||||
}
|
}
|
||||||
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
|
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
|
||||||
if t.Tag(i) != "" {
|
|
||||||
elems[i] += "`" + t.Tag(i) + "`"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "struct:" + "{" + strings.Join(elems, ",") + "}"
|
|
||||||
default:
|
default:
|
||||||
panic("unknown type: " + t.String())
|
panic("unknown type: " + t.String())
|
||||||
}
|
}
|
||||||
@@ -245,35 +155,37 @@ func getTypeCodeName(t types.Type) string {
|
|||||||
|
|
||||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||||
// method set should be unreferenced after the interface lowering pass.
|
// method set should be unreferenced after the interface lowering pass.
|
||||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
|
||||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
if !global.IsNil() {
|
if !global.IsNil() {
|
||||||
// the method set already exists
|
// the method set already exists
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
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.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
methods := make([]llvm.Value, ms.Len())
|
methods := make([]llvm.Value, ms.Len())
|
||||||
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
|
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
|
||||||
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())
|
||||||
|
}
|
||||||
|
fn, err := c.getInterfaceInvokeWrapper(f)
|
||||||
|
if err != nil {
|
||||||
|
return llvm.Value{}, err
|
||||||
}
|
}
|
||||||
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -282,110 +194,80 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
|||||||
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
|
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
|
||||||
global.SetInitializer(value)
|
global.SetInitializer(value)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInterfaceMethodSet returns a global variable with the method set of the
|
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||||
// given named interface type. This method set is used by the interface lowering
|
// given named interface type. This method set is used by the interface lowering
|
||||||
// pass.
|
// pass.
|
||||||
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||||
name := typ.String()
|
global := c.mod.NamedGlobal(typ.String() + "$interface")
|
||||||
if _, ok := typ.(*types.Named); !ok {
|
|
||||||
// Anonymous interface.
|
|
||||||
name = "reflect/types.interface:" + name
|
|
||||||
}
|
|
||||||
global := c.mod.NamedGlobal(name + "$interface")
|
|
||||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||||
if !global.IsNil() {
|
if !global.IsNil() {
|
||||||
// method set already exist, return it
|
// method set already exist, return it
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Every method is a *i8 reference indicating the signature of this method.
|
// Every method is a *i16 reference indicating the signature of this method.
|
||||||
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
|
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
|
||||||
for i := range methods {
|
for i := range methods {
|
||||||
method := typ.Underlying().(*types.Interface).Method(i)
|
method := typ.Underlying().(*types.Interface).Method(i)
|
||||||
methods[i] = c.getMethodSignature(method)
|
methods[i] = c.getMethodSignature(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
value := llvm.ConstArray(c.i8ptrType, methods)
|
value := llvm.ConstArray(methods[0].Type(), methods)
|
||||||
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
|
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
|
||||||
global.SetInitializer(value)
|
global.SetInitializer(value)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
global.SetLinkage(llvm.PrivateLinkage)
|
||||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMethodSignatureName returns a unique name (that can be used as the name of
|
|
||||||
// a global) for the given method.
|
|
||||||
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
|
|
||||||
signature := methodSignature(method)
|
|
||||||
var globalName string
|
|
||||||
if token.IsExported(method.Name()) {
|
|
||||||
globalName = "reflect/methods." + signature
|
|
||||||
} else {
|
|
||||||
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
|
|
||||||
}
|
|
||||||
return globalName
|
|
||||||
}
|
|
||||||
|
|
||||||
// getMethodSignature returns a global variable which is a reference to an
|
// getMethodSignature returns a global variable which is a reference to an
|
||||||
// external *i8 indicating the indicating the signature of this method. It is
|
// external *i16 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 *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||||
globalName := c.getMethodSignatureName(method)
|
signature := ir.MethodSignature(method)
|
||||||
signatureGlobal := c.mod.NamedGlobal(globalName)
|
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||||
if signatureGlobal.IsNil() {
|
if signatureGlobal.IsNil() {
|
||||||
// TODO: put something useful in these globals, such as the method
|
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||||
// signature. Useful to one day implement reflect.Value.Method(n).
|
|
||||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
|
||||||
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
|
|
||||||
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
signatureGlobal.SetGlobalConstant(true)
|
signatureGlobal.SetGlobalConstant(true)
|
||||||
signatureGlobal.SetAlignment(1)
|
|
||||||
}
|
}
|
||||||
return signatureGlobal
|
return signatureGlobal
|
||||||
}
|
}
|
||||||
|
|
||||||
// createTypeAssert will emit the code for a typeassert, used in if statements
|
// parseTypeAssert will emit the code for a typeassert, used in if statements
|
||||||
// and in type switches (Go SSA does not have type switches, only if/else
|
// and in type switches (Go SSA does not have type switches, only if/else
|
||||||
// chains). Note that even though the Go SSA does not contain type switches,
|
// chains). Note that even though the Go SSA does not contain type switches,
|
||||||
// LLVM will recognize the pattern and make it a real switch in many cases.
|
// LLVM will recognize the pattern and make it a real switch in many cases.
|
||||||
//
|
//
|
||||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||||
// asserts on interfaces are more difficult, see the comments in the function.
|
// asserts on interfaces are more difficult, see the comments in the function.
|
||||||
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||||
itf := b.getValue(expr.X)
|
itf := c.getValue(frame, expr.X)
|
||||||
assertedType := b.getLLVMType(expr.AssertedType)
|
assertedType := c.getLLVMType(expr.AssertedType)
|
||||||
|
|
||||||
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
|
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||||
commaOk := llvm.Value{}
|
commaOk := llvm.Value{}
|
||||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||||
// Type assert on interface type.
|
// Type assert on interface type.
|
||||||
// This is a call to an interface type assert function.
|
// This pseudo call will be lowered in the interface lowering pass to a
|
||||||
// The interface lowering pass will define this function by filling it
|
// real call which checks whether the provided typecode is any of the
|
||||||
// with a type switch over all concrete types that implement this
|
// concrete types that implements this interface.
|
||||||
// interface, and returning whether it's one of the matched types.
|
|
||||||
// This is very different from how interface asserts are implemented in
|
// This is very different from how interface asserts are implemented in
|
||||||
// the main Go compiler, where the runtime checks whether the type
|
// the main Go compiler, where the runtime checks whether the type
|
||||||
// implements each method of the interface. See:
|
// implements each method of the interface. See:
|
||||||
// https://research.swtch.com/interfaces
|
// https://research.swtch.com/interfaces
|
||||||
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
|
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
|
||||||
commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "")
|
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
|
||||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
|
||||||
if assertedTypeCodeGlobal.IsNil() {
|
|
||||||
// Create a new typecode global.
|
|
||||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
|
||||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
|
||||||
}
|
|
||||||
// Type assert on concrete type.
|
// Type assert on concrete type.
|
||||||
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
||||||
// const false in the interface lowering pass.
|
// const false in the interface lowering pass.
|
||||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType)
|
||||||
|
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add 2 new basic blocks (that should get optimized away): one for the
|
// Add 2 new basic blocks (that should get optimized away): one for the
|
||||||
@@ -399,15 +281,15 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
|||||||
// typeassert should return a zero value, not an incorrectly casted
|
// typeassert should return a zero value, not an incorrectly casted
|
||||||
// value.
|
// value.
|
||||||
|
|
||||||
prevBlock := b.GetInsertBlock()
|
prevBlock := c.builder.GetInsertBlock()
|
||||||
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
|
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
|
||||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
|
||||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||||
|
|
||||||
// Retrieve the value from the interface if the type assert was
|
// Retrieve the value from the interface if the type assert was
|
||||||
// successful.
|
// successful.
|
||||||
b.SetInsertPointAtEnd(okBlock)
|
c.builder.SetInsertPointAtEnd(okBlock)
|
||||||
var valueOk llvm.Value
|
var valueOk llvm.Value
|
||||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||||
// Type assert on interface type. Easy: just return the same
|
// Type assert on interface type. Easy: just return the same
|
||||||
@@ -416,96 +298,84 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
|||||||
} else {
|
} else {
|
||||||
// Type assert on concrete type. Extract the underlying type from
|
// Type assert on concrete type. Extract the underlying type from
|
||||||
// the interface (but only after checking it matches).
|
// the interface (but only after checking it matches).
|
||||||
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||||
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||||
}
|
}
|
||||||
b.CreateBr(nextBlock)
|
c.builder.CreateBr(nextBlock)
|
||||||
|
|
||||||
// Continue after the if statement.
|
// Continue after the if statement.
|
||||||
b.SetInsertPointAtEnd(nextBlock)
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
phi.AddIncoming([]llvm.Value{c.getZeroValue(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||||
|
|
||||||
if expr.CommaOk {
|
if expr.CommaOk {
|
||||||
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
|
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||||
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
|
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||||
return tuple
|
return tuple
|
||||||
} else {
|
} else {
|
||||||
// This is kind of dirty as the branch above becomes mostly useless,
|
// This is kind of dirty as the branch above becomes mostly useless,
|
||||||
// but hopefully this gets optimized away.
|
// but hopefully this gets optimized away.
|
||||||
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||||
return phi
|
return phi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getMethodsString returns a string to be used in the "tinygo-methods" string
|
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||||
// attribute for interface functions.
|
// interface call. It can be used in a call or defer instruction.
|
||||||
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||||
methods := make([]string, itf.NumMethods())
|
// Call an interface method with dynamic dispatch.
|
||||||
for i := range methods {
|
itf := c.getValue(frame, instr.Value) // interface
|
||||||
methods[i] = c.getMethodSignatureName(itf.Method(i))
|
|
||||||
|
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||||
|
|
||||||
|
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||||
|
values := []llvm.Value{
|
||||||
|
typecode,
|
||||||
|
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
|
||||||
|
c.getMethodSignature(instr.Method),
|
||||||
}
|
}
|
||||||
return strings.Join(methods, "; ")
|
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||||
|
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||||
|
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
|
|
||||||
|
args := []llvm.Value{receiverValue}
|
||||||
|
for _, arg := range instr.Args {
|
||||||
|
args = append(args, c.getValue(frame, arg))
|
||||||
|
}
|
||||||
|
// Add the context parameter. An interface call never takes a context but we
|
||||||
|
// have to supply the parameter anyway.
|
||||||
|
args = append(args, llvm.Undef(c.i8ptrType))
|
||||||
|
// Add the parent goroutine handle.
|
||||||
|
args = append(args, llvm.Undef(c.i8ptrType))
|
||||||
|
|
||||||
|
return fnCast, args
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInterfaceImplementsfunc returns a declared function that works as a type
|
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||||
// switch. The interface lowering pass will define this function.
|
// createInterfaceInvokeWrapper. The former is called during IR construction
|
||||||
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
|
// itself and the latter is called when finishing up the IR.
|
||||||
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
|
type interfaceInvokeWrapper struct {
|
||||||
llvmFn := c.mod.NamedFunction(fnName)
|
fn *ir.Function
|
||||||
if llvmFn.IsNil() {
|
wrapper llvm.Value
|
||||||
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
|
receiverType llvm.Type
|
||||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
|
||||||
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
|
|
||||||
}
|
|
||||||
return llvmFn
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInvokeFunction returns the thunk to call the given interface method. The
|
// Wrap an interface method function pointer. The wrapper takes in a pointer to
|
||||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
// the underlying value, dereferences it, and calls the real method. This
|
||||||
// pass.
|
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
// pointer and a pointer to the value must be created.
|
||||||
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
|
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
|
||||||
llvmFn := c.mod.NamedFunction(fnName)
|
wrapperName := f.LinkName() + "$invoke"
|
||||||
if llvmFn.IsNil() {
|
|
||||||
sig := instr.Method.Type().(*types.Signature)
|
|
||||||
var paramTuple []*types.Var
|
|
||||||
for i := 0; i < sig.Params().Len(); i++ {
|
|
||||||
paramTuple = append(paramTuple, sig.Params().At(i))
|
|
||||||
}
|
|
||||||
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
|
|
||||||
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType()
|
|
||||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
|
|
||||||
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
|
|
||||||
}
|
|
||||||
return llvmFn
|
|
||||||
}
|
|
||||||
|
|
||||||
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
|
|
||||||
// invoked from an interface. The wrapper takes in a pointer to the underlying
|
|
||||||
// value, dereferences or unpacks it if necessary, and calls the real method.
|
|
||||||
// If the method to wrap has a pointer receiver, no wrapping is necessary and
|
|
||||||
// the function is returned directly.
|
|
||||||
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
|
|
||||||
wrapperName := llvmFn.Name() + "$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.
|
||||||
return wrapper
|
return wrapper, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the expanded receiver type.
|
// Get the expanded receiver type.
|
||||||
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
|
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||||
var expandedReceiverType []llvm.Type
|
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||||
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
|
|
||||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does this method even need any wrapping?
|
// Does this method even need any wrapping?
|
||||||
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
|
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
|
||||||
@@ -513,94 +383,49 @@ 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, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
c.addStandardAttributes(wrapper)
|
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
||||||
wrapper.LastParam().SetName("parentHandle")
|
fn: f,
|
||||||
|
wrapper: wrapper,
|
||||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
receiverType: receiverType,
|
||||||
wrapper.SetUnnamedAddr(true)
|
})
|
||||||
|
return wrapper, nil
|
||||||
// Create a new builder just to create this wrapper.
|
|
||||||
b := builder{
|
|
||||||
compilerContext: c,
|
|
||||||
Builder: c.ctx.NewBuilder(),
|
|
||||||
}
|
}
|
||||||
defer b.Builder.Dispose()
|
|
||||||
|
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||||
|
// see that function for details.
|
||||||
|
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||||
|
wrapper := state.wrapper
|
||||||
|
fn := state.fn
|
||||||
|
receiverType := state.receiverType
|
||||||
|
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||||
|
wrapper.SetUnnamedAddr(true)
|
||||||
|
|
||||||
// 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(fn.Pos())
|
||||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up IR builder
|
// set up IR builder
|
||||||
block := b.ctx.AddBasicBlock(wrapper, "entry")
|
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||||
b.SetInsertPointAtEnd(block)
|
c.builder.SetInsertPointAtEnd(block)
|
||||||
|
|
||||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||||
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||||
b.CreateCall(llvmFn, params, "")
|
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||||
b.CreateRetVoid()
|
c.builder.CreateRetVoid()
|
||||||
} else {
|
} else {
|
||||||
ret := b.CreateCall(llvmFn, params, "ret")
|
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||||
b.CreateRet(ret)
|
c.builder.CreateRet(ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodSignature creates a readable version of a method signature (including
|
|
||||||
// the function name, excluding the receiver name). This string is used
|
|
||||||
// internally to match interfaces and to call the correct method on an
|
|
||||||
// interface. Examples:
|
|
||||||
//
|
|
||||||
// String() string
|
|
||||||
// Read([]byte) (int, error)
|
|
||||||
func methodSignature(method *types.Func) string {
|
|
||||||
return method.Name() + signature(method.Type().(*types.Signature))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a readable version of a function (pointer) signature.
|
|
||||||
// Examples:
|
|
||||||
//
|
|
||||||
// () string
|
|
||||||
// (string, int) (int, error)
|
|
||||||
func signature(sig *types.Signature) string {
|
|
||||||
s := ""
|
|
||||||
if sig.Params().Len() == 0 {
|
|
||||||
s += "()"
|
|
||||||
} else {
|
|
||||||
s += "("
|
|
||||||
for i := 0; i < sig.Params().Len(); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
s += ", "
|
|
||||||
}
|
|
||||||
s += sig.Params().At(i).Type().String()
|
|
||||||
}
|
|
||||||
s += ")"
|
|
||||||
}
|
|
||||||
if sig.Results().Len() == 0 {
|
|
||||||
// keep as-is
|
|
||||||
} else if sig.Results().Len() == 1 {
|
|
||||||
s += " " + sig.Results().At(0).Type().String()
|
|
||||||
} else {
|
|
||||||
s += " ("
|
|
||||||
for i := 0; i < sig.Results().Len(); i++ {
|
|
||||||
if i > 0 {
|
|
||||||
s += ", "
|
|
||||||
}
|
|
||||||
s += sig.Results().At(i).Type().String()
|
|
||||||
}
|
|
||||||
s += ")"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
|
|
||||||
// will be lowered to a real interrupt during interrupt lowering.
|
|
||||||
//
|
|
||||||
// This two-stage approach allows unused interrupts to be optimized away if
|
|
||||||
// necessary.
|
|
||||||
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
|
|
||||||
// Get the interrupt number, which must be a compile-time constant.
|
|
||||||
id, ok := instr.Args[0].(*ssa.Const)
|
|
||||||
if !ok {
|
|
||||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the func value, which also must be a compile time constant.
|
|
||||||
// Note that bound functions are allowed if the function has a pointer
|
|
||||||
// receiver and is a global. This is rather strict but still allows for
|
|
||||||
// idiomatic Go code.
|
|
||||||
funcValue := b.getValue(instr.Args[1])
|
|
||||||
if funcValue.IsAConstant().IsNil() {
|
|
||||||
// Try to determine the cause of the non-constantness for a nice error
|
|
||||||
// message.
|
|
||||||
switch instr.Args[1].(type) {
|
|
||||||
case *ssa.MakeClosure:
|
|
||||||
// This may also be a bound method.
|
|
||||||
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
|
||||||
}
|
|
||||||
// Fall back to a generic error.
|
|
||||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
|
|
||||||
}
|
|
||||||
funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
|
|
||||||
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
|
|
||||||
|
|
||||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
|
||||||
// type are lowered in the interrupt lowering pass.
|
|
||||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
|
||||||
globalLLVMType := b.getLLVMType(globalType)
|
|
||||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
|
||||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
|
||||||
global.SetVisibility(llvm.HiddenVisibility)
|
|
||||||
global.SetGlobalConstant(true)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
initializer := llvm.ConstNull(globalLLVMType)
|
|
||||||
initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0})
|
|
||||||
initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1})
|
|
||||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
|
|
||||||
global.SetInitializer(initializer)
|
|
||||||
|
|
||||||
// Add debug info to the interrupt global.
|
|
||||||
if b.Debug {
|
|
||||||
pos := b.program.Fset.Position(instr.Pos())
|
|
||||||
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
|
||||||
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
|
||||||
LinkageName: globalName,
|
|
||||||
File: b.getDIFile(pos.Filename),
|
|
||||||
Line: pos.Line,
|
|
||||||
Type: b.getDIType(globalType),
|
|
||||||
Expr: b.dibuilder.CreateExpression(nil),
|
|
||||||
LocalToUnit: false,
|
|
||||||
})
|
|
||||||
global.AddMetadata(0, diglobal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
|
|
||||||
// member of type int.
|
|
||||||
num := llvm.ConstPtrToInt(global, b.intType)
|
|
||||||
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
|
||||||
|
|
||||||
// Add dummy "use" call for AVR, because interrupts may be used even though
|
|
||||||
// they are never referenced again. This is unlike Cortex-M or the RISC-V
|
|
||||||
// PLIC where each interrupt must be enabled using the interrupt number, and
|
|
||||||
// thus keeps the Interrupt object alive.
|
|
||||||
// This call is removed during interrupt lowering.
|
|
||||||
if strings.HasPrefix(b.Triple, "avr") {
|
|
||||||
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
|
||||||
if useFn.IsNil() {
|
|
||||||
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
|
||||||
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
|
|
||||||
}
|
|
||||||
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return interrupt, nil
|
|
||||||
}
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file contains helper functions to create calls to LLVM intrinsics.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
|
|
||||||
// function, declaring this function if needed. These calls are treated
|
|
||||||
// specially by optimization passes possibly resulting in better generated code,
|
|
||||||
// and will otherwise be lowered to regular libc memcpy/memmove calls.
|
|
||||||
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
|
|
||||||
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
|
||||||
llvmFn := b.mod.NamedFunction(fnName)
|
|
||||||
if llvmFn.IsNil() {
|
|
||||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
|
|
||||||
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
|
|
||||||
}
|
|
||||||
var params []llvm.Value
|
|
||||||
for _, param := range args {
|
|
||||||
params = append(params, b.getValue(param))
|
|
||||||
}
|
|
||||||
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
|
|
||||||
b.CreateCall(llvmFn, params, "")
|
|
||||||
return llvm.Value{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
|
|
||||||
// memory, declaring the function if needed. These calls will be lowered to
|
|
||||||
// regular libc memset calls if they aren't optimized out in a different way.
|
|
||||||
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
|
|
||||||
fnName := "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
|
||||||
llvmFn := b.mod.NamedFunction(fnName)
|
|
||||||
if llvmFn.IsNil() {
|
|
||||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
|
|
||||||
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
|
|
||||||
}
|
|
||||||
params := []llvm.Value{
|
|
||||||
b.getValue(args[0]),
|
|
||||||
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
|
|
||||||
b.getValue(args[1]),
|
|
||||||
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
|
|
||||||
}
|
|
||||||
b.CreateCall(llvmFn, params, "")
|
|
||||||
return llvm.Value{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var mathToLLVMMapping = map[string]string{
|
|
||||||
"math.Sqrt": "llvm.sqrt.f64",
|
|
||||||
"math.Floor": "llvm.floor.f64",
|
|
||||||
"math.Ceil": "llvm.ceil.f64",
|
|
||||||
"math.Trunc": "llvm.trunc.f64",
|
|
||||||
}
|
|
||||||
|
|
||||||
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
|
|
||||||
// possible. It returns the call result if possible, and a boolean whether it
|
|
||||||
// succeeded. If it doesn't succeed, the architecture doesn't support the given
|
|
||||||
// intrinsic.
|
|
||||||
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
|
||||||
// Check whether this intrinsic is supported on the given GOARCH.
|
|
||||||
// If it is unsupported, this can have two reasons:
|
|
||||||
//
|
|
||||||
// 1. LLVM can expand the intrinsic inline (using float instructions), but
|
|
||||||
// the result doesn't pass the tests of the math package.
|
|
||||||
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
|
|
||||||
// libm function call, but the libm function call also fails the math
|
|
||||||
// package tests.
|
|
||||||
//
|
|
||||||
// Whatever the implementation, it must pass the tests in the math package
|
|
||||||
// so unfortunately only the below intrinsic+architecture combinations are
|
|
||||||
// supported.
|
|
||||||
name := call.StaticCallee().RelString(nil)
|
|
||||||
switch name {
|
|
||||||
case "math.Ceil", "math.Floor", "math.Trunc":
|
|
||||||
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
|
|
||||||
return llvm.Value{}, false
|
|
||||||
}
|
|
||||||
case "math.Sqrt":
|
|
||||||
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
|
|
||||||
return llvm.Value{}, false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return llvm.Value{}, false // only the above functions are supported.
|
|
||||||
}
|
|
||||||
|
|
||||||
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
|
|
||||||
if llvmFn.IsNil() {
|
|
||||||
// The intrinsic doesn't exist yet, so declare it.
|
|
||||||
// At the moment, all supported intrinsics have the form "double
|
|
||||||
// foo(double %x)" so we can hardcode the signature here.
|
|
||||||
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
|
||||||
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
|
|
||||||
}
|
|
||||||
// Create a call to the intrinsic.
|
|
||||||
args := make([]llvm.Value, len(call.Args))
|
|
||||||
for i, arg := range call.Args {
|
|
||||||
args[i] = b.getValue(arg)
|
|
||||||
}
|
|
||||||
return b.CreateCall(llvmFn, args, ""), true
|
|
||||||
}
|
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
// Package ircheck implements a checker for LLVM IR, that goes a bit further
|
|
||||||
// than the regular LLVM IR verifier. Note that it checks different things, so
|
|
||||||
// this is not a replacement for the LLVM verifier but does catch things that
|
|
||||||
// the LLVM verifier doesn't catch.
|
|
||||||
package ircheck
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type checker struct {
|
|
||||||
ctx llvm.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
|
||||||
// prevent infinite recursion for self-referential types
|
|
||||||
if _, ok := checked[t]; ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
checked[t] = struct{}{}
|
|
||||||
|
|
||||||
// check for any context mismatches
|
|
||||||
switch {
|
|
||||||
case t.Context() == c.ctx:
|
|
||||||
// this is correct
|
|
||||||
case t.Context() == llvm.GlobalContext():
|
|
||||||
// somewhere we accidentally used the global context instead of a real context
|
|
||||||
return fmt.Errorf("type %q uses global context", t.String())
|
|
||||||
default:
|
|
||||||
// we used some other context by accident
|
|
||||||
return fmt.Errorf("type %q uses context %v instead of the main context %v", t.Context(), c.ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if this is a composite type, check the components of the type
|
|
||||||
switch t.TypeKind() {
|
|
||||||
case llvm.VoidTypeKind, llvm.LabelTypeKind, llvm.TokenTypeKind, llvm.MetadataTypeKind:
|
|
||||||
// there should only be one of any of these
|
|
||||||
if s, ok := specials[t.TypeKind()]; !ok {
|
|
||||||
specials[t.TypeKind()] = t
|
|
||||||
} else if s != t {
|
|
||||||
return fmt.Errorf("duplicate special type %q: %v and %v", t.TypeKind().String(), t, s)
|
|
||||||
}
|
|
||||||
case llvm.FloatTypeKind, llvm.DoubleTypeKind, llvm.X86_FP80TypeKind, llvm.FP128TypeKind, llvm.PPC_FP128TypeKind:
|
|
||||||
// floating point numbers are primitives - nothing to recurse
|
|
||||||
case llvm.IntegerTypeKind:
|
|
||||||
// integers are primitives - nothing to recurse
|
|
||||||
case llvm.FunctionTypeKind:
|
|
||||||
// check arguments and return(s)
|
|
||||||
for i, v := range t.ParamTypes() {
|
|
||||||
if err := c.checkType(v, checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify argument %d of type %s: %s", i, t.String(), err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.checkType(t.ReturnType(), checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify return type of type %s: %s", t.String(), err.Error())
|
|
||||||
}
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
// check all elements
|
|
||||||
for i, v := range t.StructElementTypes() {
|
|
||||||
if err := c.checkType(v, checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify type of field %d of struct type %s: %s", i, t.String(), err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case llvm.ArrayTypeKind:
|
|
||||||
// check element type
|
|
||||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
|
|
||||||
}
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
// check underlying type
|
|
||||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
|
|
||||||
}
|
|
||||||
case llvm.VectorTypeKind:
|
|
||||||
// check element type
|
|
||||||
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify element type of vector type %s: %s", t.String(), err.Error())
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unrecognized kind %q of type %s", t.TypeKind(), t.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
|
||||||
// check type
|
|
||||||
if err := c.checkType(v.Type(), types, specials); err != nil {
|
|
||||||
return fmt.Errorf("failed to verify type of value: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if this is an undefined void
|
|
||||||
if v.IsUndef() && v.Type().TypeKind() == llvm.VoidTypeKind {
|
|
||||||
return errors.New("encountered undefined void value")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
|
||||||
// check value properties
|
|
||||||
if err := c.checkValue(inst, types, specials); err != nil {
|
|
||||||
return errorAt(inst, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// The alloca instruction can be present in every basic block. However,
|
|
||||||
// allocas in basic blocks other than the entry basic block have a number of
|
|
||||||
// problems:
|
|
||||||
// * They are hard to optimize, leading to potential missed optimizations.
|
|
||||||
// * They may cause stack overflows in loops that would otherwise be
|
|
||||||
// innocent.
|
|
||||||
// * They cause extra code to be generated, because it requires the use of
|
|
||||||
// a frame pointer.
|
|
||||||
// * Perhaps most importantly, the coroutine lowering pass of LLVM (as of
|
|
||||||
// LLVM 9) cannot deal with these allocas:
|
|
||||||
// https://llvm.org/docs/Coroutines.html
|
|
||||||
// Therefore, alloca instructions should be limited to the entry block.
|
|
||||||
if !inst.IsAAllocaInst().IsNil() {
|
|
||||||
if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() {
|
|
||||||
return errorAt(inst, "internal error: non-static alloca")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check operands
|
|
||||||
for i := 0; i < inst.OperandsCount(); i++ {
|
|
||||||
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
|
||||||
return errorAt(inst, fmt.Sprintf("failed to validate operand %d of instruction %q: %s", i, inst.Name(), err.Error()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
|
||||||
// check basic block value and type
|
|
||||||
var errs []error
|
|
||||||
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
|
||||||
errs = append(errs, errorAt(bb.Parent(), fmt.Sprintf("failed to validate value of basic block %s: %v", bb.AsValue().Name(), err)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check instructions
|
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
||||||
if err := c.checkInstruction(inst, types, specials); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
|
||||||
// check function value and type
|
|
||||||
var errs []error
|
|
||||||
if err := c.checkValue(fn, types, specials); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("failed to validate value of function %s: %s", fn.Name(), err.Error()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check basic blocks
|
|
||||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
|
||||||
errs = append(errs, c.checkBasicBlock(bb, types, specials)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module checks the given module and returns a slice of error, if there are
|
|
||||||
// any.
|
|
||||||
func Module(mod llvm.Module) []error {
|
|
||||||
// check for any context mismatches
|
|
||||||
var errs []error
|
|
||||||
c := checker{
|
|
||||||
ctx: mod.Context(),
|
|
||||||
}
|
|
||||||
if c.ctx == llvm.GlobalContext() {
|
|
||||||
// somewhere we accidentally used the global context instead of a real context
|
|
||||||
errs = append(errs, errors.New("module uses global context"))
|
|
||||||
}
|
|
||||||
|
|
||||||
types := map[llvm.Type]struct{}{}
|
|
||||||
specials := map[llvm.TypeKind]llvm.Type{}
|
|
||||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
||||||
errs = append(errs, c.checkFunction(fn, types, specials)...)
|
|
||||||
}
|
|
||||||
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
|
||||||
if err := c.checkValue(g, types, specials); err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package ircheck
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// errorAt returns an error value at the location of the instruction.
|
|
||||||
// The location information may not be complete as it depends on debug
|
|
||||||
// information in the IR.
|
|
||||||
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
|
||||||
return scanner.Error{
|
|
||||||
Pos: getPosition(inst),
|
|
||||||
Msg: msg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPosition returns the position information for the given value, as far as
|
|
||||||
// it is available.
|
|
||||||
func getPosition(val llvm.Value) token.Position {
|
|
||||||
if !val.IsAInstruction().IsNil() {
|
|
||||||
loc := val.InstructionDebugLoc()
|
|
||||||
if loc.IsNil() {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
file := loc.LocationScope().ScopeFile()
|
|
||||||
return token.Position{
|
|
||||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
|
||||||
Line: int(loc.LocationLine()),
|
|
||||||
Column: int(loc.LocationColumn()),
|
|
||||||
}
|
|
||||||
} else if !val.IsAFunction().IsNil() {
|
|
||||||
loc := val.Subprogram()
|
|
||||||
if loc.IsNil() {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
file := loc.ScopeFile()
|
|
||||||
return token.Position{
|
|
||||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
|
||||||
Line: int(loc.SubprogramLine()),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return token.Position{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+79
-228
@@ -1,255 +1,106 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"math/big"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains helper functions for LLVM that are not exposed in the Go
|
// This file contains helper functions for LLVM that are not exposed in the Go
|
||||||
// bindings.
|
// bindings.
|
||||||
|
|
||||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
// Return a list of values (actually, instructions) where this value is used as
|
||||||
// lifetime start information in the IR signalling that the alloca won't be used
|
// an operand.
|
||||||
// before this point.
|
func getUses(value llvm.Value) []llvm.Value {
|
||||||
//
|
if value.IsNil() {
|
||||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
return nil
|
||||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
}
|
||||||
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
var uses []llvm.Value
|
||||||
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
|
use := value.FirstUse()
|
||||||
|
for !use.IsNil() {
|
||||||
|
uses = append(uses, use.User())
|
||||||
|
use = use.NextUse()
|
||||||
|
}
|
||||||
|
return uses
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
||||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
// first if it doesn't exist yet.
|
||||||
// createTemporaryAlloca.
|
func (c *Compiler) getLifetimeEndFunc() llvm.Value {
|
||||||
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
|
fn := c.mod.NamedFunction("llvm.lifetime.end.p0i8")
|
||||||
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
|
if fn.IsNil() {
|
||||||
|
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.ctx.Int64Type(), c.i8ptrType}, false)
|
||||||
|
fn = llvm.AddFunction(c.mod, "llvm.lifetime.end.p0i8", fnType)
|
||||||
|
}
|
||||||
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitPointerPack packs the list of values into a single pointer value using
|
// splitBasicBlock splits a LLVM basic block into two parts. All instructions
|
||||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
// after afterInst are moved into a new basic block (created right after the
|
||||||
// pointer value directly. It returns the pointer with the packed data.
|
// current one) with the given name.
|
||||||
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
||||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values)
|
oldBlock := afterInst.InstructionParent()
|
||||||
}
|
newBlock := c.ctx.InsertBasicBlock(insertAfter, name)
|
||||||
|
var nextInstructions []llvm.Value // values to move
|
||||||
|
|
||||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
// Collect to-be-moved instructions.
|
||||||
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
inst := afterInst
|
||||||
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
|
||||||
// contents, and returns the global.
|
|
||||||
// Note that it is left with the default linkage etc., you should set
|
|
||||||
// linkage/constant/etc properties yourself.
|
|
||||||
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
|
|
||||||
globalType := llvm.ArrayType(elementType, len(buf))
|
|
||||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
|
||||||
value := llvm.Undef(globalType)
|
|
||||||
for i := 0; i < len(buf); i++ {
|
|
||||||
ch := uint64(buf[i])
|
|
||||||
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
|
|
||||||
}
|
|
||||||
global.SetInitializer(value)
|
|
||||||
return global
|
|
||||||
}
|
|
||||||
|
|
||||||
// createObjectLayout returns a LLVM value (of type i8*) that describes where
|
|
||||||
// there are pointers in the type t. If all the data fits in a word, it is
|
|
||||||
// returned as a word. Otherwise it will store the data in a global.
|
|
||||||
//
|
|
||||||
// The value contains two pieces of information: the length of the object and
|
|
||||||
// which words contain a pointer (indicated by setting the given bit to 1). For
|
|
||||||
// arrays, only the element is stored. This works because the GC knows the
|
|
||||||
// object size and can therefore know how this value is repeated in the object.
|
|
||||||
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
|
|
||||||
// Use the element type for arrays. This works even for nested arrays.
|
|
||||||
for {
|
for {
|
||||||
kind := t.TypeKind()
|
inst = llvm.NextInstruction(inst)
|
||||||
if kind == llvm.ArrayTypeKind {
|
if inst.IsNil() {
|
||||||
t = t.ElementType()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if kind == llvm.StructTypeKind {
|
|
||||||
fields := t.StructElementTypes()
|
|
||||||
if len(fields) == 1 {
|
|
||||||
t = fields[0]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
nextInstructions = append(nextInstructions, inst)
|
||||||
// Do a few checks to see whether we need to generate any object layout
|
|
||||||
// information at all.
|
|
||||||
objectSizeBytes := c.targetData.TypeAllocSize(t)
|
|
||||||
pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
|
|
||||||
pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
|
||||||
if objectSizeBytes < pointerSize {
|
|
||||||
// Too small to contain a pointer.
|
|
||||||
layout := (uint64(1) << 1) | 1
|
|
||||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
|
||||||
}
|
|
||||||
bitmap := c.getPointerBitmap(t, pos)
|
|
||||||
if bitmap.BitLen() == 0 {
|
|
||||||
// There are no pointers in this type, so we can simplify the layout.
|
|
||||||
// TODO: this can be done in many other cases, e.g. when allocating an
|
|
||||||
// array (like [4][]byte, which repeats a slice 4 times).
|
|
||||||
layout := (uint64(1) << 1) | 1
|
|
||||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
|
||||||
}
|
|
||||||
if objectSizeBytes%uint64(pointerAlignment) != 0 {
|
|
||||||
// This shouldn't happen except for packed structs, which aren't
|
|
||||||
// currently used.
|
|
||||||
c.addError(pos, "internal error: unexpected object size for object with pointer field")
|
|
||||||
return llvm.ConstNull(c.i8ptrType)
|
|
||||||
}
|
|
||||||
objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
|
|
||||||
|
|
||||||
pointerBits := pointerSize * 8
|
|
||||||
var sizeFieldBits uint64
|
|
||||||
switch pointerBits {
|
|
||||||
case 16:
|
|
||||||
sizeFieldBits = 4
|
|
||||||
case 32:
|
|
||||||
sizeFieldBits = 5
|
|
||||||
case 64:
|
|
||||||
sizeFieldBits = 6
|
|
||||||
default:
|
|
||||||
panic("unknown pointer size")
|
|
||||||
}
|
|
||||||
layoutFieldBits := pointerBits - 1 - sizeFieldBits
|
|
||||||
|
|
||||||
// Try to emit the value as an inline integer. This is possible in most
|
|
||||||
// cases.
|
|
||||||
if objectSizeWords < layoutFieldBits {
|
|
||||||
// If it can be stored directly in the pointer value, do so.
|
|
||||||
// The runtime knows that if the least significant bit of the pointer is
|
|
||||||
// set, the pointer contains the value itself.
|
|
||||||
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
|
|
||||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unfortunately, the object layout is too big to fit in a pointer-sized
|
// Move instructions.
|
||||||
// integer. Store it in a global instead.
|
c.builder.SetInsertPointAtEnd(newBlock)
|
||||||
|
for _, inst := range nextInstructions {
|
||||||
// Try first whether the global already exists. All objects with a
|
inst.RemoveFromParentAsInstruction()
|
||||||
// particular name have the same type, so this is possible.
|
c.builder.Insert(inst)
|
||||||
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
|
|
||||||
global := c.mod.NamedGlobal(globalName)
|
|
||||||
if !global.IsNil() {
|
|
||||||
return llvm.ConstBitCast(global, c.i8ptrType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the global initializer.
|
// Find PHI nodes to update.
|
||||||
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
|
var phiNodes []llvm.Value // PHI nodes to update
|
||||||
copy(bitmapBytes, bitmap.Bytes())
|
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||||
var bitmapByteValues []llvm.Value
|
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||||
for _, b := range bitmapBytes {
|
if inst.IsAPHINode().IsNil() {
|
||||||
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
|
|
||||||
}
|
|
||||||
initializer := c.ctx.ConstStruct([]llvm.Value{
|
|
||||||
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
|
|
||||||
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
|
|
||||||
}, false)
|
|
||||||
|
|
||||||
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
|
|
||||||
global.SetInitializer(initializer)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
global.SetGlobalConstant(true)
|
|
||||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
|
|
||||||
// AVR doesn't have alignment by default.
|
|
||||||
global.SetAlignment(2)
|
|
||||||
}
|
|
||||||
if c.Debug && pos != token.NoPos {
|
|
||||||
// Creating a fake global so that the value can be inspected in GDB.
|
|
||||||
// For example, the layout for strings.stringFinder (as of Go version
|
|
||||||
// 1.15) has the following type according to GDB:
|
|
||||||
// type = struct {
|
|
||||||
// uintptr numBits;
|
|
||||||
// uint8 data[33];
|
|
||||||
// }
|
|
||||||
// ...that's sort of a mixed C/Go type, but it is readable. More
|
|
||||||
// importantly, these object layout globals can be read and printed by
|
|
||||||
// GDB which may be useful for debugging.
|
|
||||||
position := c.program.Fset.Position(pos)
|
|
||||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{
|
|
||||||
Name: globalName,
|
|
||||||
File: c.getDIFile(position.Filename),
|
|
||||||
Line: position.Line,
|
|
||||||
Type: c.getDIType(types.NewStruct([]*types.Var{
|
|
||||||
types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]),
|
|
||||||
types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))),
|
|
||||||
}, nil)),
|
|
||||||
LocalToUnit: false,
|
|
||||||
Expr: c.dibuilder.CreateExpression(nil),
|
|
||||||
})
|
|
||||||
global.AddMetadata(0, diglobal)
|
|
||||||
}
|
|
||||||
|
|
||||||
return llvm.ConstBitCast(global, c.i8ptrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
|
|
||||||
// bigint at the word offset that contains a pointer. This scan is recursive.
|
|
||||||
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
|
|
||||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
|
||||||
switch typ.TypeKind() {
|
|
||||||
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
|
||||||
return big.NewInt(0)
|
|
||||||
case llvm.PointerTypeKind:
|
|
||||||
return big.NewInt(1)
|
|
||||||
case llvm.StructTypeKind:
|
|
||||||
ptrs := big.NewInt(0)
|
|
||||||
if typ.StructName() == "runtime.funcValue" {
|
|
||||||
// Hack: the type runtime.funcValue contains an 'id' field which is
|
|
||||||
// of type uintptr, but before the LowerFuncValues pass it actually
|
|
||||||
// contains a pointer (ptrtoint) to a global. This trips up the
|
|
||||||
// interp package. Therefore, make the id field a pointer for now.
|
|
||||||
typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
|
|
||||||
}
|
|
||||||
for i, subtyp := range typ.StructElementTypes() {
|
|
||||||
subptrs := c.getPointerBitmap(subtyp, pos)
|
|
||||||
if subptrs.BitLen() == 0 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
offset := c.targetData.ElementOffset(typ, i)
|
needsUpdate := false
|
||||||
if offset%uint64(alignment) != 0 {
|
incomingCount := inst.IncomingCount()
|
||||||
// This error will let the compilation fail, but by continuing
|
for i := 0; i < incomingCount; i++ {
|
||||||
// the error can still easily be shown.
|
if inst.IncomingBlock(i) == oldBlock {
|
||||||
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
|
needsUpdate = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !needsUpdate {
|
||||||
|
// PHI node has no incoming edge from the old block.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
phiNodes = append(phiNodes, inst)
|
||||||
ptrs.Or(ptrs, subptrs)
|
|
||||||
}
|
|
||||||
return ptrs
|
|
||||||
case llvm.ArrayTypeKind:
|
|
||||||
subtyp := typ.ElementType()
|
|
||||||
subptrs := c.getPointerBitmap(subtyp, pos)
|
|
||||||
ptrs := big.NewInt(0)
|
|
||||||
if subptrs.BitLen() == 0 {
|
|
||||||
return ptrs
|
|
||||||
}
|
|
||||||
elementSize := c.targetData.TypeAllocSize(subtyp)
|
|
||||||
if elementSize%uint64(alignment) != 0 {
|
|
||||||
// This error will let the compilation fail (but continues so that
|
|
||||||
// other errors can be shown).
|
|
||||||
c.addError(pos, "internal error: allocated array contains unaligned pointer")
|
|
||||||
return ptrs
|
|
||||||
}
|
|
||||||
for i := 0; i < typ.ArrayLength(); i++ {
|
|
||||||
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
|
||||||
ptrs.Or(ptrs, subptrs)
|
|
||||||
}
|
|
||||||
return ptrs
|
|
||||||
default:
|
|
||||||
// Should not happen.
|
|
||||||
panic("unknown LLVM type")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update PHI nodes.
|
||||||
|
for _, phi := range phiNodes {
|
||||||
|
c.builder.SetInsertPointBefore(phi)
|
||||||
|
newPhi := c.builder.CreatePHI(phi.Type(), "")
|
||||||
|
incomingCount := phi.IncomingCount()
|
||||||
|
incomingVals := make([]llvm.Value, incomingCount)
|
||||||
|
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
||||||
|
for i := 0; i < incomingCount; i++ {
|
||||||
|
value := phi.IncomingValue(i)
|
||||||
|
block := phi.IncomingBlock(i)
|
||||||
|
if block == oldBlock {
|
||||||
|
block = newBlock
|
||||||
|
}
|
||||||
|
incomingVals[i] = value
|
||||||
|
incomingBlocks[i] = block
|
||||||
|
}
|
||||||
|
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
||||||
|
phi.ReplaceAllUsesWith(newPhi)
|
||||||
|
phi.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBlock
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
// Package llvmutil contains utility functions used across multiple compiler
|
|
||||||
// packages. For example, they may be used by both the compiler pacakge and
|
|
||||||
// transformation packages.
|
|
||||||
//
|
|
||||||
// Normally, utility packages are avoided. However, in this case, the utility
|
|
||||||
// functions are non-trivial and hard to get right. Copying them to multiple
|
|
||||||
// places would be a big risk if only one of them is updated.
|
|
||||||
package llvmutil
|
|
||||||
|
|
||||||
import "tinygo.org/x/go-llvm"
|
|
||||||
|
|
||||||
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though
|
|
||||||
// the IR builder is located elsewhere. It assumes that the insert point is
|
|
||||||
// at the end of the current block.
|
|
||||||
func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm.Value {
|
|
||||||
currentBlock := builder.GetInsertBlock()
|
|
||||||
entryBlock := currentBlock.Parent().EntryBasicBlock()
|
|
||||||
if entryBlock.FirstInstruction().IsNil() {
|
|
||||||
builder.SetInsertPointAtEnd(entryBlock)
|
|
||||||
} else {
|
|
||||||
builder.SetInsertPointBefore(entryBlock.FirstInstruction())
|
|
||||||
}
|
|
||||||
alloca := builder.CreateAlloca(t, name)
|
|
||||||
builder.SetInsertPointAtEnd(currentBlock)
|
|
||||||
return alloca
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTemporaryAlloca creates a new alloca in the entry block and adds
|
|
||||||
// lifetime start infromation in the IR signalling that the alloca won't be used
|
|
||||||
// before this point.
|
|
||||||
//
|
|
||||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
|
||||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
|
||||||
func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
|
||||||
ctx := t.Context()
|
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
|
||||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
|
||||||
alloca = CreateEntryBlockAlloca(builder, t, name)
|
|
||||||
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
|
||||||
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
|
||||||
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInstructionAlloca creates an alloca in the entry block, and places lifetime control intrinsics around the instruction
|
|
||||||
func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, inst llvm.Value, name string) llvm.Value {
|
|
||||||
ctx := mod.Context()
|
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
|
||||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
|
||||||
|
|
||||||
alloca := CreateEntryBlockAlloca(builder, t, name)
|
|
||||||
builder.SetInsertPointBefore(inst)
|
|
||||||
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
|
|
||||||
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
|
|
||||||
builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
|
|
||||||
if next := llvm.NextInstruction(inst); !next.IsNil() {
|
|
||||||
builder.SetInsertPointBefore(next)
|
|
||||||
} else {
|
|
||||||
builder.SetInsertPointAtEnd(inst.InstructionParent())
|
|
||||||
}
|
|
||||||
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "")
|
|
||||||
return alloca
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
|
||||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
|
||||||
// createTemporaryAlloca.
|
|
||||||
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
|
|
||||||
builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
|
||||||
// first if it doesn't exist yet.
|
|
||||||
func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
|
|
||||||
fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
|
|
||||||
ctx := mod.Context()
|
|
||||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
|
||||||
if fn.IsNil() {
|
|
||||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
|
||||||
fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType)
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
|
|
||||||
// first if it doesn't exist yet.
|
|
||||||
func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
|
|
||||||
fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
|
|
||||||
ctx := mod.Context()
|
|
||||||
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
|
||||||
if fn.IsNil() {
|
|
||||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
|
|
||||||
fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType)
|
|
||||||
}
|
|
||||||
return fn
|
|
||||||
}
|
|
||||||
|
|
||||||
// SplitBasicBlock splits a LLVM basic block into two parts. All instructions
|
|
||||||
// after afterInst are moved into a new basic block (created right after the
|
|
||||||
// current one) with the given name.
|
|
||||||
func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llvm.BasicBlock, name string) llvm.BasicBlock {
|
|
||||||
oldBlock := afterInst.InstructionParent()
|
|
||||||
newBlock := afterInst.Type().Context().InsertBasicBlock(insertAfter, name)
|
|
||||||
var nextInstructions []llvm.Value // values to move
|
|
||||||
|
|
||||||
// Collect to-be-moved instructions.
|
|
||||||
inst := afterInst
|
|
||||||
for {
|
|
||||||
inst = llvm.NextInstruction(inst)
|
|
||||||
if inst.IsNil() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nextInstructions = append(nextInstructions, inst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move instructions.
|
|
||||||
builder.SetInsertPointAtEnd(newBlock)
|
|
||||||
for _, inst := range nextInstructions {
|
|
||||||
inst.RemoveFromParentAsInstruction()
|
|
||||||
builder.Insert(inst)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find PHI nodes to update.
|
|
||||||
var phiNodes []llvm.Value // PHI nodes to update
|
|
||||||
for bb := insertAfter.Parent().FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
|
||||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
|
||||||
if inst.IsAPHINode().IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
needsUpdate := false
|
|
||||||
incomingCount := inst.IncomingCount()
|
|
||||||
for i := 0; i < incomingCount; i++ {
|
|
||||||
if inst.IncomingBlock(i) == oldBlock {
|
|
||||||
needsUpdate = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !needsUpdate {
|
|
||||||
// PHI node has no incoming edge from the old block.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
phiNodes = append(phiNodes, inst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update PHI nodes.
|
|
||||||
for _, phi := range phiNodes {
|
|
||||||
builder.SetInsertPointBefore(phi)
|
|
||||||
newPhi := builder.CreatePHI(phi.Type(), "")
|
|
||||||
incomingCount := phi.IncomingCount()
|
|
||||||
incomingVals := make([]llvm.Value, incomingCount)
|
|
||||||
incomingBlocks := make([]llvm.BasicBlock, incomingCount)
|
|
||||||
for i := 0; i < incomingCount; i++ {
|
|
||||||
value := phi.IncomingValue(i)
|
|
||||||
block := phi.IncomingBlock(i)
|
|
||||||
if block == oldBlock {
|
|
||||||
block = newBlock
|
|
||||||
}
|
|
||||||
incomingVals[i] = value
|
|
||||||
incomingBlocks[i] = block
|
|
||||||
}
|
|
||||||
newPhi.AddIncoming(incomingVals, incomingBlocks)
|
|
||||||
phi.ReplaceAllUsesWith(newPhi)
|
|
||||||
phi.EraseFromParentAsInstruction()
|
|
||||||
}
|
|
||||||
|
|
||||||
return newBlock
|
|
||||||
}
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
package llvmutil
|
|
||||||
|
|
||||||
// This file contains utility functions to pack and unpack sets of values. It
|
|
||||||
// can take in a list of values and tries to store it efficiently in the pointer
|
|
||||||
// itself if possible and legal.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EmitPointerPack packs the list of values into a single pointer value using
|
|
||||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
|
||||||
// pointer value directly. It returns the pointer with the packed data.
|
|
||||||
// If the values are all constants, they are be stored in a constant global and deduplicated.
|
|
||||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value {
|
|
||||||
ctx := mod.Context()
|
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
|
||||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
|
||||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
|
||||||
|
|
||||||
valueTypes := make([]llvm.Type, len(values))
|
|
||||||
for i, value := range values {
|
|
||||||
valueTypes[i] = value.Type()
|
|
||||||
}
|
|
||||||
packedType := ctx.StructType(valueTypes, false)
|
|
||||||
|
|
||||||
// Allocate memory for the packed data.
|
|
||||||
size := targetData.TypeAllocSize(packedType)
|
|
||||||
if size == 0 {
|
|
||||||
return llvm.ConstPointerNull(i8ptrType)
|
|
||||||
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
|
|
||||||
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
|
|
||||||
} else if size <= targetData.TypeAllocSize(i8ptrType) {
|
|
||||||
// Packed data fits in a pointer, so store it directly inside the
|
|
||||||
// pointer.
|
|
||||||
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
|
|
||||||
// Try to keep this cast in SSA form.
|
|
||||||
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
|
||||||
// it in a *i8 alloca first and load the *i8 value from there. This is
|
|
||||||
// effectively a bitcast.
|
|
||||||
packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "")
|
|
||||||
|
|
||||||
if size < targetData.TypeAllocSize(i8ptrType) {
|
|
||||||
// The alloca is bigger than the value that will be stored in it.
|
|
||||||
// To avoid having some bits undefined, zero the alloca first.
|
|
||||||
// Hopefully this will get optimized away.
|
|
||||||
builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store all values in the alloca.
|
|
||||||
packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
|
|
||||||
for i, value := range values {
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := builder.CreateInBoundsGEP(packedAllocCast, indices, "")
|
|
||||||
builder.CreateStore(value, gep)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load value (the *i8) from the alloca.
|
|
||||||
result := builder.CreateLoad(packedAlloc, "")
|
|
||||||
|
|
||||||
// End the lifetime of the alloca, to help the optimizer.
|
|
||||||
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
|
||||||
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
|
||||||
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
|
||||||
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
// Check if the values are all constants.
|
|
||||||
constant := true
|
|
||||||
for _, v := range values {
|
|
||||||
if !v.IsConstant() {
|
|
||||||
constant = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if constant {
|
|
||||||
// The data is known at compile time, so store it in a constant global.
|
|
||||||
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
|
|
||||||
global := llvm.AddGlobal(mod, packedType, prefix+"$pack")
|
|
||||||
global.SetInitializer(ctx.ConstStruct(values, false))
|
|
||||||
global.SetGlobalConstant(true)
|
|
||||||
global.SetUnnamedAddr(true)
|
|
||||||
global.SetLinkage(llvm.InternalLinkage)
|
|
||||||
return llvm.ConstBitCast(global, i8ptrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
|
||||||
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
|
||||||
alloc := mod.NamedFunction("runtime.alloc")
|
|
||||||
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
|
|
||||||
sizeValue,
|
|
||||||
llvm.ConstNull(i8ptrType),
|
|
||||||
llvm.Undef(i8ptrType), // unused context parameter
|
|
||||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
|
||||||
}, "")
|
|
||||||
if needsStackObjects {
|
|
||||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
|
||||||
builder.CreateCall(trackPointer, []llvm.Value{
|
|
||||||
packedHeapAlloc,
|
|
||||||
llvm.Undef(i8ptrType), // unused context parameter
|
|
||||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
|
||||||
}, "")
|
|
||||||
}
|
|
||||||
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
|
||||||
|
|
||||||
// Store all values in the heap pointer.
|
|
||||||
for i, value := range values {
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
|
||||||
builder.CreateStore(value, gep)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the original heap allocation pointer, which already is an *i8.
|
|
||||||
return packedHeapAlloc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EmitPointerUnpack extracts a list of values packed using EmitPointerPack.
|
|
||||||
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
|
||||||
ctx := mod.Context()
|
|
||||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
|
||||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
|
||||||
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
|
||||||
|
|
||||||
packedType := ctx.StructType(valueTypes, false)
|
|
||||||
|
|
||||||
// Get a correctly-typed pointer to the packed data.
|
|
||||||
var packedAlloc, packedRawAlloc llvm.Value
|
|
||||||
size := targetData.TypeAllocSize(packedType)
|
|
||||||
if size == 0 {
|
|
||||||
// No data to unpack.
|
|
||||||
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
|
|
||||||
// A single pointer is always stored directly.
|
|
||||||
return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
|
|
||||||
} else if size <= targetData.TypeAllocSize(i8ptrType) {
|
|
||||||
// Packed data stored directly in pointer.
|
|
||||||
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
|
|
||||||
// Keep this cast in SSA form.
|
|
||||||
return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
|
|
||||||
}
|
|
||||||
// Fallback: load it using an alloca.
|
|
||||||
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
|
|
||||||
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
|
|
||||||
builder.CreateStore(packedRawValue, packedRawAlloc)
|
|
||||||
packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
|
|
||||||
} else {
|
|
||||||
// Packed data stored on the heap. Bitcast the passed-in pointer to the
|
|
||||||
// correct pointer type.
|
|
||||||
packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
|
|
||||||
}
|
|
||||||
// Load each value from the packed data.
|
|
||||||
values := make([]llvm.Value, len(valueTypes))
|
|
||||||
for i, valueType := range valueTypes {
|
|
||||||
if targetData.TypeAllocSize(valueType) == 0 {
|
|
||||||
// This value has length zero, so there's nothing to load.
|
|
||||||
values[i] = llvm.ConstNull(valueType)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
indices := []llvm.Value{
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
|
||||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
|
||||||
}
|
|
||||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
|
||||||
values[i] = builder.CreateLoad(gep, "")
|
|
||||||
}
|
|
||||||
if !packedRawAlloc.IsNil() {
|
|
||||||
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
|
|
||||||
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
|
|
||||||
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
|
|
||||||
}
|
|
||||||
return values
|
|
||||||
}
|
|
||||||
+54
-122
@@ -6,165 +6,99 @@ import (
|
|||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
|
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||||
// initializing an appropriately sized object.
|
llvmValueType := c.getLLVMType(valueType)
|
||||||
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
mapValueAlloca := c.builder.CreateAlloca(llvmValueType, "hashmap.value")
|
||||||
mapType := expr.Type().Underlying().(*types.Map)
|
mapValuePtr := c.builder.CreateBitCast(mapValueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||||
keyType := mapType.Key().Underlying()
|
|
||||||
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
|
|
||||||
var llvmKeyType llvm.Type
|
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
|
||||||
// String keys.
|
|
||||||
llvmKeyType = b.getLLVMType(keyType)
|
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
|
||||||
// Trivially comparable keys.
|
|
||||||
llvmKeyType = b.getLLVMType(keyType)
|
|
||||||
} else {
|
|
||||||
// All other keys. Implemented as map[interface{}]valueType for ease of
|
|
||||||
// implementation.
|
|
||||||
llvmKeyType = b.getLLVMRuntimeType("_interface")
|
|
||||||
}
|
|
||||||
keySize := b.targetData.TypeAllocSize(llvmKeyType)
|
|
||||||
valueSize := b.targetData.TypeAllocSize(llvmValueType)
|
|
||||||
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
|
|
||||||
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
|
|
||||||
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
|
|
||||||
if expr.Reserve != nil {
|
|
||||||
sizeHint = b.getValue(expr.Reserve)
|
|
||||||
var err error
|
|
||||||
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
|
|
||||||
if err != nil {
|
|
||||||
return llvm.Value{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
|
|
||||||
return hashmap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createMapLookup returns the value in a map. It calls a runtime function
|
|
||||||
// depending on the map key type to load the map value and its comma-ok value.
|
|
||||||
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
|
||||||
llvmValueType := b.getLLVMType(valueType)
|
|
||||||
|
|
||||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
|
||||||
// will be zeroed by the hashmap get implementation if the key is not
|
|
||||||
// present in the map.
|
|
||||||
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
|
||||||
|
|
||||||
// We need the map size (with type uintptr) to pass to the hashmap*Get
|
|
||||||
// functions. This is necessary because those *Get functions are valid on
|
|
||||||
// nil maps, and they'll need to zero the value pointer by that number of
|
|
||||||
// bytes.
|
|
||||||
mapValueSize := mapValueAllocaSize
|
|
||||||
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
|
||||||
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do the lookup. How it is done depends on the key type.
|
|
||||||
var commaOkValue llvm.Value
|
var commaOkValue llvm.Value
|
||||||
keyType = keyType.Underlying()
|
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
|
params := []llvm.Value{m, key, mapValuePtr}
|
||||||
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
|
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||||
// growth.
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||||
b.CreateStore(key, mapKeyAlloca)
|
params := []llvm.Value{m, keyPtr, mapValuePtr}
|
||||||
// Fetch the value from the hashmap.
|
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
|
|
||||||
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
|
|
||||||
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
|
||||||
} else {
|
} else {
|
||||||
// Not trivially comparable using memcmp. Make it an interface instead.
|
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
itfKey := key
|
|
||||||
if _, ok := keyType.(*types.Interface); !ok {
|
|
||||||
// Not already an interface, so convert it to an interface now.
|
|
||||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
|
||||||
}
|
}
|
||||||
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
|
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||||
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the resulting value from the hashmap. The value is set to the zero
|
|
||||||
// value if the key doesn't exist in the hashmap.
|
|
||||||
mapValue := b.CreateLoad(mapValueAlloca, "")
|
|
||||||
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
|
|
||||||
|
|
||||||
if commaOk {
|
if commaOk {
|
||||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||||
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
|
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
||||||
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
|
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
|
||||||
return tuple, nil
|
return tuple, nil
|
||||||
} else {
|
} else {
|
||||||
return mapValue, nil
|
return mapValue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMapUpdate updates a map key to a given value, by creating an
|
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||||
// appropriate runtime call.
|
valueAlloca := c.builder.CreateAlloca(value.Type(), "hashmap.value")
|
||||||
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
c.builder.CreateStore(value, valueAlloca)
|
||||||
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
|
valuePtr := c.builder.CreateBitCast(valueAlloca, c.i8ptrType, "hashmap.valueptr")
|
||||||
b.CreateStore(value, valueAlloca)
|
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key, valuePtr}
|
params := []llvm.Value{m, key, valuePtr}
|
||||||
b.createRuntimeCall("hashmapStringSet", params, "")
|
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
// key can be compared with runtime.memequal
|
// key can be compared with runtime.memequal
|
||||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||||
b.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||||
b.createRuntimeCall("hashmapBinarySet", params, "")
|
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||||
b.emitLifetimeEnd(keyPtr, keySize)
|
|
||||||
} else {
|
} else {
|
||||||
// Key is not trivially comparable, so compare it as an interface instead.
|
c.addError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
itfKey := key
|
|
||||||
if _, ok := keyType.(*types.Interface); !ok {
|
|
||||||
// Not already an interface, so convert it to an interface first.
|
|
||||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
|
||||||
}
|
}
|
||||||
params := []llvm.Value{m, itfKey, valuePtr}
|
|
||||||
b.createRuntimeCall("hashmapInterfaceSet", params, "")
|
|
||||||
}
|
|
||||||
b.emitLifetimeEnd(valuePtr, valueSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createMapDelete deletes a key from a map by calling the appropriate runtime
|
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||||
// function. It is the implementation of the Go delete() builtin.
|
|
||||||
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
|
||||||
keyType = keyType.Underlying()
|
keyType = keyType.Underlying()
|
||||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||||
// key is a string
|
// key is a string
|
||||||
params := []llvm.Value{m, key}
|
params := []llvm.Value{m, key}
|
||||||
b.createRuntimeCall("hashmapStringDelete", params, "")
|
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||||
return nil
|
return nil
|
||||||
} else if hashmapIsBinaryKey(keyType) {
|
} else if hashmapIsBinaryKey(keyType) {
|
||||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
keyAlloca := c.builder.CreateAlloca(key.Type(), "hashmap.key")
|
||||||
b.CreateStore(key, keyAlloca)
|
c.builder.CreateStore(key, keyAlloca)
|
||||||
|
keyPtr := c.builder.CreateBitCast(keyAlloca, c.i8ptrType, "hashmap.keyptr")
|
||||||
params := []llvm.Value{m, keyPtr}
|
params := []llvm.Value{m, keyPtr}
|
||||||
b.createRuntimeCall("hashmapBinaryDelete", params, "")
|
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||||
b.emitLifetimeEnd(keyPtr, keySize)
|
|
||||||
return nil
|
return nil
|
||||||
} else {
|
} else {
|
||||||
// Key is not trivially comparable, so compare it as an interface
|
return c.makeError(pos, "only strings, bools, ints or structs of bools/ints are supported as map keys, but got: "+keyType.String())
|
||||||
// instead.
|
|
||||||
itfKey := key
|
|
||||||
if _, ok := keyType.(*types.Interface); !ok {
|
|
||||||
// Not already an interface, so convert it to an interface first.
|
|
||||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
|
||||||
}
|
}
|
||||||
params := []llvm.Value{m, itfKey}
|
|
||||||
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 hashmapHash(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 hashmapTopHash(hash uint32) uint8 {
|
||||||
|
tophash := uint8(hash >> 24)
|
||||||
|
if tophash < 1 {
|
||||||
|
// 0 means empty slot, so make it bigger.
|
||||||
|
tophash += 1
|
||||||
|
}
|
||||||
|
return tophash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if this key type does not contain strings, interfaces etc., so
|
// Returns true if this key type does not contain strings, interfaces etc., so
|
||||||
@@ -173,8 +107,6 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
|
|||||||
switch keyType := keyType.(type) {
|
switch keyType := keyType.(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
|
||||||
case *types.Pointer:
|
|
||||||
return true
|
|
||||||
case *types.Struct:
|
case *types.Struct:
|
||||||
for i := 0; i < keyType.NumFields(); i++ {
|
for i := 0; i < keyType.NumFields(); i++ {
|
||||||
fieldType := keyType.Field(i).Type().Underlying()
|
fieldType := keyType.Field(i).Type().Underlying()
|
||||||
|
|||||||
@@ -0,0 +1,374 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Run the LLVM optimizer over the module.
|
||||||
|
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||||
|
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
|
||||||
|
builder := llvm.NewPassManagerBuilder()
|
||||||
|
defer builder.Dispose()
|
||||||
|
builder.SetOptLevel(optLevel)
|
||||||
|
builder.SetSizeLevel(sizeLevel)
|
||||||
|
if inlinerThreshold != 0 {
|
||||||
|
builder.UseInlinerWithThreshold(inlinerThreshold)
|
||||||
|
}
|
||||||
|
builder.AddCoroutinePassesToExtensionPoints()
|
||||||
|
|
||||||
|
if c.PanicStrategy == "trap" {
|
||||||
|
c.replacePanicsWithTrap() // -panic=trap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run function passes for each function.
|
||||||
|
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||||
|
defer funcPasses.Dispose()
|
||||||
|
builder.PopulateFunc(funcPasses)
|
||||||
|
funcPasses.InitializeFunc()
|
||||||
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
|
funcPasses.RunFunc(fn)
|
||||||
|
}
|
||||||
|
funcPasses.FinalizeFunc()
|
||||||
|
|
||||||
|
if optLevel > 0 {
|
||||||
|
// Run some preparatory passes for the Go optimizer.
|
||||||
|
goPasses := llvm.NewPassManager()
|
||||||
|
defer goPasses.Dispose()
|
||||||
|
goPasses.AddGlobalOptimizerPass()
|
||||||
|
goPasses.AddConstantPropagationPass()
|
||||||
|
goPasses.AddAggressiveDCEPass()
|
||||||
|
goPasses.AddFunctionAttrsPass()
|
||||||
|
goPasses.Run(c.mod)
|
||||||
|
|
||||||
|
// Run Go-specific optimization passes.
|
||||||
|
c.OptimizeMaps()
|
||||||
|
c.OptimizeStringToBytes()
|
||||||
|
c.OptimizeAllocs()
|
||||||
|
c.LowerInterfaces()
|
||||||
|
c.LowerFuncValues()
|
||||||
|
|
||||||
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
|
// interprocedural optimizations. To get them to work, function
|
||||||
|
// attributes have to be updated first.
|
||||||
|
goPasses.Run(c.mod)
|
||||||
|
|
||||||
|
// Run TinyGo-specific interprocedural optimizations.
|
||||||
|
c.OptimizeAllocs()
|
||||||
|
c.OptimizeStringToBytes()
|
||||||
|
|
||||||
|
// Lower runtime.isnil calls to regular nil comparisons.
|
||||||
|
isnil := c.mod.NamedFunction("runtime.isnil")
|
||||||
|
if !isnil.IsNil() {
|
||||||
|
for _, use := range getUses(isnil) {
|
||||||
|
c.builder.SetInsertPointBefore(use)
|
||||||
|
ptr := use.Operand(0)
|
||||||
|
if !ptr.IsABitCastInst().IsNil() {
|
||||||
|
ptr = ptr.Operand(0)
|
||||||
|
}
|
||||||
|
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||||
|
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||||
|
use.ReplaceAllUsesWith(icmp)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.LowerGoroutines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Must be run at any optimization level.
|
||||||
|
c.LowerInterfaces()
|
||||||
|
c.LowerFuncValues()
|
||||||
|
err := c.LowerGoroutines()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := c.Verify(); err != nil {
|
||||||
|
return errors.New("optimizations caused a verification failure")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sizeLevel >= 2 {
|
||||||
|
// Set the "optsize" attribute to make slightly smaller binaries at the
|
||||||
|
// cost of some performance.
|
||||||
|
kind := llvm.AttributeKindID("optsize")
|
||||||
|
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
||||||
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
|
fn.AddFunctionAttr(attr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||||
|
// doesn't get lowered.
|
||||||
|
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
|
funcPasses.RunFunc(fn)
|
||||||
|
}
|
||||||
|
funcPasses.FinalizeFunc()
|
||||||
|
|
||||||
|
// Run module passes.
|
||||||
|
modPasses := llvm.NewPassManager()
|
||||||
|
defer modPasses.Dispose()
|
||||||
|
builder.Populate(modPasses)
|
||||||
|
modPasses.Run(c.mod)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace panic calls with calls to llvm.trap, to reduce code size. This is the
|
||||||
|
// -panic=trap intrinsic.
|
||||||
|
func (c *Compiler) replacePanicsWithTrap() {
|
||||||
|
trap := c.mod.NamedFunction("llvm.trap")
|
||||||
|
for _, name := range []string{"runtime._panic", "runtime.runtimePanic"} {
|
||||||
|
fn := c.mod.NamedFunction(name)
|
||||||
|
if fn.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, use := range getUses(fn) {
|
||||||
|
if use.IsACallInst().IsNil() || use.CalledValue() != fn {
|
||||||
|
panic("expected use of a panic function to be a call")
|
||||||
|
}
|
||||||
|
c.builder.SetInsertPointBefore(use)
|
||||||
|
c.builder.CreateCall(trap, nil, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminate created but not used maps.
|
||||||
|
//
|
||||||
|
// In the future, this should statically allocate created but never modified
|
||||||
|
// maps. This has not yet been implemented, however.
|
||||||
|
func (c *Compiler) OptimizeMaps() {
|
||||||
|
hashmapMake := c.mod.NamedFunction("runtime.hashmapMake")
|
||||||
|
if hashmapMake.IsNil() {
|
||||||
|
// nothing to optimize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hashmapBinarySet := c.mod.NamedFunction("runtime.hashmapBinarySet")
|
||||||
|
hashmapStringSet := c.mod.NamedFunction("runtime.hashmapStringSet")
|
||||||
|
|
||||||
|
for _, makeInst := range getUses(hashmapMake) {
|
||||||
|
updateInsts := []llvm.Value{}
|
||||||
|
unknownUses := false // are there any uses other than setting a value?
|
||||||
|
|
||||||
|
for _, use := range getUses(makeInst) {
|
||||||
|
if use := use.IsACallInst(); !use.IsNil() {
|
||||||
|
switch use.CalledValue() {
|
||||||
|
case hashmapBinarySet, hashmapStringSet:
|
||||||
|
updateInsts = append(updateInsts, use)
|
||||||
|
default:
|
||||||
|
unknownUses = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unknownUses = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unknownUses {
|
||||||
|
// This map can be entirely removed, as it is only created but never
|
||||||
|
// used.
|
||||||
|
for _, inst := range updateInsts {
|
||||||
|
inst.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
makeInst.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
||||||
|
// possible. This optimizes the following pattern:
|
||||||
|
// w.Write([]byte("foo"))
|
||||||
|
// where Write does not store to the slice.
|
||||||
|
func (c *Compiler) OptimizeStringToBytes() {
|
||||||
|
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
|
||||||
|
if stringToBytes.IsNil() {
|
||||||
|
// nothing to optimize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, call := range getUses(stringToBytes) {
|
||||||
|
strptr := call.Operand(0)
|
||||||
|
strlen := call.Operand(1)
|
||||||
|
|
||||||
|
// strptr is always constant because strings are always constant.
|
||||||
|
|
||||||
|
convertedAllUses := true
|
||||||
|
for _, use := range getUses(call) {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if use.IsAExtractValueInst() == nilValue {
|
||||||
|
convertedAllUses = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch use.Type().TypeKind() {
|
||||||
|
case llvm.IntegerTypeKind:
|
||||||
|
// A length (len or cap). Propagate the length value.
|
||||||
|
use.ReplaceAllUsesWith(strlen)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
case llvm.PointerTypeKind:
|
||||||
|
// The string pointer itself.
|
||||||
|
if !c.isReadOnly(use) {
|
||||||
|
convertedAllUses = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
use.ReplaceAllUsesWith(strptr)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
default:
|
||||||
|
// should not happen
|
||||||
|
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if convertedAllUses {
|
||||||
|
// Call to runtime.stringToBytes can be eliminated: both the input
|
||||||
|
// and the output is constant.
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basic escape analysis: translate runtime.alloc calls into alloca
|
||||||
|
// instructions.
|
||||||
|
func (c *Compiler) OptimizeAllocs() {
|
||||||
|
allocator := c.mod.NamedFunction("runtime.alloc")
|
||||||
|
if allocator.IsNil() {
|
||||||
|
// nothing to optimize
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
heapallocs := getUses(allocator)
|
||||||
|
for _, heapalloc := range heapallocs {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if heapalloc.Operand(0).IsAConstant() == nilValue {
|
||||||
|
// Do not allocate variable length arrays on the stack.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
size := heapalloc.Operand(0).ZExtValue()
|
||||||
|
if size > 256 {
|
||||||
|
// The maximum value for a stack allocation.
|
||||||
|
// TODO: tune this, this is just a random value.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// In general the pattern is:
|
||||||
|
// %0 = call i8* @runtime.alloc(i32 %size)
|
||||||
|
// %1 = bitcast i8* %0 to type*
|
||||||
|
// (use %1 only)
|
||||||
|
// But the bitcast might sometimes be dropped when allocating an *i8.
|
||||||
|
// The 'bitcast' variable below is thus usually a bitcast of the
|
||||||
|
// heapalloc but not always.
|
||||||
|
bitcast := heapalloc // instruction that creates the value
|
||||||
|
if uses := getUses(heapalloc); len(uses) == 1 && uses[0].IsABitCastInst() != nilValue {
|
||||||
|
// getting only bitcast use
|
||||||
|
bitcast = uses[0]
|
||||||
|
}
|
||||||
|
if !c.doesEscape(bitcast) {
|
||||||
|
// Insert alloca in the entry block. Do it here so that mem2reg can
|
||||||
|
// promote it to a SSA value.
|
||||||
|
fn := bitcast.InstructionParent().Parent()
|
||||||
|
c.builder.SetInsertPointBefore(fn.EntryBasicBlock().FirstInstruction())
|
||||||
|
alignment := c.targetData.ABITypeAlignment(c.i8ptrType)
|
||||||
|
sizeInWords := (size + uint64(alignment) - 1) / uint64(alignment)
|
||||||
|
allocaType := llvm.ArrayType(c.ctx.IntType(alignment*8), int(sizeInWords))
|
||||||
|
alloca := c.builder.CreateAlloca(allocaType, "stackalloc.alloca")
|
||||||
|
zero := c.getZeroValue(alloca.Type().ElementType())
|
||||||
|
c.builder.CreateStore(zero, alloca)
|
||||||
|
stackalloc := c.builder.CreateBitCast(alloca, bitcast.Type(), "stackalloc")
|
||||||
|
bitcast.ReplaceAllUsesWith(stackalloc)
|
||||||
|
if heapalloc != bitcast {
|
||||||
|
bitcast.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
heapalloc.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Very basic escape analysis.
|
||||||
|
func (c *Compiler) doesEscape(value llvm.Value) bool {
|
||||||
|
uses := getUses(value)
|
||||||
|
for _, use := range uses {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if use.IsAGetElementPtrInst() != nilValue {
|
||||||
|
if c.doesEscape(use) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsABitCastInst() != nilValue {
|
||||||
|
// A bitcast escapes if the casted-to value escapes.
|
||||||
|
if c.doesEscape(use) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsALoadInst() != nilValue {
|
||||||
|
// Load does not escape.
|
||||||
|
} else if use.IsAStoreInst() != nilValue {
|
||||||
|
// Store only escapes when the value is stored to, not when the
|
||||||
|
// value is stored into another value.
|
||||||
|
if use.Operand(0) == value {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsACallInst() != nilValue {
|
||||||
|
if !c.hasFlag(use, value, "nocapture") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if use.IsAICmpInst() != nilValue {
|
||||||
|
// Comparing pointers don't let the pointer escape.
|
||||||
|
// This is often a compiler-inserted nil check.
|
||||||
|
} else {
|
||||||
|
// Unknown instruction, might escape.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// does not escape
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the given value (which is of pointer type) is never stored to.
|
||||||
|
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||||
|
uses := getUses(value)
|
||||||
|
for _, use := range uses {
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if use.IsAGetElementPtrInst() != nilValue {
|
||||||
|
if !c.isReadOnly(use) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if use.IsACallInst() != nilValue {
|
||||||
|
if !c.hasFlag(use, value, "readonly") {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Unknown instruction, might not be readonly.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether all uses of this param as parameter to the call have the given
|
||||||
|
// flag. In most cases, there will only be one use but a function could take the
|
||||||
|
// same parameter twice, in which case both must have the flag.
|
||||||
|
// A flag can be any enum flag, like "readonly".
|
||||||
|
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||||
|
fn := call.CalledValue()
|
||||||
|
nilValue := llvm.Value{}
|
||||||
|
if fn.IsAFunction() == nilValue {
|
||||||
|
// This is not a function but something else, like a function pointer.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
kindID := llvm.AttributeKindID(kind)
|
||||||
|
for i := 0; i < fn.ParamsCount(); i++ {
|
||||||
|
if call.Operand(i) != param {
|
||||||
|
// This is not the parameter we're checking.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
index := i + 1 // param attributes start at 1
|
||||||
|
attr := fn.GetEnumAttributeAtIndex(index, kindID)
|
||||||
|
nilAttribute := llvm.Attribute{}
|
||||||
|
if attr == nilAttribute {
|
||||||
|
// At least one parameter doesn't have the flag (there may be
|
||||||
|
// multiple).
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
package compiler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var basicTypes = map[string]int64{
|
||||||
|
"bool": 1,
|
||||||
|
"int": 2,
|
||||||
|
"int8": 3,
|
||||||
|
"int16": 4,
|
||||||
|
"int32": 5,
|
||||||
|
"int64": 6,
|
||||||
|
"uint": 7,
|
||||||
|
"uint8": 8,
|
||||||
|
"uint16": 9,
|
||||||
|
"uint32": 10,
|
||||||
|
"uint64": 11,
|
||||||
|
"uintptr": 12,
|
||||||
|
"float32": 13,
|
||||||
|
"float64": 14,
|
||||||
|
"complex64": 15,
|
||||||
|
"complex128": 16,
|
||||||
|
"string": 17,
|
||||||
|
"unsafeptr": 18,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
|
||||||
|
fn := c.mod.NamedFunction("reflect.ValueOf")
|
||||||
|
if fn.IsNil() {
|
||||||
|
// reflect.ValueOf is never used, so we can use the most efficient
|
||||||
|
// encoding possible.
|
||||||
|
for i, t := range typeSlice {
|
||||||
|
t.num = uint64(i + 1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign typecodes the way the reflect package expects.
|
||||||
|
fallbackIndex := 1
|
||||||
|
namedTypes := make(map[string]int)
|
||||||
|
for _, t := range typeSlice {
|
||||||
|
if t.name[:5] != "type:" {
|
||||||
|
panic("expected type name to start with 'type:'")
|
||||||
|
}
|
||||||
|
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
|
||||||
|
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
|
||||||
|
// TODO: support this in some way, using a side table for example.
|
||||||
|
// That's less efficient but better than not working at all.
|
||||||
|
// Particularly important on systems with 16-bit pointers (e.g.
|
||||||
|
// AVR).
|
||||||
|
panic("compiler: could not store type code number inside interface type code")
|
||||||
|
}
|
||||||
|
t.num = num.Uint64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||||
|
// reflect package. Also see getTypeCodeName, which serializes types to a string
|
||||||
|
// based on a types.Type value for this function.
|
||||||
|
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
|
||||||
|
// Note: see src/reflect/type.go for bit allocations.
|
||||||
|
// A type can be named or unnamed. Example of both:
|
||||||
|
// basic:~foo:uint64
|
||||||
|
// basic:uint64
|
||||||
|
// Extract the class (basic, slice, pointer, etc.), the name, and the
|
||||||
|
// contents of this type ID string. Allocate bits based on that, as
|
||||||
|
// src/runtime/types.go expects.
|
||||||
|
class := id[:strings.IndexByte(id, ':')]
|
||||||
|
value := id[len(class)+1:]
|
||||||
|
name := ""
|
||||||
|
if value[0] == '~' {
|
||||||
|
name = value[1:strings.IndexByte(value, ':')]
|
||||||
|
value = value[len(name)+2:]
|
||||||
|
}
|
||||||
|
if class == "basic" {
|
||||||
|
// Basic types follow the following bit pattern:
|
||||||
|
// ...xxxxx0
|
||||||
|
// where xxxxx is allocated for the 18 possible basic types and all the
|
||||||
|
// upper bits are used to indicate the named type.
|
||||||
|
num, ok := basicTypes[value]
|
||||||
|
if !ok {
|
||||||
|
panic("invalid basic type: " + id)
|
||||||
|
}
|
||||||
|
if name != "" {
|
||||||
|
// This type is named, set the upper bits to the name ID.
|
||||||
|
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
|
||||||
|
}
|
||||||
|
return big.NewInt(num << 1)
|
||||||
|
} else {
|
||||||
|
// Complex types use the following bit pattern:
|
||||||
|
// ...nxxx1
|
||||||
|
// where xxx indicates the complex type (any non-basic type). The upper
|
||||||
|
// bits contain whatever the type contains. Types that wrap a single
|
||||||
|
// other type (channel, interface, pointer, slice) just contain the bits
|
||||||
|
// of the wrapped type. Other types (like struct) have a different
|
||||||
|
// method of encoding the contents of the type.
|
||||||
|
var num *big.Int
|
||||||
|
var classNumber int64
|
||||||
|
switch class {
|
||||||
|
case "chan":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 0
|
||||||
|
case "interface":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 1
|
||||||
|
case "pointer":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 2
|
||||||
|
case "slice":
|
||||||
|
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
|
||||||
|
classNumber = 3
|
||||||
|
case "array":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 4
|
||||||
|
case "func":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 5
|
||||||
|
case "map":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 6
|
||||||
|
case "struct":
|
||||||
|
num = big.NewInt(int64(*fallbackIndex))
|
||||||
|
*fallbackIndex++
|
||||||
|
classNumber = 7
|
||||||
|
default:
|
||||||
|
panic("unknown type kind: " + id)
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
|
||||||
|
} else {
|
||||||
|
// TODO: store num in a sidetable
|
||||||
|
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
|
||||||
|
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNamedTypeNum returns an appropriate (unique) number for the given named
|
||||||
|
// type. If the name already has a number that number is returned, else a new
|
||||||
|
// number is returned. The number is always non-zero.
|
||||||
|
func getNamedTypeNum(namedTypes map[string]int, name string) int {
|
||||||
|
if num, ok := namedTypes[name]; ok {
|
||||||
|
return num
|
||||||
|
} else {
|
||||||
|
num = len(namedTypes) + 1
|
||||||
|
namedTypes[name] = num
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
-12
@@ -10,13 +10,13 @@ import (
|
|||||||
// The original license can be found here:
|
// The original license can be found here:
|
||||||
// https://golang.org/LICENSE
|
// https://golang.org/LICENSE
|
||||||
|
|
||||||
type stdSizes struct {
|
type StdSizes struct {
|
||||||
IntSize int64
|
IntSize int64
|
||||||
PtrSize int64
|
PtrSize int64
|
||||||
MaxAlign int64
|
MaxAlign int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stdSizes) Alignof(T types.Type) int64 {
|
func (s *StdSizes) Alignof(T types.Type) int64 {
|
||||||
// For arrays and structs, alignment is defined in terms
|
// For arrays and structs, alignment is defined in terms
|
||||||
// of alignment of the elements and fields, respectively.
|
// of alignment of the elements and fields, respectively.
|
||||||
switch t := T.Underlying().(type) {
|
switch t := T.Underlying().(type) {
|
||||||
@@ -61,8 +61,14 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
|
|||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
|
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||||
offsets := make([]int64, len(fields))
|
offsets := make([]int64, len(fields))
|
||||||
|
if len(fields) > 1 && fields[0].Name() == "C union" {
|
||||||
|
// This struct contains the magic "C union" field which indicates that
|
||||||
|
// this is actually a union from CGo.
|
||||||
|
// All fields in the union start at 0 so return that.
|
||||||
|
return offsets // all fields are still set to 0
|
||||||
|
}
|
||||||
var o int64
|
var o int64
|
||||||
for i, f := range fields {
|
for i, f := range fields {
|
||||||
a := s.Alignof(f.Type())
|
a := s.Alignof(f.Type())
|
||||||
@@ -89,7 +95,7 @@ var basicSizes = [...]byte{
|
|||||||
types.Complex128: 16,
|
types.Complex128: 16,
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stdSizes) Sizeof(T types.Type) int64 {
|
func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||||
switch t := T.Underlying().(type) {
|
switch t := T.Underlying().(type) {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
k := t.Kind()
|
k := t.Kind()
|
||||||
@@ -110,9 +116,6 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
|
|||||||
if k == types.UnsafePointer {
|
if k == types.UnsafePointer {
|
||||||
return s.PtrSize
|
return s.PtrSize
|
||||||
}
|
}
|
||||||
if k == types.Invalid {
|
|
||||||
return 0 // only relevant when there is a type error somewhere
|
|
||||||
}
|
|
||||||
panic("unknown basic type: " + t.String())
|
panic("unknown basic type: " + t.String())
|
||||||
case *types.Array:
|
case *types.Array:
|
||||||
n := t.Len()
|
n := t.Len()
|
||||||
@@ -140,19 +143,33 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
|
|||||||
maxAlign = al
|
maxAlign = al
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if fields[0].Name() == "C union" {
|
||||||
|
// Magic field that indicates this is a CGo union and not a struct.
|
||||||
|
// The size is the biggest element, aligned to the element with the
|
||||||
|
// biggest alignment. This is not necessarily the same, for example
|
||||||
|
// in the following union:
|
||||||
|
// union { int32_t l; int16_t s[3] }
|
||||||
|
maxSize := int64(0)
|
||||||
|
for _, field := range fields[1:] {
|
||||||
|
si := s.Sizeof(field.Type())
|
||||||
|
if si > maxSize {
|
||||||
|
maxSize = si
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return align(maxSize, maxAlign)
|
||||||
|
} else {
|
||||||
|
// This is a regular struct.
|
||||||
// Pick the size that fits this struct and add some alignment. Some
|
// Pick the size that fits this struct and add some alignment. Some
|
||||||
// structs have some extra padding at the end which should also be taken
|
// structs have some extra padding at the end which should also be
|
||||||
// care of:
|
// taken care of:
|
||||||
// struct { int32 n; byte b }
|
// struct { int32 n; byte b }
|
||||||
offsets := s.Offsetsof(fields)
|
offsets := s.Offsetsof(fields)
|
||||||
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
|
||||||
|
}
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
return s.PtrSize * 2
|
return s.PtrSize * 2
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
return s.PtrSize
|
return s.PtrSize
|
||||||
case *types.Signature:
|
|
||||||
// Func values in TinyGo are two words in size.
|
|
||||||
return s.PtrSize * 2
|
|
||||||
default:
|
default:
|
||||||
panic("unknown type: " + t.String())
|
panic("unknown type: " + t.String())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,526 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file manages symbols, that is, functions and globals. It reads their
|
|
||||||
// pragmas, determines the link name, etc.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
"go/types"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"golang.org/x/tools/go/ssa"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// functionInfo contains some information about a function or method. In
|
|
||||||
// particular, it contains information obtained from pragmas.
|
|
||||||
//
|
|
||||||
// The linkName value contains a valid link name, even if //go:linkname is not
|
|
||||||
// present.
|
|
||||||
type functionInfo struct {
|
|
||||||
module string // go:wasm-module
|
|
||||||
importName string // go:linkname, go:export - The name the developer assigns
|
|
||||||
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
|
|
||||||
section string // go:section - object file section name
|
|
||||||
exported bool // go:export, CGo
|
|
||||||
interrupt bool // go:interrupt
|
|
||||||
nobounds bool // go:nobounds
|
|
||||||
variadic bool // go:variadic (CGo only)
|
|
||||||
inline inlineType // go:inline
|
|
||||||
}
|
|
||||||
|
|
||||||
type inlineType int
|
|
||||||
|
|
||||||
// How much to inline.
|
|
||||||
const (
|
|
||||||
// Default behavior. The compiler decides for itself whether any given
|
|
||||||
// function will be inlined. Whether any function is inlined depends on the
|
|
||||||
// optimization level.
|
|
||||||
inlineDefault inlineType = iota
|
|
||||||
|
|
||||||
// Inline hint, just like the C inline keyword (signalled using
|
|
||||||
// //go:inline). The compiler will be more likely to inline this function,
|
|
||||||
// but it is not a guarantee.
|
|
||||||
inlineHint
|
|
||||||
|
|
||||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
|
||||||
// //go:noinline.
|
|
||||||
inlineNone
|
|
||||||
)
|
|
||||||
|
|
||||||
// getFunction returns the LLVM function for the given *ssa.Function, creating
|
|
||||||
// it if needed. It can later be filled with compilerContext.createFunction().
|
|
||||||
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
|
||||||
info := c.getFunctionInfo(fn)
|
|
||||||
llvmFn := c.mod.NamedFunction(info.linkName)
|
|
||||||
if !llvmFn.IsNil() {
|
|
||||||
return llvmFn
|
|
||||||
}
|
|
||||||
|
|
||||||
var retType llvm.Type
|
|
||||||
if fn.Signature.Results() == nil {
|
|
||||||
retType = c.ctx.VoidType()
|
|
||||||
} else if fn.Signature.Results().Len() == 1 {
|
|
||||||
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
|
|
||||||
} else {
|
|
||||||
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
|
|
||||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
|
||||||
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
|
|
||||||
}
|
|
||||||
retType = c.ctx.StructType(results, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
var paramInfos []paramInfo
|
|
||||||
for _, param := range getParams(fn.Signature) {
|
|
||||||
paramType := c.getLLVMType(param.Type())
|
|
||||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
|
||||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add an extra parameter as the function context. This context is used in
|
|
||||||
// closures and bound methods, but should be optimized away when not used.
|
|
||||||
if !info.exported {
|
|
||||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
|
|
||||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
|
|
||||||
}
|
|
||||||
|
|
||||||
var paramTypes []llvm.Type
|
|
||||||
for _, info := range paramInfos {
|
|
||||||
paramTypes = append(paramTypes, info.llvmType)
|
|
||||||
}
|
|
||||||
|
|
||||||
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
|
|
||||||
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
|
|
||||||
if strings.HasPrefix(c.Triple, "wasm") {
|
|
||||||
// C functions without prototypes like this:
|
|
||||||
// void foo();
|
|
||||||
// are actually variadic functions. However, it appears that it has been
|
|
||||||
// decided in WebAssembly that such prototype-less functions are not
|
|
||||||
// allowed in WebAssembly.
|
|
||||||
// In C, this can only happen when there are zero parameters, hence this
|
|
||||||
// check here. For more information:
|
|
||||||
// https://reviews.llvm.org/D48443
|
|
||||||
// https://github.com/WebAssembly/tool-conventions/issues/16
|
|
||||||
if info.variadic && len(fn.Params) == 0 {
|
|
||||||
attr := c.ctx.CreateStringAttribute("no-prototype", "")
|
|
||||||
llvmFn.AddFunctionAttr(attr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
|
||||||
|
|
||||||
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
|
|
||||||
for i, info := range paramInfos {
|
|
||||||
if info.flags¶mIsDeferenceableOrNull == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
|
|
||||||
el := info.llvmType.ElementType()
|
|
||||||
size := c.targetData.TypeAllocSize(el)
|
|
||||||
if size == 0 {
|
|
||||||
// dereferenceable_or_null(0) appears to be illegal in LLVM.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
|
|
||||||
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set a number of function or parameter attributes, depending on the
|
|
||||||
// function. These functions are runtime functions that are known to have
|
|
||||||
// certain attributes that might not be inferred by the compiler.
|
|
||||||
switch info.linkName {
|
|
||||||
case "abort":
|
|
||||||
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
|
|
||||||
// Mark it as noreturn so LLVM can optimize away code.
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
|
|
||||||
case "runtime.alloc":
|
|
||||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
|
||||||
// returns values that are never null and never alias to an existing value.
|
|
||||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
|
||||||
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
|
|
||||||
}
|
|
||||||
case "runtime.sliceAppend":
|
|
||||||
// Appending a slice will only read the to-be-appended slice, it won't
|
|
||||||
// be modified.
|
|
||||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
|
||||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
|
||||||
case "runtime.sliceCopy":
|
|
||||||
// Copying a slice won't capture any of the parameters.
|
|
||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
|
|
||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
|
||||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
|
||||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
|
||||||
case "runtime.trackPointer":
|
|
||||||
// This function is necessary for tracking pointers on the stack in a
|
|
||||||
// portable way (see gc_stack_portable.go). Indicate to the optimizer
|
|
||||||
// that the only thing we'll do is read the pointer.
|
|
||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
|
||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
// External/exported functions may not retain pointer values.
|
|
||||||
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
|
||||||
if info.exported {
|
|
||||||
// Set the wasm-import-module attribute if the function's module is set.
|
|
||||||
if info.module != "" {
|
|
||||||
|
|
||||||
// We need to add the wasm-import-module and the wasm-import-name
|
|
||||||
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
|
|
||||||
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
|
|
||||||
|
|
||||||
// Add the Wasm Import Name, if we are a named wasm import
|
|
||||||
if info.importName != "" {
|
|
||||||
wasmImportNameAttr := c.ctx.CreateStringAttribute("wasm-import-name", info.importName)
|
|
||||||
llvmFn.AddFunctionAttr(wasmImportNameAttr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
|
||||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
|
||||||
for i, typ := range paramTypes {
|
|
||||||
if typ.TypeKind() == llvm.PointerTypeKind {
|
|
||||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Synthetic functions are functions that do not appear in the source code,
|
|
||||||
// they are artificially constructed. Usually they are wrapper functions
|
|
||||||
// that are not referenced anywhere except in a SSA call instruction so
|
|
||||||
// should be created right away.
|
|
||||||
// The exception is the package initializer, which does appear in the
|
|
||||||
// *ssa.Package members and so shouldn't be created here.
|
|
||||||
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
|
|
||||||
irbuilder := c.ctx.NewBuilder()
|
|
||||||
b := newBuilder(c, irbuilder, fn)
|
|
||||||
b.createFunction()
|
|
||||||
irbuilder.Dispose()
|
|
||||||
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
|
|
||||||
llvmFn.SetUnnamedAddr(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return llvmFn
|
|
||||||
}
|
|
||||||
|
|
||||||
// getFunctionInfo returns information about a function that is not directly
|
|
||||||
// present in *ssa.Function, such as the link name and whether it should be
|
|
||||||
// exported.
|
|
||||||
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
|
||||||
info := functionInfo{}
|
|
||||||
if strings.HasPrefix(f.Name(), "C.") {
|
|
||||||
// Created by CGo: such a name cannot be created by regular C code.
|
|
||||||
info.linkName = f.Name()[2:]
|
|
||||||
info.exported = true
|
|
||||||
} else {
|
|
||||||
// Pick the default linkName.
|
|
||||||
info.linkName = f.RelString(nil)
|
|
||||||
}
|
|
||||||
// Check for //go: pragmas, which may change the link name (among others).
|
|
||||||
info.parsePragmas(f)
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
|
||||||
// //export or //go:noinline.
|
|
||||||
func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
|
||||||
if f.Syntax() == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
|
||||||
|
|
||||||
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
|
|
||||||
var importName string
|
|
||||||
|
|
||||||
for _, comment := range decl.Doc.List {
|
|
||||||
text := comment.Text
|
|
||||||
if strings.HasPrefix(text, "//export ") {
|
|
||||||
// Rewrite '//export' to '//go:export' for compatibility with
|
|
||||||
// gc.
|
|
||||||
text = "//go:" + text[2:]
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(text, "//go:") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Fields(text)
|
|
||||||
switch parts[0] {
|
|
||||||
case "//go:export":
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
importName = parts[1]
|
|
||||||
info.exported = true
|
|
||||||
case "//go:interrupt":
|
|
||||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
|
||||||
info.interrupt = true
|
|
||||||
}
|
|
||||||
case "//go:wasm-module":
|
|
||||||
// Alternative comment for setting the import module.
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
info.module = parts[1]
|
|
||||||
case "//go:inline":
|
|
||||||
info.inline = inlineHint
|
|
||||||
case "//go:noinline":
|
|
||||||
info.inline = inlineNone
|
|
||||||
case "//go:linkname":
|
|
||||||
if len(parts) != 3 || parts[1] != f.Name() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Only enable go:linkname when the package imports "unsafe".
|
|
||||||
// This is a slightly looser requirement than what gc uses: gc
|
|
||||||
// requires the file to import "unsafe", not the package as a
|
|
||||||
// whole.
|
|
||||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
|
||||||
info.linkName = parts[2]
|
|
||||||
}
|
|
||||||
case "//go:section":
|
|
||||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
|
||||||
info.section = parts[1]
|
|
||||||
}
|
|
||||||
case "//go:nobounds":
|
|
||||||
// Skip bounds checking in this function. Useful for some
|
|
||||||
// runtime functions.
|
|
||||||
// This is somewhat dangerous and thus only imported in packages
|
|
||||||
// that import unsafe.
|
|
||||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
|
||||||
info.nobounds = true
|
|
||||||
}
|
|
||||||
case "//go:variadic":
|
|
||||||
// The //go:variadic pragma is emitted by the CGo preprocessing
|
|
||||||
// pass for C variadic functions. This includes both explicit
|
|
||||||
// (with ...) and implicit (no parameters in signature)
|
|
||||||
// functions.
|
|
||||||
if strings.HasPrefix(f.Name(), "C.") {
|
|
||||||
// This prefix cannot naturally be created, it must have
|
|
||||||
// been created as a result of CGo preprocessing.
|
|
||||||
info.variadic = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the importName for our exported function if we have one
|
|
||||||
if importName != "" {
|
|
||||||
if info.module == "" {
|
|
||||||
info.linkName = importName
|
|
||||||
} else {
|
|
||||||
// WebAssembly import
|
|
||||||
info.importName = importName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getParams returns the function parameters, including the receiver at the
|
|
||||||
// start. This is an alternative to the Params member of *ssa.Function, which is
|
|
||||||
// not yet populated when the package has not yet been built.
|
|
||||||
func getParams(sig *types.Signature) []*types.Var {
|
|
||||||
params := []*types.Var{}
|
|
||||||
if sig.Recv() != nil {
|
|
||||||
params = append(params, sig.Recv())
|
|
||||||
}
|
|
||||||
for i := 0; i < sig.Params().Len(); i++ {
|
|
||||||
params = append(params, sig.Params().At(i))
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
// addStandardDeclaredAttributes adds attributes that are set for any function,
|
|
||||||
// whether declared or defined.
|
|
||||||
func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) {
|
|
||||||
if c.SizeLevel >= 1 {
|
|
||||||
// Set the "optsize" attribute to make slightly smaller binaries at the
|
|
||||||
// cost of minimal performance loss (-Os in Clang).
|
|
||||||
kind := llvm.AttributeKindID("optsize")
|
|
||||||
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
|
||||||
llvmFn.AddFunctionAttr(attr)
|
|
||||||
}
|
|
||||||
if c.SizeLevel >= 2 {
|
|
||||||
// Set the "minsize" attribute to reduce code size even further,
|
|
||||||
// regardless of performance loss (-Oz in Clang).
|
|
||||||
kind := llvm.AttributeKindID("minsize")
|
|
||||||
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
|
||||||
llvmFn.AddFunctionAttr(attr)
|
|
||||||
}
|
|
||||||
if c.CPU != "" {
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-cpu", c.CPU))
|
|
||||||
}
|
|
||||||
if c.Features != "" {
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-features", c.Features))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addStandardDefinedAttributes adds the set of attributes that are added to
|
|
||||||
// every function defined by TinyGo (even thunks/wrappers), possibly depending
|
|
||||||
// on the architecture. It does not set attributes only set for declared
|
|
||||||
// functions, use addStandardDeclaredAttributes for this.
|
|
||||||
func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
|
|
||||||
// TinyGo does not currently raise exceptions, so set the 'nounwind' flag.
|
|
||||||
// This behavior matches Clang when compiling C source files.
|
|
||||||
// It reduces binary size on Linux a little bit on non-x86_64 targets by
|
|
||||||
// eliminating exception tables for these functions.
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
|
|
||||||
if strings.Split(c.Triple, "-")[0] == "x86_64" {
|
|
||||||
// Required by the ABI.
|
|
||||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addStandardAttribute adds all attributes added to defined functions.
|
|
||||||
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
|
|
||||||
c.addStandardDeclaredAttributes(llvmFn)
|
|
||||||
c.addStandardDefinedAttributes(llvmFn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// globalInfo contains some information about a specific global. By default,
|
|
||||||
// linkName is equal to .RelString(nil) on a global and extern is false, but for
|
|
||||||
// some symbols this is different (due to //go:extern for example).
|
|
||||||
type globalInfo struct {
|
|
||||||
linkName string // go:extern
|
|
||||||
extern bool // go:extern
|
|
||||||
align int // go:align
|
|
||||||
section string // go:section
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
|
||||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
|
||||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
|
||||||
for _, file := range pkg.Files {
|
|
||||||
for _, decl := range file.Decls {
|
|
||||||
switch decl := decl.(type) {
|
|
||||||
case *ast.GenDecl:
|
|
||||||
switch decl.Tok {
|
|
||||||
case token.VAR:
|
|
||||||
if len(decl.Specs) != 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, spec := range decl.Specs {
|
|
||||||
switch spec := spec.(type) {
|
|
||||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
|
||||||
for _, name := range spec.Names {
|
|
||||||
id := pkg.Pkg.Path() + "." + name.Name
|
|
||||||
c.astComments[id] = decl.Doc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
|
||||||
// the LLVM IR if it has not been added already.
|
|
||||||
func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
|
||||||
info := c.getGlobalInfo(g)
|
|
||||||
llvmGlobal := c.mod.NamedGlobal(info.linkName)
|
|
||||||
if llvmGlobal.IsNil() {
|
|
||||||
typ := g.Type().(*types.Pointer).Elem()
|
|
||||||
llvmType := c.getLLVMType(typ)
|
|
||||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
|
||||||
|
|
||||||
// Set alignment from the //go:align comment.
|
|
||||||
var alignInBits uint32
|
|
||||||
alignment := c.targetData.ABITypeAlignment(llvmType)
|
|
||||||
if info.align > alignment {
|
|
||||||
alignment = info.align
|
|
||||||
}
|
|
||||||
if alignment <= 0 || alignment&(alignment-1) != 0 {
|
|
||||||
// Check for power-of-two (or 0).
|
|
||||||
// See: https://stackoverflow.com/a/108360
|
|
||||||
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
|
|
||||||
} else {
|
|
||||||
// Set the alignment only when it is a power of two.
|
|
||||||
alignInBits = uint32(alignment) ^ uint32(alignment-1)
|
|
||||||
llvmGlobal.SetAlignment(alignment)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.Debug && !info.extern {
|
|
||||||
// Add debug info.
|
|
||||||
pos := c.program.Fset.Position(g.Pos())
|
|
||||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
|
||||||
Name: g.RelString(nil),
|
|
||||||
LinkageName: info.linkName,
|
|
||||||
File: c.getDIFile(pos.Filename),
|
|
||||||
Line: pos.Line,
|
|
||||||
Type: c.getDIType(typ),
|
|
||||||
LocalToUnit: false,
|
|
||||||
Expr: c.dibuilder.CreateExpression(nil),
|
|
||||||
AlignInBits: alignInBits,
|
|
||||||
})
|
|
||||||
llvmGlobal.AddMetadata(0, diglobal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return llvmGlobal
|
|
||||||
}
|
|
||||||
|
|
||||||
// getGlobalInfo returns some information about a specific global.
|
|
||||||
func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
|
|
||||||
info := globalInfo{}
|
|
||||||
if strings.HasPrefix(g.Name(), "C.") {
|
|
||||||
// Created by CGo: such a name cannot be created by regular C code.
|
|
||||||
info.linkName = g.Name()[2:]
|
|
||||||
info.extern = true
|
|
||||||
} else {
|
|
||||||
// Pick the default linkName.
|
|
||||||
info.linkName = g.RelString(nil)
|
|
||||||
// Check for //go: pragmas, which may change the link name (among
|
|
||||||
// others).
|
|
||||||
doc := c.astComments[info.linkName]
|
|
||||||
if doc != nil {
|
|
||||||
info.parsePragmas(doc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse //go: pragma comments from the source. In particular, it parses the
|
|
||||||
// //go:extern pragma on globals.
|
|
||||||
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
|
||||||
for _, comment := range doc.List {
|
|
||||||
if !strings.HasPrefix(comment.Text, "//go:") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Fields(comment.Text)
|
|
||||||
switch parts[0] {
|
|
||||||
case "//go:extern":
|
|
||||||
info.extern = true
|
|
||||||
if len(parts) == 2 {
|
|
||||||
info.linkName = parts[1]
|
|
||||||
}
|
|
||||||
case "//go:align":
|
|
||||||
align, err := strconv.Atoi(parts[1])
|
|
||||||
if err == nil {
|
|
||||||
info.align = align
|
|
||||||
}
|
|
||||||
case "//go:section":
|
|
||||||
if len(parts) == 2 {
|
|
||||||
info.section = parts[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all methods of a type.
|
|
||||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
|
||||||
ms := prog.MethodSets.MethodSet(typ)
|
|
||||||
methods := make([]*types.Selection, ms.Len())
|
|
||||||
for i := 0; i < ms.Len(); i++ {
|
|
||||||
methods[i] = ms.At(i)
|
|
||||||
}
|
|
||||||
return methods
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if this package imports "unsafe", false otherwise.
|
|
||||||
func hasUnsafeImport(pkg *types.Package) bool {
|
|
||||||
for _, imp := range pkg.Imports() {
|
|
||||||
if imp == types.Unsafe {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user