Compare commits

..

6 Commits

Author SHA1 Message Date
Ayke van Laethem ebde8b5875 targets/gba: implement interrupt handler 2020-01-06 14:45:32 +01:00
Ayke van Laethem 9d50587d63 targets/gba: make linker script cleaner
Make it clearer where the stack is located.
2020-01-06 11:48:09 +01:00
Ayke van Laethem 5e6f1725ac WIP interrupts via ptrtoint handler 2020-01-05 08:05:38 +01:00
Ayke van Laethem 9cdc2fa768 WIP: interrupt API 2020-01-04 23:40:45 +01:00
Ayke van Laethem 084386262a compiler: add support for debugging globals
This makes most globals visible from GDB, using `info variables`.
2020-01-04 23:40:44 +01:00
Ayke van Laethem 674ddef219 compiler: add globaldce pass to start of optimization pipeline
This reduces code size in a few cases when tested against the drivers
smoketests (although there was one minor increase) without significantly
increasing compile time. In fact, in my testing compile time appears to
be going down a little bit (around 1%, within the noise).
2020-01-04 23:40:44 +01:00
1160 changed files with 20725 additions and 101000 deletions
+268 -75
View File
@@ -6,123 +6,316 @@ commands:
- run:
name: "Pull submodules"
command: git submodule update --init
install-xtensa-toolchain:
apt-dependencies:
parameters:
variant:
llvm:
type: string
steps:
- run:
name: "Install Xtensa toolchain"
name: "Install apt dependencies"
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
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 \
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:
steps:
- run:
name: "Install node.js"
command: |
wget https://nodejs.org/dist/v10.15.1/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
rm node-v10.15.1-linux-x64.tar.xz
llvm-source-linux:
steps:
- restore_cache:
keys:
- llvm-source-14-v3
- llvm-source-9-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-14-v3
key: llvm-source-9-v0
paths:
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/compiler-rt
- llvm-project/lld/include
- llvm-project/llvm/include
hack-ninja-jobs:
steps:
- run:
name: "Hack Ninja to use less jobs"
command: |
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
build-binaryen-linux:
- llvm-project
test-linux:
steps:
- checkout
- submodules
- apt-dependencies:
llvm: "-9"
- install-node
- restore_cache:
keys:
- binaryen-linux-v2
- run:
name: "Build Binaryen"
command: |
make binaryen
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install .
- run: go test -v ./cgo ./compileopts ./interp ./transform .
- run: make gen-device -j4
- run: make smoketest
- save_cache:
key: binaryen-linux-v2
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- build/wasm-opt
test-linux:
parameters:
llvm:
type: string
fmt-check:
type: boolean
default: true
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make fmt-check
assert-test-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update
apt-get install --no-install-recommends -y \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc \
cmake \
ninja-build
- hack-ninja-jobs
- build-binaryen-linux
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v3-{{ checksum "go.mod" }}
- 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-v6
- run: make wasi-libc
- llvm-build-9-linux-v0-assert
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make ASSERT=1 llvm-build
fi
- save_cache:
key: wasi-libc-sysroot-systemclang-v6
key: llvm-build-9-linux-v0-assert
paths:
- lib/wasi-libc/sysroot
- when:
condition: <<parameters.fmt-check>>
steps:
- run:
# Do this before gen-device so that it doesn't check the
# formatting of generated files.
name: Check Go code formatting
command: make fmt-check
- run: make gen-device -j4
- run: make smoketest XTENSA=0
llvm-build
- run: make ASSERT=1
- run:
name: "Test TinyGo"
command: make ASSERT=1 test
- save_cache:
key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run: make gen-device -j4
- run: make smoketest TINYGO=build/tinygo
build-linux:
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
sudo apt-get install \
gcc-arm-linux-gnueabihf \
binutils-arm-none-eabi \
libc6-dev-armel-cross \
gcc-aarch64-linux-gnu \
libc6-dev-arm64-cross \
qemu-system-arm \
qemu-user \
gcc-avr \
avr-libc
- install-node
- restore_cache:
keys:
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v2-{{ checksum "go.mod" }}
- llvm-source-linux
- restore_cache:
keys:
- llvm-build-9-linux-v0
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
sudo apt-get install cmake clang ninja-build
# make build faster
export CC=clang
export CXX=clang++
# hack ninja to use less jobs
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-9-linux-v0
paths:
llvm-build
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Build TinyGo release"
command: |
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo.linux-amd64.tar.gz
- save_cache:
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
tinygo version
- run: make smoketest
build-macos:
steps:
- checkout
- submodules
- run:
name: "Install dependencies"
command: |
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
ln -s /usr/local/go/bin/go /usr/local/bin/go
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
- restore_cache:
keys:
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-macos-v2-{{ checksum "go.mod" }}
- restore_cache:
keys:
- llvm-source-9-macos-v0
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-9-macos-v0
paths:
- llvm-project
- restore_cache:
keys:
- llvm-build-9-macos-v0
- run:
name: "Build LLVM"
command: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
fi
- save_cache:
key: llvm-build-9-macos-v0
paths:
llvm-build
- run:
name: "Test TinyGo"
command: make test
- run:
name: "Build TinyGo release"
command: |
make release -j3
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
- store_artifacts:
path: /tmp/tinygo.darwin-amd64.tar.gz
- run:
name: "Extract release tarball"
command: |
mkdir -p ~/lib
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
tinygo version
- run:
name: "Download SiFive GNU toolchain"
command: |
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
- run: make smoketest AVR=0
- save_cache:
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- ~/.cache/tinygo
- /go/pkg/mod
jobs:
test-llvm14-go118:
test-llvm9-go111:
docker:
- image: golang:1.18-buster
- image: circleci/golang:1.11-stretch
steps:
- test-linux:
llvm: "14"
resource_class: large
- test-linux
test-llvm9-go112:
docker:
- image: circleci/golang:1.12-stretch
steps:
- test-linux
test-llvm9-go113:
docker:
- image: circleci/golang:1.13-stretch
steps:
- test-linux
assert-test-linux:
docker:
- image: circleci/golang:1.13-stretch
steps:
- assert-test-linux
build-linux:
docker:
- image: circleci/golang:1.13-stretch
steps:
- build-linux
build-macos:
macos:
xcode: "10.1.0"
steps:
- build-macos
workflows:
test-all:
jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm14-go118
- test-llvm9-go111
- test-llvm9-go112
- test-llvm9-go113
- build-linux
- build-macos
- assert-test-linux
-3
View File
@@ -1,3 +0,0 @@
build/
llvm-*/
-122
View File
@@ -1,122 +0,0 @@
name: macOS
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-macos:
name: build-macos
runs-on: macos-11
steps:
- name: Install Dependencies
shell: bash
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
- name: Install Xtensa toolchain
shell: bash
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.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-macos.tar.gz
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-macos-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-macos-v1
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
# install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache wasi-libc sysroot
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball
run: make release -j3
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
shell: bash
run: cp -p build/release.tar.gz build/tinygo.darwin-amd64.tar.gz
- 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 double-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/tinygo.darwin-amd64.tar.gz
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo AVR=0
test-macos-homebrew:
name: homebrew-install
runs-on: macos-latest
steps:
- name: Install LLVM
shell: bash
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@14
- name: Checkout
uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Build TinyGo
run: go install
- name: Check binary
run: tinygo version
-89
View File
@@ -1,89 +0,0 @@
# This is the Github action to build and push the tinygo/tinygo-dev Docker image.
# If you are looking for the tinygo/tinygo "release" Docker image please see
# https://github.com/tinygo-org/docker
#
name: Docker
on:
push:
branches: [ dev, fix-docker-llvm-build ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
push_to_registry:
name: build-push-dev
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v2
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- 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 }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trigger Drivers repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/drivers/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger Bluetooth repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfs/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyFont repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfont/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyDraw repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinydraw/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
-448
View File
@@ -1,448 +0,0 @@
name: Linux
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-linux:
# Build Linux binaries, ready for release.
# This runs inside an Alpine Linux container so we can more easily create a
# statically linked binary.
runs-on: ubuntu-latest
container:
image: golang:1.19-alpine
steps:
- name: Install apk dependencies
# tar: needed for actions/cache@v3
# git+openssh: needed for checkout (I think?)
# ruby: needed to install fpm
run: apk add tar git openssh make g++ ruby
- name: Work around CVE-2022-24765
# We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Cache Go
uses: actions/cache@v3
with:
key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-linux-alpine-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-linux-alpine-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# install dependencies
apk add cmake samurai python3
# build!
make llvm-build
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-alpine-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
apk add cmake samurai python3
make binaryen STATIC=1
- name: Cache wasi-libc
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-alpine-v1
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Install fpm
run: |
gem install --version 4.0.7 public_suffix
gem install --version 2.7.6 dotenv
gem install --no-document fpm
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v2
with:
name: linux-amd64-double-zipped
path: |
/tmp/tinygo.linux-amd64.tar.gz
/tmp/tinygo_amd64.deb
test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Download release artifact
uses: actions/download-artifact@v2
with:
name: linux-amd64-double-zipped
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- name: Install apt dependencies
run: |
sudo apt-get install --no-install-recommends \
gcc-avr \
avr-libc
- name: "Install Xtensa toolchain"
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.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-linux-amd64.tar.gz
- run: make tinygo-test-wasi-fast
- run: make smoketest
assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
# potential bugs.
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install apt dependencies
run: |
echo "Show cpuinfo; sometimes useful when troubleshooting"
cat /proc/cpuinfo
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-system-arm \
qemu-system-riscv32 \
qemu-user \
gcc-avr \
avr-libc \
simavr \
ninja-build
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Install Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Install wasmtime
run: |
curl https://wasmtime.dev/install.sh -sSf | bash
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-linux-asserts-v2
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-linux-asserts-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# build!
make llvm-build ASSERT=1
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-asserts-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: make binaryen
- name: Cache wasi-libc
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-asserts-v5
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- run: make gen-device -j4
- name: Test TinyGo
run: make ASSERT=1 test
- name: Build TinyGo
run: |
make ASSERT=1
echo "$(pwd)/build" >> $GITHUB_PATH
- name: Test stdlib packages
run: make tinygo-test
- name: Install Xtensa toolchain
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.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-linux-amd64.tar.gz
- run: make smoketest
- run: make wasmtest
- run: make tinygo-baremetal
build-linux-arm:
# Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against
# an older glibc version and therefore are compatible with a wide range of
# Linux distributions.
# It is set to "needs: build-linux" because it modifies the release created
# in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball.
runs-on: ubuntu-18.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-arm-linux-gnueabihf \
libc6-dev-armhf-cross
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-linux-v2
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-linux-arm-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# Install build dependencies.
sudo apt-get install --no-install-recommends ninja-build
# build!
make llvm-build CROSS=arm-linux-gnueabihf
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-arm-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
sudo apt-get install --no-install-recommends ninja-build
git submodule update --init lib/binaryen
make CROSS=arm-linux-gnueabihf binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
sudo gem install --version 2.7.6 dotenv
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=arm-linux-gnueabihf
- name: Download amd64 release
uses: actions/download-artifact@v2
with:
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=armhf
cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz
cp -p build/release.deb /tmp/tinygo_armhf.deb
- name: Publish release artifact
uses: actions/upload-artifact@v2
with:
name: linux-arm-double-zipped
path: |
/tmp/tinygo.linux-arm.tar.gz
/tmp/tinygo_armhf.deb
build-linux-arm64:
# Build ARM64 Linux binaries, ready for release.
# It is set to "needs: build-linux" because it modifies the release created
# in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball.
runs-on: ubuntu-18.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-aarch64-linux-gnu \
libc6-dev-arm64-cross \
ninja-build
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-linux-arm64-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# build!
make llvm-build CROSS=aarch64-linux-gnu
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-arm64-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
git submodule update --init lib/binaryen
make CROSS=aarch64-linux-gnu binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
sudo gem install --version 2.7.6 dotenv
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=aarch64-linux-gnu
- name: Download amd64 release
uses: actions/download-artifact@v2
with:
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm64 release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=arm64
cp -p build/release.tar.gz /tmp/tinygo.linux-arm64.tar.gz
cp -p build/release.deb /tmp/tinygo_arm64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v2
with:
name: linux-arm64-double-zipped
path: |
/tmp/tinygo.linux-arm64.tar.gz
/tmp/tinygo_arm64.deb
-104
View File
@@ -1,104 +0,0 @@
name: Windows
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-windows:
runs-on: windows-2022
steps:
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop install ninja binaryen
- name: Checkout
uses: actions/checkout@v2
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.19'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-windows-v2
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
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@v3
id: cache-llvm-build
with:
key: llvm-build-14-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 CCACHE=OFF
# 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@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Install wasmtime
run: |
scoop install wasmtime
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball
shell: bash
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=$(PWD)/build/tinygo AVR=0 XTENSA=0
- name: Test stdlib packages
run: make tinygo-test
- name: Test stdlib packages on wasi
run: make tinygo-test-wasi-fast
+1 -19
View File
@@ -1,34 +1,16 @@
build
docs/_build
src/device/avr/*.go
src/device/avr/*.ld
src/device/avr/*.s
src/device/esp/*.go
src/device/nrf/*.go
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/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
src/device/rp/*.go
src/device/rp/*.s
vendor
llvm-build
llvm-project
build/*
# Ignore files generated by smoketest
test
test.bin
test.elf
test.exe
test.gba
test.hex
test.nro
test.wasm
wasm.wasm
+4 -21
View File
@@ -10,24 +10,7 @@
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/tinygo-org/cmsis-svd
[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
[submodule "lib/macos-minimal-sdk"]
path = lib/macos-minimal-sdk
url = https://github.com/aykevl/macos-minimal-sdk.git
[submodule "lib/compiler-rt"]
path = lib/compiler-rt
url = https://github.com/llvm-mirror/compiler-rt.git
branch = release_80
+6 -8
View File
@@ -10,15 +10,12 @@ 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
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 help with development of TinyGo itself, you should follow the guide located at https://tinygo.org/docs/guides/build/
## Dependencies
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.18+)
* Go (1.11+)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -38,6 +35,11 @@ You can also store LLVM outside of the TinyGo root directory by setting the
`LLVM_BUILDDIR`, `CLANG_SRC` and `LLD_SRC` make variables, but that is not
covered by this guide.
TinyGo uses Go modules, so if you clone TinyGo inside your GOPATH (and are using
Go below 1.13), make sure that Go modules are enabled:
export GO111MODULE=on
## Build LLVM, Clang, LLD
Before starting the build, you may want to set the following environment
@@ -82,10 +84,6 @@ Now that we have a working static build, it's time to make a release tarball:
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 following command (for example in ~/lib):
-1133
View File
File diff suppressed because it is too large Load Diff
+52 -1
View File
@@ -1 +1,52 @@
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details. Thank you.
# How to contribute
Thank you for your interest in improving TinyGo.
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
### New to TinyGo
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
### Something in TinyGo is not working as you expect
Please open a Github issue with your problem, and we will be happy to assist.
### Something in Go that you want/need does not appear to be in TinyGo
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
### Some specific hardware you want to use does not appear to be in TinyGo
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
Please start by opening a Github issue. We want to help you to help us to help you.
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
## How to use our Github repository
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:
- Fork repo
- Create a feature branch off of the `dev` branch
- Make some useful change
- Make sure the tests still pass
- Submit a pull request against the `dev` branch.
- Be kind
## How to run tests
To run the tests:
```
make test
```
-1
View File
@@ -15,4 +15,3 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
Daniel Esteban <conejo@conejo.me>
Loon, LLC.
Ron Evans <ron@hybridgroup.com>
Nia Weiss <niaow1234@gmail.com>
+63 -37
View File
@@ -1,50 +1,76 @@
# tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.19 AS tinygo-llvm
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
FROM golang:1.13 AS tinygo-base
RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-11 binutils-avr gcc-avr avr-libc ninja-build
COPY ./Makefile /tinygo/Makefile
RUN cd /tinygo/ && \
make llvm-source
# tinygo-llvm-build stage build the custom llvm with xtensa support
FROM tinygo-llvm AS tinygo-llvm-build
RUN cd /tinygo/ && \
make llvm-build
# tinygo-xtensa stage installs tools needed for ESP32
FROM tinygo-llvm-build AS tinygo-xtensa
ARG xtensa_version="1.22.0-80-g6c4433a-5.2.0"
RUN cd /tmp/ && \
wget -q https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
tar xzf xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
cp ./xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/ && \
rm -rf /tmp/xtensa*
# tinygo-compiler stage builds the compiler itself
FROM tinygo-xtensa AS tinygo-compiler
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y llvm-9-dev libclang-9-dev git
COPY . /tinygo
# update submodules
# remove submodules directories and re-init them to fix any hard-coded paths
# after copying the tinygo directory in the previous step.
RUN cd /tinygo/ && \
rm -rf ./lib/*/ && \
git submodule sync && \
rm -rf ./lib/* && \
git submodule update --init --recursive --force
RUN cd /tinygo/ && \
make
go install /tinygo/
# tinygo-tools stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-compiler AS tinygo-tools
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
FROM tinygo-base AS tinygo-wasm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
apt-get update && \
apt-get install -y libllvm9 lld-9
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
FROM tinygo-base AS tinygo-avr
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
make wasi-libc binaryen && \
make gen-device -j4 && \
cp build/* $GOPATH/bin/
apt-get update && \
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
make gen-device-avr && \
apt-get autoremove -y && \
apt-get clean
# tinygo-arm stage installs the needed dependencies to compile TinyGo programs for ARM microcontrollers.
FROM tinygo-base AS tinygo-arm
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
COPY --from=tinygo-base /tinygo/src /tinygo/src
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-9 && \
make gen-device-nrf && make gen-device-stm32
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-wasm AS tinygo-all
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
RUN cd /tinygo/ && \
apt-get update && \
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
make gen-device
CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2022 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.
+71 -620
View File
@@ -4,50 +4,23 @@ all: tinygo
# Default build and source directories, as created by `make llvm-build`.
LLVM_BUILDDIR ?= llvm-build
LLVM_PROJECTDIR ?= llvm-project
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools.
# Versions are listed here in descending priority order.
LLVM_VERSIONS = 14 13 12 11
errifempty = $(if $(1),$(1),$(error $(2)))
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
toolSearchPathsVersion = $(1)-$(2)
ifeq ($(shell uname -s),Darwin)
# Also explicitly search Brew's copy, which is not in PATH by default.
BREW_PREFIX := $(shell brew --prefix)
toolSearchPathsVersion += $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)-$(2) $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)
endif
# First search for a custom built copy, then move on to explicitly version-tagged binaries, then just see if the tool is in path with its normal name.
findLLVMTool = $(call detect,$(1),$(abspath llvm-build/bin/$(1)) $(foreach ver,$(LLVM_VERSIONS),$(call toolSearchPathsVersion,$(1),$(ver))) $(1))
CLANG ?= $(call findLLVMTool,clang)
LLVM_AR ?= $(call findLLVMTool,llvm-ar)
LLVM_NM ?= $(call findLLVMTool,llvm-nm)
CLANG_SRC ?= llvm-project/clang
LLD_SRC ?= llvm-project/lld
# Go binary and GOROOT to select
GO ?= go
export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test.
GOTESTFLAGS ?= -v
# md5sum binary
MD5SUM = md5sum
# tinygo binary for tests
TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
TINYGO ?= tinygo
# Check for ccache if the user hasn't set it to on or off.
ifeq (, $(CCACHE))
# Use CCACHE for LLVM if possible
ifneq (, $(shell command -v ccache 2> /dev/null))
CCACHE := ON
else
CCACHE := OFF
endif
# Use CCACHE for LLVM if possible
ifneq (, $(shell which ccache))
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
endif
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=$(CCACHE)'
# Allow enabling LLVM assertions
ifeq (1, $(ASSERT))
@@ -56,58 +29,9 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif
ifeq (1, $(STATIC))
# Build TinyGo as a fully statically linked binary (no dynamically loaded
# libraries such as a libc). This is not supported with glibc which is used
# on most major Linux distributions. However, it is supported in Alpine
# Linux with musl.
CGO_LDFLAGS += -static
# Also set the thread stack size to 1MB. This is necessary on musl as the
# default stack size is 128kB and LLVM uses more than that.
# For more information, see:
# https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-stack-size
CGO_LDFLAGS += -Wl,-z,stack-size=1048576
# Build wasm-opt with static linking.
# For details, see:
# https://github.com/WebAssembly/binaryen/blob/version_102/.github/workflows/ci.yml#L181
BINARYEN_OPTION += -DCMAKE_CXX_FLAGS="-static" -DCMAKE_C_FLAGS="-static"
endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-avr
# Cross compiling support.
ifneq ($(CROSS),)
CC = $(CROSS)-gcc
CXX = $(CROSS)-g++
LLVM_OPTION += \
-DCMAKE_C_COMPILER=$(CC) \
-DCMAKE_CXX_COMPILER=$(CXX) \
-DLLVM_DEFAULT_TARGET_TRIPLE=$(CROSS) \
-DCROSS_TOOLCHAIN_FLAGS_NATIVE="-UCMAKE_C_COMPILER;-UCMAKE_CXX_COMPILER"
ifeq ($(CROSS), arm-linux-gnueabihf)
# Assume we're building on a Debian-like distro, with QEMU installed.
LLVM_CONFIG_PREFIX = qemu-arm -L /usr/arm-linux-gnueabihf/
# The CMAKE_SYSTEM_NAME flag triggers cross compilation mode.
LLVM_OPTION += \
-DCMAKE_SYSTEM_NAME=Linux \
-DLLVM_TARGET_ARCH=ARM
GOENVFLAGS = GOARCH=arm CC=$(CC) CXX=$(CXX) CGO_ENABLED=1
BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX)
else ifeq ($(CROSS), aarch64-linux-gnu)
# Assume we're building on a Debian-like distro, with QEMU installed.
LLVM_CONFIG_PREFIX = qemu-aarch64 -L /usr/aarch64-linux-gnu/
# The CMAKE_SYSTEM_NAME flag triggers cross compilation mode.
LLVM_OPTION += \
-DCMAKE_SYSTEM_NAME=Linux \
-DLLVM_TARGET_ARCH=AArch64
GOENVFLAGS = GOARCH=arm64 CC=$(CC) CXX=$(CXX) CGO_ENABLED=1
BINARYEN_OPTION += -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX)
else
$(error Unknown cross compilation target: $(CROSS))
endif
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
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
ifeq ($(OS),Windows_NT)
EXE = .exe
@@ -117,76 +41,64 @@ ifeq ($(OS),Windows_NT)
# LLVM compiled using MinGW on Windows appears to have problems with threads.
# Without this flag, linking results in errors like these:
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
CGO_LDFLAGS_EXTRA += -lversion
USE_SYSTEM_BINARYEN ?= 1
# Build libclang manually because the CMake-based build system on Windows
# doesn't allow building libclang as a static library.
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
# Add the libclang dependency to the tinygo binary target.
tinygo: $(LIBCLANG_PATH)
test: $(LIBCLANG_PATH)
# Build libclang.
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
@mkdir -p build
ar rcs $(LIBCLANG_PATH) $^
else ifeq ($(shell uname -s),Darwin)
MD5SUM = md5
CGO_LDFLAGS += -lxar
USE_SYSTEM_BINARYEN ?= 1
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
else ifeq ($(shell uname -s),FreeBSD)
MD5SUM = md5
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
else
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group
endif
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD.
LLD_LIB_NAMES = lldCOFF lldCommon lldELF lldMachO lldMinGW lldWasm
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
# Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMX86TargetMCA
# All libraries to be built and linked with the tinygo binary (lib/lib*.a).
LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
# 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,$(LIB_NAMES)))
# For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
CGO_CPPFLAGS=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++11
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif
clean:
@rm -rf build
FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform
FMT_PATHS = ./*.go builder cgo compiler interp ir loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
fmt:
@gofmt -l -w $(FMT_PATHS)
fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
ifneq ($(STM32), 0)
gen-device: gen-device-stm32
endif
gen-device: gen-device-avr gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32
gen-device-avr:
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
@@ -195,245 +107,53 @@ gen-device-avr:
build/gen-device-svd: ./tools/gen-device-svd/*.go
$(GO) build -o $@ ./tools/gen-device-svd/
gen-device-esp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
GO111MODULE=off $(GO) fmt ./src/device/esp
gen-device-nrf: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
GO111MODULE=off $(GO) fmt ./src/device/nrf
gen-device-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/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
GO111MODULE=off $(GO) fmt ./src/device/sifive
gen-device-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/
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
GO111MODULE=off $(GO) fmt ./src/device/stm32
gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp
# Get LLVM sources.
$(LLVM_PROJECTDIR)/llvm:
git clone -b xtensa_release_14.0.0-patched --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm
llvm-project/README.md:
git clone -b release/9.x https://github.com/llvm/llvm-project
llvm-source: llvm-project/README.md
# Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja:
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)
$(LLVM_BUILDDIR)/build.ninja: llvm-source
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/llvm-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
# Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS)
ifneq ($(USE_SYSTEM_BINARYEN),1)
# Build Binaryen
.PHONY: binaryen
binaryen: build/wasm-opt$(EXE)
build/wasm-opt$(EXE):
mkdir -p build
cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE)
cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE)
endif
# 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 -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM)
cd $(LLVM_BUILDDIR); ninja
# Build the Go compiler.
tinygo:
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \
compress/bzip2 \
crypto/dsa \
index/suffixarray \
test:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -tags byollvm ./cgo ./compileopts ./interp ./transform .
# Standard library packages that pass tests quickly on darwin, linux, wasi, and windows
TEST_PACKAGES_FAST = \
compress/zlib \
container/heap \
container/list \
container/ring \
crypto/des \
crypto/internal/subtle \
crypto/md5 \
crypto/rc4 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
debug/macho \
embed/internal/embedtest \
encoding \
encoding/ascii85 \
encoding/base32 \
encoding/csv \
encoding/hex \
go/scanner \
hash \
hash/adler32 \
hash/crc64 \
hash/fnv \
html \
internal/itoa \
internal/profile \
math \
math/cmplx \
net \
net/http/internal/ascii \
net/mail \
os \
path \
reflect \
sync \
testing \
testing/iotest \
text/scanner \
unicode \
unicode/utf16 \
unicode/utf8 \
$(nil)
# Assume this will go away before Go2, so only check minor version.
ifeq ($(filter $(shell $(GO) env GOVERSION | cut -f 2 -d.), 16 17 18), )
TEST_PACKAGES_FAST += crypto/internal/nistec/fiat
else
TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat
endif
# archive/zip requires os.ReadAt, which is not yet supported on windows
# compress/flate appears to hang on wasi
# compress/lzw appears to hang on wasi
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# strconv requires recover() which is not yet supported on wasi
# text/template/parse requires recover(), which is not yet supported on wasi
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
# Additional standard library packages that pass tests on individual platforms
TEST_PACKAGES_LINUX := \
archive/zip \
compress/flate \
compress/lzw \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
io/ioutil \
strconv \
testing/fstest \
text/template/parse
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
TEST_PACKAGES_WINDOWS := \
compress/flate \
compress/lzw \
crypto/hmac \
strconv \
text/template/parse \
$(nil)
# Report platforms on which each standard library package is known to pass tests
jointmp := $(shell echo /tmp/join.$$$$)
report-stdlib-tests-pass:
@for t in $(TEST_PACKAGES_DARWIN); do echo "$$t darwin"; done | sort > $(jointmp).darwin
@for t in $(TEST_PACKAGES_LINUX); do echo "$$t linux"; done | sort > $(jointmp).linux
@for t in $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW); do echo "$$t darwin linux wasi windows"; done | sort > $(jointmp).portable
@join -a1 -a2 $(jointmp).darwin $(jointmp).linux | \
join -a1 -a2 - $(jointmp).portable
@rm $(jointmp).*
# Standard library packages that pass tests quickly on the current platform
ifeq ($(shell uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true
endif
ifeq ($(shell uname),Linux)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
TEST_IOFS := true
endif
ifeq ($(OS),Windows_NT)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif
# 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_HOST) $(TEST_PACKAGES_SLOW)
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143.
ifeq ($(TEST_IOFS),true)
$(TINYGO) test -stack-size=6MB io/fs
endif
tinygo-test-fast:
$(TINYGO) test $(TEST_PACKAGES_HOST)
tinygo-bench:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
tinygo-bench-fast:
$(TINYGO) test -bench . $(TEST_PACKAGES_HOST)
# Same thing, except for wasi rather than the current platform.
tinygo-test-wasi:
$(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasi-fast:
$(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
tinygo-bench-wasi:
$(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
tinygo-bench-wasi-fast:
$(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST)
# Test external packages in a large corpus.
test-corpus:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml
test-corpus-fast:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml
test-corpus-wasi: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasi
tinygo-baremetal:
# Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest.
# regression test for #2666: e.g. encoding/hex must pass on baremetal
$(TINYGO) test -target cortex-m-qemu encoding/hex
cd tests/tinygotest && tinygo test
.PHONY: smoketest
smoketest:
$(TINYGO) version
$(TINYGO) targets > /dev/null
# regression test for #2892
cd tests/testing/recurse && ($(TINYGO) test ./... > recurse.log && cat recurse.log && test $$(wc -l < recurse.log) = 2 && rm recurse.log)
# compile-only platform-independent examples
cd tests/text/template/smoke && $(TINYGO) test -c && rm -f smoke.test
# regression test for #2563
cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test
# test all examples (except pwm)
# test all examples
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
@@ -448,17 +168,13 @@ smoketest:
@$(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=pca10040 examples/echo2
@$(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
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pwm
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
@@ -466,42 +182,30 @@ smoketest:
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=microbit examples/microbit-blink
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/serial
@$(MD5SUM) test.wasm
endif
# 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=bluepill examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
@@ -510,10 +214,6 @@ endif
@$(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
@@ -522,33 +222,23 @@ endif
@$(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
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.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=matrixportal-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
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex
@@ -556,277 +246,38 @@ endif
@$(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=teensy41 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=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
$(TINYGO) build -size short -o test.hex -target=qtpy-rp2040 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=macropad-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=challenger-rp2040 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=trinkey-qt2040 examples/temp
@$(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
# test usb
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/hid-keyboard
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/hid-keyboard
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi
@$(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=nucleo-wl55jc 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
$(TINYGO) build -size short -o test.hex -target=stm32f469disco examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=lorae5 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=swan examples/blinky1
@$(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
$(TINYGO) build -size short -o test.bin -target m5stack examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 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.bin -target=esp32c3-12f examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-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=maixbit examples/blinky1
@$(MD5SUM) test.hex
ifneq ($(WASM), 0)
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
endif
# 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 -size short -o test.exe ./testdata/cgo
GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo
GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./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
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
wasmtest:
$(GO) test ./tests/wasm
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
release: tinygo gen-device
@mkdir -p build/release/tinygo/bin
@mkdir -p build/release/tinygo/lib/clang/include
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@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/compiler-rt/lib
@mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
@mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0
@mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus
@mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
@echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
ifneq ($(USE_SYSTEM_BINARYEN),1)
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
endif
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
@cp -rp lib/macos-minimal-sdk/* build/release/tinygo/lib/macos-minimal-sdk
@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/math 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/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/README.txt build/release/tinygo/lib/compiler-rt
@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/newlib/libm/math 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 llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
@cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
@cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc
./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc
release:
./build/tinygo build-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
tar -czf build/release.tar.gz -C build/release tinygo
DEB_ARCH ?= native
deb:
@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 -a $(DEB_ARCH) -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
ifneq ($(RELEASEONLY), 1)
release: build/release
deb: build/release
endif
+9 -79
View File
@@ -1,6 +1,6 @@
# TinyGo - Go compiler for small places
[![Linux](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [![macOS](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [![Windows](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [![Docker](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
[![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [![Build Status](https://dev.azure.com/tinygo/tinygo/_apis/build/status/tinygo-CI?branchName=dev)](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
@@ -43,101 +43,32 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
The following 91 microcontroller boards are currently supported:
The following 22 microcontroller boards are currently supported:
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit MacroPad RP2040](https://www.adafruit.com/product/5100)
* [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 QT Py RP2040](https://www.adafruit.com/product/4900)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Adafruit Trinkey QT2040](https://adafruit.com/product/5056)
* [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 Nano33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
* [BBC micro:bit](https://microbit.org/)
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
* [blues wireless Swan](https://blues.io/products/swan/)
* [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 - Core board](https://www.espressif.com/en/products/socs/esp32)
* [ESP32 - mini32](https://www.espressif.com/en/products/socs/esp32)
* [ESP32-C3-12f](https://www.espressif.com/en/products/socs/esp32-c3)
* [ESP8266 - d1mini](https://www.espressif.com/en/products/socs/esp8266)
* [ESP8266 - NodeMCU](https://www.espressif.com/en/products/socs/esp8266)
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
* [iLabs Challenger RP2040 LoRa](https://ilabs.se/product/challenger-rp2040-lora/)
* [M5Stack](https://docs.m5stack.com/en/core/basic)
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
* [M5Stamp C3](https://docs.m5stack.com/en/core/stamp_c3)
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
* [MCH2022 badge](https://badge.team/docs/badges/mch2022/)
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
* [Nintendo Switch](https://www.nintendo.com/switch/)
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
* [Pimoroni Badger2040](https://shop.pimoroni.com/products/badger-2040)
* [Pimoroni Tufty2040](https://shop.pimoroni.com/products/tufty-2040)
* [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)
* [PJRC Teensy 4.1](https://www.pjrc.com/store/teensy41.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 Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
* [Seeed XIAO BLE](https://www.seeedstudio.com/Seeed-XIAO-BLE-nRF52840-p-5201.html)
* [Seeed XIAO ESP32C3](https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html)
* [Seeed XIAO RP2040](https://www.seeedstudio.com/XIAO-RP2040-v1-0-p-5026.html)
* [Seeed LoRa-E5 Development Kit](https://www.seeedstudio.com/LoRa-E5-Dev-Kit-p-4868.html)
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
* [SiFIve HiFive1 Rev B](https://www.sifive.com/boards/hifive1-rev-b)
* [Sparkfun Thing Plus RP2040](https://www.sparkfun.com/products/17745)
* [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 "Nucleo" WL55JC](https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html)
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
* [ST Micro STM32F469 "Discovery"](https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-discovery-kits/32f469idiscovery.html)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
@@ -164,7 +95,7 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
Your contributions are welcome!
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details.
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
## Project Scope
@@ -178,6 +109,7 @@ Goals:
Non-goals:
* Using more than one core.
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
* Be able to compile every Go program out there.
@@ -196,6 +128,4 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
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.
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
+74
View File
@@ -0,0 +1,74 @@
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
# be built anyway.
trigger:
- master
- dev
jobs:
- job: Build
timeoutInMinutes: 240 # 4h
pool:
vmImage: 'VS2017-Win2016'
steps:
- checkout: self
- task: CacheBeta@0
displayName: Cache LLVM source
inputs:
key: llvm-source-9-windows-v0
path: llvm-project
- task: Bash@3
displayName: Download LLVM source
inputs:
targetType: inline
script: make llvm-source
- task: CacheBeta@0
displayName: Cache LLVM build
inputs:
key: llvm-build-9-windows-v0
path: llvm-build
- task: Bash@3
displayName: Build LLVM
inputs:
targetType: inline
script: |
if [ ! -f llvm-build/lib/liblldELF.a ]
then
choco install ninja
# LLVM 9 cannot be built with MinGW 8.
# For details: https://reviews.llvm.org/D70266
choco uninstall mingw
choco install mingw --version=7.3.0
make llvm-build
fi
- task: Bash@3
displayName: Install QEMU
inputs:
targetType: inline
script: choco install qemu
- task: Bash@3
displayName: Test TinyGo
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make test
- task: Bash@3
displayName: Build TinyGo release tarball
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make release -j4
- publish: $(System.DefaultWorkingDirectory)/build/release.tar.gz
displayName: Publish tarball as artifact
artifact: tinygo.windows-amd64.tar.gz
- task: Bash@3
displayName: Smoke tests
inputs:
targetType: inline
script: |
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
unset GOROOT
make smoketest TINYGO=build/tinygo AVR=0
+49 -73
View File
@@ -3,10 +3,8 @@ package builder
import (
"bytes"
"debug/elf"
"debug/pe"
"encoding/binary"
"errors"
"fmt"
"io"
"os"
"path/filepath"
@@ -18,13 +16,18 @@ import (
// 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 {
// ar -rcs <archivePath> <objs...>
func makeArchive(archivePath string, objs []string) error {
// Open the archive file.
arwriter := ar.NewWriter(arfile)
err := arwriter.WriteGlobalHeader()
arfile, err := os.Create(archivePath)
if err != nil {
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
return err
}
defer arfile.Close()
arwriter := ar.NewWriter(arfile)
err = arwriter.WriteGlobalHeader()
if err != nil {
return &os.PathError{"write ar header", archivePath, err}
}
// Open all object files and read the symbols for the symbol table.
@@ -32,55 +35,43 @@ func makeArchive(arfile *os.File, objs []string) error {
name string // symbol name
fileIndex int // index into objfiles
}{}
archiveOffsets := make([]int32, len(objs))
objfiles := make([]struct {
file *os.File
archiveOffset int32
}, len(objs))
for i, objpath := range objs {
objfile, err := os.Open(objpath)
if err != nil {
return err
}
objfiles[i].file = objfile
// Read the symbols and add them to the symbol table.
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)
dbg, err := elf.NewFile(objfile)
if err != nil {
return err
}
symbols, err := dbg.Symbols()
if err != nil {
return err
}
for _, symbol := range symbols {
bind := elf.ST_BIND(symbol.Info)
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
// Don't include local symbols (STB_LOCAL).
continue
}
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
// Not a function.
// TODO: perhaps globals variables should also be included?
continue
}
// Include in archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
// Close file, to avoid issues with too many open files (especially on
// MacOS X).
objfile.Close()
}
// Create the symbol table buffer.
@@ -134,14 +125,7 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// 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()
for i, objfile := range objfiles {
// Store the start index, for when we'll update the symbol table with
// the correct file start indices.
offset, err := arfile.Seek(0, os.SEEK_CUR)
@@ -149,17 +133,17 @@ func makeArchive(arfile *os.File, objs []string) error {
return err
}
if int64(int32(offset)) != offset {
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
return errors.New("large archives (4GB+) not supported: " + archivePath)
}
archiveOffsets[i] = int32(offset)
objfiles[i].archiveOffset = int32(offset)
// Write the file header.
st, err := objfile.Stat()
st, err := objfile.file.Stat()
if err != nil {
return err
}
err = arwriter.WriteHeader(&ar.Header{
Name: filepath.Base(objfile.Name()),
Name: filepath.Base(objfile.file.Name()),
ModTime: time.Unix(0, 0),
Uid: 0,
Gid: 0,
@@ -171,30 +155,22 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// 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(&copyBuf, objfile)
n, err := io.Copy(arwriter, objfile.file)
if err != nil {
return fmt.Errorf("could not copy object file into ar file: %w", err)
return 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)
return errors.New("file modified during ar creation: " + archivePath)
}
// File is not needed anymore.
objfile.Close()
objfile.file.Close()
}
// Create symbol indices.
indicesBuf := &bytes.Buffer{}
for _, sym := range symbolTable {
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
if err != nil {
return err
}
+147 -1371
View File
File diff suppressed because it is too large Load Diff
+118
View File
@@ -0,0 +1,118 @@
package builder
import (
"io"
"os"
"path/filepath"
"time"
"github.com/tinygo-org/tinygo/goenv"
)
// 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) {
cachepath := filepath.Join(goenv.Get("GOCACHE"), 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 := goenv.Get("GOCACHE")
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 = outf.Close()
if err != nil {
return err
}
return os.Rename(dst+".tmp", dst)
}
-167
View File
@@ -1,167 +0,0 @@
package builder
import (
"fmt"
"os"
"path/filepath"
"runtime"
"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",
"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"},
} {
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
if options.GOARCH == "arm" {
name += ",GOARM=" + options.GOARM
}
t.Run(name, 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 = os.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 hasBuiltinTools || runtime.GOOS != "linux" {
// Skip this step when using an external Clang invocation on Linux.
// The reason is that Debian has patched Clang in a way that
// modifies the LLVM features string, changing lots of FPU/float
// related flags. We want to test vanilla Clang, not Debian Clang.
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#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())
}
-103
View File
@@ -1,103 +0,0 @@
package builder
import (
"bytes"
"debug/elf"
"debug/macho"
"encoding/binary"
"fmt"
"io"
"os"
"runtime"
)
// ReadBuildID reads the build ID from the currently running executable.
func ReadBuildID() ([]byte, error) {
executable, err := os.Executable()
if err != nil {
return nil, err
}
f, err := os.Open(executable)
if err != nil {
return nil, err
}
defer f.Close()
switch runtime.GOOS {
case "linux", "freebsd", "android":
// Read the GNU build id section. (Not sure about FreeBSD though...)
file, err := elf.NewFile(f)
if err != nil {
return nil, err
}
var gnuID, goID []byte
for _, section := range file.Sections {
if section.Type != elf.SHT_NOTE ||
(section.Name != ".note.gnu.build-id" && section.Name != ".note.go.buildid") {
continue
}
buf := make([]byte, section.Size)
n, err := section.ReadAt(buf, 0)
if uint64(n) != section.Size || err != nil {
return nil, fmt.Errorf("could not read build id: %w", err)
}
if section.Name == ".note.gnu.build-id" {
gnuID = buf
} else {
goID = buf
}
}
if gnuID != nil {
return gnuID, nil
} else if goID != nil {
return goID, nil
}
case "darwin":
// Read the LC_UUID load command, which contains the equivalent of a
// build ID.
file, err := macho.NewFile(f)
if err != nil {
return nil, err
}
for _, load := range file.Loads {
// Unfortunately, the debug/macho package doesn't support the
// LC_UUID command directly. So we have to read it from
// macho.LoadBytes.
load, ok := load.(macho.LoadBytes)
if !ok {
continue
}
raw := load.Raw()
command := binary.LittleEndian.Uint32(raw)
if command != 0x1b {
// Looking for the LC_UUID load command.
// LC_UUID is defined here as 0x1b:
// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
continue
}
return raw[4:], nil
}
default:
// On other platforms (such as Windows) there isn't such a convenient
// build ID. Luckily, Go does have an equivalent of the build ID, which
// is stored as a special symbol named go.buildid. You can read it
// using `go tool buildid`, but the code below extracts it directly
// from the binary.
// Unfortunately, because of stripping with the -w flag, no symbol
// table might be available. Therefore, we have to scan the binary
// directly. Luckily the build ID is always at the start of the file.
// For details, see:
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
fileStart := make([]byte, 4096)
_, err := io.ReadFull(f, fileStart)
index := bytes.Index(fileStart, []byte("\xff Go build ID: \""))
if index < 0 || index > len(fileStart)-103 {
return nil, fmt.Errorf("could not find build id in %s", err)
}
buf := fileStart[index : index+103]
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
}
}
return nil, fmt.Errorf("could not find build ID in %s", executable)
}
+104 -35
View File
@@ -1,6 +1,7 @@
package builder
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
@@ -40,6 +41,7 @@ var genericBuiltins = []string{
"divdf3.c",
"divdi3.c",
"divmoddi4.c",
"divmodsi4.c",
"divsc3.c",
"divsf3.c",
"divsi3.c",
@@ -75,7 +77,6 @@ var genericBuiltins = []string{
"floatunsisf.c",
"floatuntidf.c",
"floatuntisf.c",
"fp_mode.c",
//"int_util.c",
"lshrdi3.c",
"lshrti3.c",
@@ -126,6 +127,7 @@ var genericBuiltins = []string{
"ucmpti2.c",
"udivdi3.c",
"udivmoddi4.c",
"udivmodsi4.c",
"udivmodti4.c",
"udivsi3.c",
"udivti3.c",
@@ -152,40 +154,107 @@ var aeabiBuiltins = []string{
"arm/aeabi_memset.S",
"arm/aeabi_uidivmod.S",
"arm/aeabi_uldivmod.S",
// These two are not technically EABI builtins but are used by them and only
// seem to be used on ARM. LLVM seems to use __divsi3 and __modsi3 on most
// other architectures.
// Most importantly, they have a different calling convention on AVR so
// should not be used on AVR.
"divmodsi4.c",
"udivmodsi4.c",
}
// 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: func() string {
llvmDir := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project/compiler-rt/lib/builtins")
if _, err := os.Stat(llvmDir); err == nil {
// Release build.
return llvmDir
}
// Development build.
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-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
},
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(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
}
// Get the builtins archive, possibly generating it as needed.
func loadBuiltins(target string) (path string, err error) {
// Try to load a precompiled compiler-rt library.
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
if _, err := os.Stat(precompiledPath); err == nil {
// Found a precompiled compiler-rt for this OS/architecture. Return the
// path directly.
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.
args := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
}
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
if err != nil {
return &commandError{"failed to build", srcpath, err}
}
}
// Put all the object files in a single archive. This archive file will be
// used to statically link compiler-rt.
arpath := filepath.Join(dir, "librt.a")
err = makeArchive(arpath, objs)
if err != nil {
return err
}
// Give the caller the resulting file. The callback must copy the file,
// because after it returns the temporary directory will be removed.
return callback(arpath)
}
-326
View File
@@ -1,326 +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/fs"
"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, thinlto bool, printCommands func(string, ...string)) (string, error) {
// Hash input file.
fileHash, err := hashFile(abspath)
if err != nil {
return "", err
}
// Acquire a lock (if supported).
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
defer unlock()
ext := ".o"
if thinlto {
ext = ".bc"
}
// 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 := os.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, ext)
if err == nil {
if _, err := os.Stat(outpath); err == nil {
return outpath, nil
} else if !errors.Is(err, fs.ErrNotExist) {
return "", err
}
}
} else if !errors.Is(err, fs.ErrNotExist) {
// expected either nil or IsNotExist
return "", err
}
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*"+ext)
if err != nil {
return "", err
}
objTmpFile.Close()
depTmpFile, err := os.CreateTemp(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
if thinlto {
flags = append(flags, "-flto=thin")
}
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 := os.CreateTemp(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, ext)
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, ext 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+ext)
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 := os.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
}
+70 -83
View File
@@ -38,7 +38,6 @@
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCTargetOptions.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
@@ -49,9 +48,9 @@
#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"
@@ -71,12 +70,12 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
bool Success = true;
// Parse the arguments.
const OptTable &OptTbl = getDriverOptTable();
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
@@ -89,7 +88,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args);
std::string Nearest;
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
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)
@@ -101,7 +100,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
@@ -115,32 +114,30 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Any DebugInfoKind implies GenDwarfForAssembly.
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections_EQ)) {
Opts.CompressDebugSections =
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
.Case("none", llvm::DebugCompressionType::None)
.Case("zlib", llvm::DebugCompressionType::Z)
.Default(llvm::DebugCompressionType::None);
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);
if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32))
Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64);
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));
if (const Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ,
options::OPT_fdebug_compilation_dir_EQ))
Opts.DebugCompilationDir = A->getValue();
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
auto Split = StringRef(Arg).split('=');
Opts.DebugPrefixMap.insert(
{std::string(Split.first), std::string(Split.second)});
}
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
// Frontend Options
if (Args.hasArg(OPT_INPUT)) {
@@ -156,9 +153,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
}
}
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
Opts.SplitDwarfOutput =
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
Opts.OutputPath = Args.getLastArgValue(OPT_o);
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
if (Arg *A = Args.getLastArg(OPT_filetype)) {
StringRef Name = A->getValue();
unsigned OutputType = StringSwitch<unsigned>(Name)
@@ -185,10 +181,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
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.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
Opts.IncrementalLinkerCompatible =
Args.hasArg(OPT_mincremental_linker_compatible);
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
@@ -214,8 +208,8 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
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_TextWithCRLF));
auto Out = llvm::make_unique<raw_fd_ostream>(
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
if (EC) {
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
return nullptr;
@@ -224,8 +218,7 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
return Out;
}
static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
DiagnosticsEngine &Diags) {
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
// Get the target specific parser.
std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
@@ -233,7 +226,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
if (std::error_code EC = Buffer.getError()) {
Error = EC.message();
@@ -252,9 +245,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
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));
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
assert(MAI && "Unable to create target asm info!");
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
@@ -274,15 +265,11 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
if (!Opts.SplitDwarfOutput.empty())
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
// Build up the feature string from the target feature list.
std::string FS = llvm::join(Opts.Features, ",");
// 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());
std::unique_ptr<MCSubtargetInfo> STI(
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
assert(STI && "Unable to create subtarget info!");
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
&MCOptions);
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
bool PIC = false;
if (Opts.RelocationModel == "static") {
@@ -295,12 +282,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
PIC = false;
}
// 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(
TheTarget->createMCObjectFileInfo(Ctx, PIC));
Ctx.setObjectFileInfo(MOFI.get());
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly)
@@ -322,22 +304,29 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
if (!Opts.MainFileName.empty())
Ctx.setMainFileName(StringRef(Opts.MainFileName));
Ctx.setDwarfFormat(Opts.Dwarf64 ? dwarf::DWARF64 : dwarf::DWARF32);
Ctx.setDwarfVersion(Opts.DwarfVersion);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfRootFile(Opts.InputFile,
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
// Build up the feature string from the target feature list.
std::string FS;
if (!Opts.Features.empty()) {
FS = Opts.Features[0];
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
FS += "," + Opts.Features[i];
}
std::unique_ptr<MCStreamer> Str;
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
assert(MCII && "Unable to create instruction info!");
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;
MCTargetOptions MCOptions;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
@@ -351,7 +340,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
@@ -362,7 +351,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
"Invalid file type!");
if (!FDOS->supportsSeeking()) {
BOS = std::make_unique<buffer_ostream>(*FDOS);
BOS = make_unique<buffer_ostream>(*FDOS);
Out = BOS.get();
}
@@ -370,8 +359,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
assert(MAB && "Unable to create asm backend!");
std::unique_ptr<MCObjectWriter> OW =
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out);
@@ -381,16 +368,17 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
Str.get()->initSections(Opts.NoExecStack, *STI);
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.getObjectFileType() == MCContext::IsMachO) {
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);
Str.get()->EmitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
@@ -423,12 +411,12 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
Failed = Parser->Run(Opts.NoInitialTextSection);
}
return Failed;
}
bool ExecuteAssembler(AssemblerInvocation &Opts,
DiagnosticsEngine &Diags) {
bool Failed = ExecuteAssemblerImpl(Opts, Diags);
// 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) {
@@ -441,14 +429,14 @@ bool ExecuteAssembler(AssemblerInvocation &Opts,
return Failed;
}
static void LLVMErrorHandler(void *UserData, const char *Message,
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);
exit(1);
}
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
@@ -476,11 +464,11 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
return 1;
if (Asm.ShowHelp) {
getDriverOptTable().printHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
@@ -497,7 +485,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// FIXME: Remove this, one day.
if (!Asm.LLVMArgs.empty()) {
unsigned NumArgs = Asm.LLVMArgs.size();
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
Args[0] = "clang (LLVM option parsing)";
for (unsigned i = 0; i != NumArgs; ++i)
Args[i + 1] = Asm.LLVMArgs[i].c_str();
@@ -511,7 +499,6 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
// If any timers were active but haven't been destroyed yet, print their
// results now.
TimerGroup::printAll(errs());
TimerGroup::clearAll();
return !!Failed;
}
+2 -4
View File
@@ -11,6 +11,8 @@
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/ArrayRef.h"
/// Helper class for representing a single invocation of the assembler.
struct AssemblerInvocation {
/// @name Target Options
@@ -39,7 +41,6 @@ struct AssemblerInvocation {
unsigned SaveTemporaryLabels : 1;
unsigned GenDwarfForAssembly : 1;
unsigned RelaxELFRelocations : 1;
unsigned Dwarf64 : 1;
unsigned DwarfVersion;
std::string DwarfDebugFlags;
std::string DwarfDebugProducer;
@@ -81,7 +82,6 @@ struct AssemblerInvocation {
unsigned RelaxAll : 1;
unsigned NoExecStack : 1;
unsigned FatalWarnings : 1;
unsigned NoWarn : 1;
unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1;
@@ -107,9 +107,7 @@ public:
RelaxAll = 0;
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
IncrementalLinkerCompatible = 0;
Dwarf64 = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
}
-33
View File
@@ -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
}
}
}
+3 -4
View File
@@ -11,7 +11,6 @@
#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;
@@ -53,7 +52,8 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
bool success = clang::CompilerInvocation::CreateFromArgs(
Clang->getInvocation(),
CCArgs,
const_cast<const char **>(CCArgs.data()),
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
Diags);
if (!success) {
return false;
@@ -74,8 +74,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
} 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))
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
return false;
// Execute the invocation, unless there were parsing errors.
+24 -52
View File
@@ -2,44 +2,29 @@ package builder
import (
"errors"
"fmt"
"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{}
var commands = map[string][]string{
"clang": {"clang-9"},
"ld.lld": {"ld.lld-9", "ld.lld"},
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
}
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
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
// manually set $PATH).
if runtime.GOOS == "darwin" {
var prefix string
switch runtime.GOARCH {
case "amd64":
prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
case "arm64":
prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/"
default:
// unknown GOARCH
panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH))
}
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")
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
}
// Add the path for when LLVM was installed with the installer from
// llvm.org, which by default doesn't add LLVM to the $PATH environment
@@ -48,42 +33,29 @@ func init() {
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.
// Add the path to the llvm90 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")
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
}
}
// 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)
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 errors.Unwrap(err) == exec.ErrNotFound {
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
// this command was not found, try the next
continue
}
return cmdName, err
return err
}
return cmdName, nil
return nil
}
return "", errors.New("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()
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
}
+60
View File
@@ -0,0 +1,60 @@
// +build byollvm
package builder
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#cgo CXXFLAGS: -fno-rtti
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
*/
import "C"
// runCCompiler invokes a C compiler with the given arguments.
//
// This version invokes the built-in Clang when trying to run the Clang compiler.
func runCCompiler(command string, flags ...string) error {
switch command {
case "clang":
// Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
flags = append([]string{"tinygo:" + command}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to compile using built-in clang")
}
return nil
default:
// Running some other compiler. Maybe it has been defined in the
// commands map (unlikely).
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
// Alternatively, run the compiler directly.
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
}
+24
View File
@@ -0,0 +1,24 @@
// +build !byollvm
package builder
// This file provides a way to a C compiler as an external command. See also:
// clang-external.go
import (
"os"
"os/exec"
)
// runCCompiler invokes a C compiler with the given arguments.
//
// This version always runs the compiler as an external command.
func runCCompiler(command string, flags ...string) error {
if cmdNames, ok := commands[command]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(command, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+4 -14
View File
@@ -13,32 +13,22 @@ import (
// 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)
spec, err := compileopts.LoadTarget(options.Target)
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)
major, minor, err := getGorootVersion(goroot)
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 18 || minor > 19 {
return nil, fmt.Errorf("requires go version 1.18 through 1.19, got go%d.%d", major, minor)
if major != 1 || (minor != 11 && minor != 12 && minor != 13) {
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
-58
View File
@@ -1,58 +0,0 @@
package builder
import (
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// Create a job that builds a Darwin libSystem.dylib stub library. This library
// contains all the symbols needed so that we can link against it, but it
// doesn't contain any real symbol implementations.
func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJob {
return &compileJob{
description: "compile Darwin libSystem.dylib",
run: func(job *compileJob) (err error) {
arch := strings.Split(config.Triple(), "-")[0]
job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
// Compile assembly file to object file.
flags := []string{
"-nostdlib",
"--target=" + config.Triple(),
"-c",
"-o", objpath,
inpath,
}
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", flags...)
}
err = runCCompiler(flags...)
if err != nil {
return err
}
// Link object file to dynamic library.
platformVersion := strings.TrimPrefix(strings.Split(config.Triple(), "-")[2], "macosx")
flags = []string{
"-flavor", "darwin",
"-demangle",
"-dynamic",
"-dylib",
"-arch", arch,
"-platform_version", "macos", platformVersion, platformVersion,
"-install_name", "/usr/lib/libSystem.B.dylib",
"-o", job.result,
objpath,
}
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("ld.lld", flags...)
}
return link("ld.lld", flags...)
},
}
}
-57
View File
@@ -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
}
+67 -45
View File
@@ -2,36 +2,88 @@ package builder
import (
"errors"
"io/fs"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"tinygo.org/x/go-llvm"
)
// getGorootVersion returns the major and minor version for a given GOROOT path.
// If the goroot cannot be determined, (0, 0) is returned.
func getGorootVersion(goroot string) (major, minor int, err error) {
s, err := GorootVersionString(goroot)
if err != nil {
return 0, 0, err
}
if s == "" || s[:2] != "go" {
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
}
parts := strings.Split(s[2:], ".")
if len(parts) < 2 {
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
}
// Ignore the errors, we don't really handle errors here anyway.
var trailing string
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
if n == 2 && err == io.EOF {
// Means there were no trailing characters (i.e., not an alpha/beta)
err = nil
}
if err != nil {
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
}
return
}
// GorootVersionString returns the version string as reported by the Go
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
// can have some variations (for beta releases, for example).
func GorootVersionString(goroot string) (string, error) {
if data, err := ioutil.ReadFile(filepath.Join(
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
r := regexp.MustCompile("const TheVersion = `(.*)`")
matches := r.FindSubmatch(data)
if len(matches) != 2 {
return "", errors.New("Invalid go version output:\n" + string(data))
}
return string(matches[1]), nil
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
return string(data), nil
} else {
return "", err
}
}
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) {
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); !errors.Is(err, fs.ErrNotExist) {
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 {
@@ -46,38 +98,22 @@ func getClangHeaderPath(TINYGOROOT string) string {
// 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 {
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
dirs, err := ioutil.ReadDir(clangVersionRoot)
if err != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(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++
}
dirnames := make([]string, len(dirs))
for i, d := range dirs {
dirnames[i] = d.Name()
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := dirCount - 1; i >= 0; i-- {
path := filepath.Join(dirnames[i], "include")
for i := len(dirnames) - 1; i >= 0; i-- {
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
@@ -86,20 +122,6 @@ func getClangHeaderPath(TINYGOROOT string) string {
}
}
// On Arch Linux, the clang executable is stored in /usr/bin rather than being symlinked from there.
// Search directly in /usr/lib for clang.
if matches, err := filepath.Glob("/usr/lib/clang/" + llvmMajor + ".*.*"); err == nil {
// Check for the highest version first.
sort.Strings(matches)
for i := len(matches) - 1; i >= 0; i-- {
path := filepath.Join(matches[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
// Could not find it.
return ""
}
-193
View File
@@ -1,193 +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"
"os"
"sort"
"strings"
)
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{}
// Separate esp32 and esp32-img. The -img suffix indicates we should make an
// image, not just a binary to be flashed at 0x1000 for example.
chip := format
makeImage := false
if strings.HasSuffix(format, "-img") {
makeImage = true
chip = format[:len(format)-len("-img")]
}
if makeImage {
// The bootloader starts at 0x1000, or 4096.
// TinyGo doesn't use a separate bootloader and runs the entire
// application in the bootloader location.
outf.Write(make([]byte, 4096))
}
// 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,
}[chip]
// Image header.
switch chip {
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 chip != "esp8266" {
// SHA256 hash (to protect against image corruption, not for security).
hash := sha256.Sum256(outf.Bytes())
outf.Write(hash[:])
}
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
// image to be a certain size.
if makeImage {
// Use a default image size of 4MB.
grow := 4096*1024 - outf.Len()
if grow > 0 {
outf.Write(make([]byte, grow))
}
}
// Write the image to the output file.
return os.WriteFile(outfile, outf.Bytes(), 0666)
}
-229
View File
@@ -1,229 +0,0 @@
package builder
// This file implements a job runner for the compiler, which runs jobs in
// parallel while taking care of dependencies.
import (
"container/heap"
"errors"
"fmt"
"runtime"
"sort"
"strings"
"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)
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,
}
}
// 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, sema chan struct{}) error {
if sema == nil {
// Have a default, if the semaphore isn't set. This is useful for
// tests.
sema = make(chan struct{}, runtime.NumCPU())
}
if cap(sema) == 0 {
return errors.New("cannot 0 jobs at a time")
}
// 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)
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
dependents := make(map[*compileJob][]*compileJob, len(jobs))
jidx := make(map[*compileJob]int)
var ready intHeap
for i, job := range jobs {
jidx[job] = i
if len(job.dependencies) == 0 {
// This job is ready to run.
ready.Push(i)
continue
}
// Construct a map for dependencies which the job is currently waiting on.
waitDeps := make(map[*compileJob]struct{})
waiting[job] = waitDeps
// Add the job to the dependents list of each dependency.
for _, dep := range job.dependencies {
dependents[dep] = append(dependents[dep], job)
waitDeps[dep] = struct{}{}
}
}
// Create a channel to accept notifications of completion.
doneChan := make(chan *compileJob)
// 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 len(ready.IntSlice) > 0 || numRunningJobs != 0 {
var completed *compileJob
if len(ready.IntSlice) > 0 {
select {
case sema <- struct{}{}:
// Start a job.
job := jobs[heap.Pop(&ready).(int)]
if jobRunnerDebug {
fmt.Println("## start: ", job.description)
}
go runJob(job, doneChan)
numRunningJobs++
continue
case completed = <-doneChan:
// A job completed.
}
} else {
// Wait for a job to complete.
completed = <-doneChan
}
numRunningJobs--
<-sema
if jobRunnerDebug {
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
}
if completed.err != nil {
// Wait for any current jobs to finish.
for numRunningJobs != 0 {
<-doneChan
numRunningJobs--
}
// The build failed.
return completed.err
}
// Update total run time.
totalTime += completed.duration
// Update dependent jobs.
for _, j := range dependents[completed] {
wait := waiting[j]
delete(wait, completed)
if len(wait) == 0 {
// This job is now ready to run.
ready.Push(jidx[j])
delete(waiting, j)
}
}
}
if len(waiting) != 0 {
// There is a dependency cycle preventing some jobs from running.
return errDependencyCycle{waiting}
}
// 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
}
type errDependencyCycle struct {
waiting map[*compileJob]map[*compileJob]struct{}
}
func (err errDependencyCycle) Error() string {
waits := make([]string, 0, len(err.waiting))
for j, wait := range err.waiting {
deps := make([]string, 0, len(wait))
for dep := range wait {
deps = append(deps, dep.description)
}
sort.Strings(deps)
waits = append(waits, fmt.Sprintf("\t%s is waiting for [%s]",
j.description, strings.Join(deps, ", "),
))
}
sort.Strings(waits)
return "deadlock:\n" + strings.Join(waits, "\n")
}
type intHeap struct {
sort.IntSlice
}
func (h *intHeap) Push(x interface{}) {
h.IntSlice = append(h.IntSlice, x.(int))
}
func (h *intHeap) Pop() interface{} {
x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x
}
// runJob runs a compile job and notifies doneChan of completion.
func runJob(job *compileJob, doneChan chan *compileJob) {
start := time.Now()
if job.run != nil {
err := job.run(job)
if err != nil {
job.err = err
}
}
job.duration = time.Since(start)
doneChan <- job
}
-277
View File
@@ -1,277 +0,0 @@
package builder
import (
"errors"
"io/fs"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
"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.
sourceDir func() 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, unlock, err := l.load(config, tmpdir)
if err != nil {
return "", err
}
defer unlock()
err = runJobs(job, config.Options.Semaphore)
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, abortLock func(), 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), func() {}, nil
}
// Create a lock on the output (if supported).
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
outname := filepath.Base(outdir)
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), outname+".lock"))
var ok bool
defer func() {
if !ok {
unlock()
}
}()
// Try to fetch this library from the cache.
if _, err := os.Stat(archiveFilePath); err == nil {
return dummyCompileJob(archiveFilePath), func() {}, nil
}
// Cache miss, build it now.
// Create the destination directory where the components of this library
// (lib.a file, include directory) are placed.
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, 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 := os.MkdirTemp(outdir, "include.tmp*")
if err != nil {
return nil, nil, err
}
defer os.RemoveAll(temporaryHeaderPath)
err = l.makeHeaders(target, temporaryHeaderPath)
if err != nil {
return nil, nil, err
}
err = os.Chmod(temporaryHeaderPath, 0o755) // TempDir uses 0o700 by default
if err != nil {
return nil, nil, err
}
err = os.Rename(temporaryHeaderPath, headerPath)
if err != nil {
switch {
case errors.Is(err, fs.ErrExist):
// Another invocation of TinyGo also seems to have already created the headers.
case runtime.GOOS == "windows" && errors.Is(err, fs.ErrPermission):
// On Windows, a rename with a destination directory that already
// exists does not result in an IsExist error, but rather in an
// access denied error. To be sure, check for this case by checking
// whether the target directory exists.
if _, err := os.Stat(headerPath); err == nil {
break
}
fallthrough
default:
return nil, 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, 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 if strings.HasPrefix(target, "avr") {
args = append(args, "-mmcu="+cpu)
} else {
args = append(args, "-mcpu="+cpu)
}
}
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
if strings.Split(target, "-")[2] == "linux" {
args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
} else {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
}
}
if strings.HasPrefix(target, "avr") {
// AVR defaults to C float and double both being 32-bit. This deviates
// from what most code (and certainly compiler-rt) expects. So we need
// to force the compiler to use 64-bit floating point numbers for
// double.
args = append(args, "-mdouble=64")
}
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")
}
if strings.HasPrefix(target, "xtensa") {
// Hack to work around an issue in the Xtensa port:
// https://github.com/espressif/llvm-project/issues/52
// Hopefully this will be fixed soon (LLVM 14).
args = append(args, "-D__ELF__")
}
var once sync.Once
// 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 {
defer once.Do(unlock)
// Create an archive of all object files.
f, err := os.CreateTemp(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
}
err = os.Chmod(f.Name(), 0o644) // TempFile uses 0o600 by default
if err != nil {
return err
}
// Store this archive in the cache.
return os.Rename(f.Name(), archiveFilePath)
},
}
sourceDir := l.sourceDir()
// 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(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(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 := os.CreateTemp(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"))
},
})
}
ok = true
return job, func() {
once.Do(unlock)
}, nil
}
+71
View File
@@ -0,0 +1,71 @@
// +build byollvm
package builder
// This file provides a Link() function that uses the bundled lld if possible.
import (
"errors"
"os"
"os/exec"
"unsafe"
"github.com/tinygo-org/tinygo/goenv"
)
/*
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_link_elf(int argc, char **argv);
bool tinygo_link_wasm(int argc, char **argv);
*/
import "C"
// link invokes a linker with the given name and flags.
//
// This version uses the built-in linker when trying to use lld.
func link(linker string, flags ...string) error {
switch linker {
case "ld.lld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in ld.lld")
}
return nil
case "wasm-ld":
flags = append([]string{"tinygo:" + linker}, flags...)
var cflag *C.char
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
defer C.free(buf)
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
for i, flag := range flags {
cflag := C.CString(flag)
cflags[i] = cflag
defer C.free(unsafe.Pointer(cflag))
}
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
if !ok {
return errors.New("failed to link using built-in wasm-ld")
}
return nil
default:
// Fall back to external command.
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
}
+27
View File
@@ -0,0 +1,27 @@
// +build !byollvm
package builder
// This file provides a Link() function that always runs an external command. It
// is provided for when tinygo is built without linking to liblld.
import (
"os"
"os/exec"
"github.com/tinygo-org/tinygo/goenv"
)
// link invokes a linker with the given name and arguments.
//
// This version always runs the linker as an external command.
func link(linker string, flags ...string) error {
if cmdNames, ok := commands[linker]; ok {
return execCommand(cmdNames, flags...)
}
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
+2 -12
View File
@@ -8,22 +8,12 @@ extern "C" {
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::elf::link(args, llvm::outs(), llvm::errs(), false, false);
}
bool tinygo_link_macho(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::macho::link(args, llvm::outs(), llvm::errs(), false, false);
}
bool tinygo_link_mingw(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::mingw::link(args, llvm::outs(), llvm::errs(), false, false);
return lld::elf::link(args, false);
}
bool tinygo_link_wasm(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
return lld::wasm::link(args, llvm::outs(), llvm::errs(), false, false);
return lld::wasm::link(args, false);
}
} // external "C"
-92
View File
@@ -1,92 +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
},
sourceDir: func() string { return "" }, // unused
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", "-DDATA", "-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
}
-165
View File
@@ -1,165 +0,0 @@
package builder
import (
"bytes"
"fmt"
"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 := os.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 := os.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",
"-Wno-ignored-pragmas",
"-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",
"-fno-stack-protector",
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "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",
"legacy/*.c",
"malloc/*.c",
"mman/*.c",
"math/*.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
}
-27
View File
@@ -1,27 +0,0 @@
package builder
import (
"fmt"
"io"
"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 = io.Discard
err := cmd.Run()
if err != nil {
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
return nil
}
+19 -29
View File
@@ -2,17 +2,14 @@ package builder
import (
"debug/elf"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"github.com/marcinbor85/gohex"
)
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
const maxPadBytes = 4095
// objcopyError is an error returned by functions that act like objcopy.
type objcopyError struct {
Op string
@@ -61,7 +58,7 @@ func extractROM(path string) (uint64, []byte, error) {
progs := make(progSlice, 0, 2)
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
continue
}
progs = append(progs, prog)
@@ -73,21 +70,10 @@ func extractROM(path string) (uint64, []byte, error) {
var rom []byte
for _, prog := range progs {
romEnd := progs[0].Paddr + uint64(len(rom))
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
// Sometimes, the linker seems to insert a bit of padding between
// segments. Simply zero-fill these parts.
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
}
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
if diff > maxPadBytes {
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
// Pad the difference
rom = append(rom, make([]byte, diff)...)
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
}
data, err := io.ReadAll(prog.Open())
data, err := ioutil.ReadAll(prog.Open())
if err != nil {
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
}
@@ -107,7 +93,7 @@ func extractROM(path string) (uint64, []byte, error) {
// objcopy converts an ELF file to a different (simpler) output file format:
// .bin or .hex. It extracts only the .text section.
func objcopy(infile, outfile, binaryFormat string) error {
func objcopy(infile, outfile string) error {
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
@@ -121,20 +107,24 @@ func objcopy(infile, outfile, binaryFormat string) error {
}
// Write to the file, in the correct format.
switch binaryFormat {
case "hex":
// Intel hex file, includes the firmware start address.
switch filepath.Ext(outfile) {
case ".gba":
// The address is not stored in a .gba file.
_, err := f.Write(data)
return err
case ".bin":
// The address is not stored in a .bin file (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
case ".hex":
mem := gohex.NewMemory()
err := mem.AddBinary(uint32(addr), data)
if err != nil {
return objcopyError{"failed to create .hex file", err}
}
return mem.DumpIntelHex(f, 16)
case "bin":
// The start address is not stored in raw firmware files (therefore you
// should use .hex files in most cases).
_, err := f.Write(data)
return err
mem.DumpIntelHex(f, 16) // TODO: handle error
return nil
default:
panic("unreachable")
}
-421
View File
@@ -1,421 +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 {
newlibDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-D_COMPILING_NEWLIB",
"-DHAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0",
"-nostdlibinc",
"-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio",
"-I" + newlibDir + "/libm/common",
"-I" + headerPath,
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
librarySources: func(target string) []string {
return picolibcSources
},
}
var picolibcSources = []string{
"../../picolibc-stdio.c",
"libc/tinystdio/asprintf.c",
"libc/tinystdio/atod_engine.c",
"libc/tinystdio/atod_ryu.c",
"libc/tinystdio/atof_engine.c",
"libc/tinystdio/atof_ryu.c",
//"libc/tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
"libc/tinystdio/clearerr.c",
"libc/tinystdio/compare_exchange.c",
"libc/tinystdio/dtoa_data.c",
"libc/tinystdio/dtoa_engine.c",
"libc/tinystdio/dtoa_ryu.c",
"libc/tinystdio/ecvtbuf.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvt_data.c",
"libc/tinystdio/ecvtfbuf.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/ecvtf_data.c",
"libc/tinystdio/exchange.c",
//"libc/tinystdio/fclose.c", // posix-io
"libc/tinystdio/fcvtbuf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvtfbuf.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fdevopen.c",
//"libc/tinystdio/fdopen.c", // posix-io
"libc/tinystdio/feof.c",
"libc/tinystdio/ferror.c",
"libc/tinystdio/fflush.c",
"libc/tinystdio/fgetc.c",
"libc/tinystdio/fgets.c",
"libc/tinystdio/fileno.c",
"libc/tinystdio/filestrget.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/filestrput.c",
//"libc/tinystdio/fopen.c", // posix-io
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftoa_data.c",
"libc/tinystdio/ftoa_engine.c",
"libc/tinystdio/ftoa_ryu.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/gcvtbuf.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtfbuf.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/perror.c",
//"libc/tinystdio/posixiob.c", // posix-io
//"libc/tinystdio/posixio.c", // posix-io
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/ryu_divpow2.c",
"libc/tinystdio/ryu_log10.c",
"libc/tinystdio/ryu_log2pow5.c",
"libc/tinystdio/ryu_pow5bits.c",
"libc/tinystdio/ryu_table.c",
"libc/tinystdio/ryu_umul128.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setvbuf.c",
//"libc/tinystdio/sflags.c", // posix-io
"libc/tinystdio/snprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/strtof.c",
//"libc/tinystdio/strtold.c", // have_long_double and not long_double_equals_double
//"libc/tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libc/tinystdio/vsscanf.c",
"libc/string/bcmp.c",
"libc/string/bcopy.c",
"libc/string/bzero.c",
"libc/string/explicit_bzero.c",
"libc/string/ffsl.c",
"libc/string/ffsll.c",
"libc/string/fls.c",
"libc/string/flsl.c",
"libc/string/flsll.c",
"libc/string/gnu_basename.c",
"libc/string/index.c",
"libc/string/memccpy.c",
"libc/string/memchr.c",
"libc/string/memcmp.c",
"libc/string/memcpy.c",
"libc/string/memmem.c",
"libc/string/memmove.c",
"libc/string/mempcpy.c",
"libc/string/memrchr.c",
"libc/string/memset.c",
"libc/string/rawmemchr.c",
"libc/string/rindex.c",
"libc/string/stpcpy.c",
"libc/string/stpncpy.c",
"libc/string/strcasecmp.c",
"libc/string/strcasecmp_l.c",
"libc/string/strcasestr.c",
"libc/string/strcat.c",
"libc/string/strchr.c",
"libc/string/strchrnul.c",
"libc/string/strcmp.c",
"libc/string/strcoll.c",
"libc/string/strcoll_l.c",
"libc/string/strcpy.c",
"libc/string/strcspn.c",
"libc/string/strdup.c",
"libc/string/strerror.c",
"libc/string/strerror_r.c",
"libc/string/strlcat.c",
"libc/string/strlcpy.c",
"libc/string/strlen.c",
"libc/string/strlwr.c",
"libc/string/strncasecmp.c",
"libc/string/strncasecmp_l.c",
"libc/string/strncat.c",
"libc/string/strncmp.c",
"libc/string/strncpy.c",
"libc/string/strndup.c",
"libc/string/strnlen.c",
"libc/string/strnstr.c",
"libc/string/strpbrk.c",
"libc/string/strrchr.c",
"libc/string/strsep.c",
"libc/string/strsignal.c",
"libc/string/strspn.c",
"libc/string/strstr.c",
"libc/string/strtok.c",
"libc/string/strtok_r.c",
"libc/string/strupr.c",
"libc/string/strverscmp.c",
"libc/string/strxfrm.c",
"libc/string/strxfrm_l.c",
"libc/string/swab.c",
"libc/string/timingsafe_bcmp.c",
"libc/string/timingsafe_memcmp.c",
"libc/string/u_strerr.c",
"libc/string/wcpcpy.c",
"libc/string/wcpncpy.c",
"libc/string/wcscasecmp.c",
"libc/string/wcscasecmp_l.c",
"libc/string/wcscat.c",
"libc/string/wcschr.c",
"libc/string/wcscmp.c",
"libc/string/wcscoll.c",
"libc/string/wcscoll_l.c",
"libc/string/wcscpy.c",
"libc/string/wcscspn.c",
"libc/string/wcsdup.c",
"libc/string/wcslcat.c",
"libc/string/wcslcpy.c",
"libc/string/wcslen.c",
"libc/string/wcsncasecmp.c",
"libc/string/wcsncasecmp_l.c",
"libc/string/wcsncat.c",
"libc/string/wcsncmp.c",
"libc/string/wcsncpy.c",
"libc/string/wcsnlen.c",
"libc/string/wcspbrk.c",
"libc/string/wcsrchr.c",
"libc/string/wcsspn.c",
"libc/string/wcsstr.c",
"libc/string/wcstok.c",
"libc/string/wcswidth.c",
"libc/string/wcsxfrm.c",
"libc/string/wcsxfrm_l.c",
"libc/string/wcwidth.c",
"libc/string/wmemchr.c",
"libc/string/wmemcmp.c",
"libc/string/wmemcpy.c",
"libc/string/wmemmove.c",
"libc/string/wmempcpy.c",
"libc/string/wmemset.c",
"libc/string/xpg_strerror_r.c",
"libm/common/sf_finite.c",
"libm/common/sf_copysign.c",
"libm/common/sf_modf.c",
"libm/common/sf_scalbn.c",
"libm/common/sf_cbrt.c",
"libm/common/sf_exp10.c",
"libm/common/sf_expm1.c",
"libm/common/sf_ilogb.c",
"libm/common/sf_infinity.c",
"libm/common/sf_isinf.c",
"libm/common/sf_isinff.c",
"libm/common/sf_isnan.c",
"libm/common/sf_isnanf.c",
"libm/common/sf_issignaling.c",
"libm/common/sf_log1p.c",
"libm/common/sf_nan.c",
"libm/common/sf_nextafter.c",
"libm/common/sf_pow10.c",
"libm/common/sf_rint.c",
"libm/common/sf_logb.c",
"libm/common/sf_fdim.c",
"libm/common/sf_fma.c",
"libm/common/sf_fmax.c",
"libm/common/sf_fmin.c",
"libm/common/sf_fpclassify.c",
"libm/common/sf_lrint.c",
"libm/common/sf_llrint.c",
"libm/common/sf_lround.c",
"libm/common/sf_llround.c",
"libm/common/sf_nearbyint.c",
"libm/common/sf_remquo.c",
"libm/common/sf_round.c",
"libm/common/sf_scalbln.c",
"libm/common/sf_trunc.c",
"libm/common/sf_exp.c",
"libm/common/sf_exp2.c",
"libm/common/sf_exp2_data.c",
"libm/common/sf_log.c",
"libm/common/sf_log_data.c",
"libm/common/sf_log2.c",
"libm/common/sf_log2_data.c",
"libm/common/sf_pow_log2_data.c",
"libm/common/sf_pow.c",
"libm/common/s_finite.c",
"libm/common/s_copysign.c",
"libm/common/s_modf.c",
"libm/common/s_scalbn.c",
"libm/common/s_cbrt.c",
"libm/common/s_exp10.c",
"libm/common/s_expm1.c",
"libm/common/s_ilogb.c",
"libm/common/s_infinity.c",
"libm/common/s_isinf.c",
"libm/common/s_isinfd.c",
"libm/common/s_isnan.c",
"libm/common/s_isnand.c",
"libm/common/s_issignaling.c",
"libm/common/s_log1p.c",
"libm/common/s_nan.c",
"libm/common/s_nextafter.c",
"libm/common/s_pow10.c",
"libm/common/s_rint.c",
"libm/common/s_logb.c",
"libm/common/s_log2.c",
"libm/common/s_fdim.c",
"libm/common/s_fma.c",
"libm/common/s_fmax.c",
"libm/common/s_fmin.c",
"libm/common/s_fpclassify.c",
"libm/common/s_lrint.c",
"libm/common/s_llrint.c",
"libm/common/s_lround.c",
"libm/common/s_llround.c",
"libm/common/s_nearbyint.c",
"libm/common/s_remquo.c",
"libm/common/s_round.c",
"libm/common/s_scalbln.c",
"libm/common/s_signbit.c",
"libm/common/s_trunc.c",
"libm/common/exp.c",
"libm/common/exp2.c",
"libm/common/exp_data.c",
"libm/common/math_err_with_errno.c",
"libm/common/math_err_xflow.c",
"libm/common/math_err_uflow.c",
"libm/common/math_err_oflow.c",
"libm/common/math_err_divzero.c",
"libm/common/math_err_invalid.c",
"libm/common/math_err_may_uflow.c",
"libm/common/math_err_check_uflow.c",
"libm/common/math_err_check_oflow.c",
"libm/common/log.c",
"libm/common/log_data.c",
"libm/common/log2.c",
"libm/common/log2_data.c",
"libm/common/pow.c",
"libm/common/pow_log_data.c",
"libm/math/e_acos.c",
"libm/math/e_acosh.c",
"libm/math/e_asin.c",
"libm/math/e_atan2.c",
"libm/math/e_atanh.c",
"libm/math/e_cosh.c",
"libm/math/e_exp.c",
"libm/math/ef_acos.c",
"libm/math/ef_acosh.c",
"libm/math/ef_asin.c",
"libm/math/ef_atan2.c",
"libm/math/ef_atanh.c",
"libm/math/ef_cosh.c",
"libm/math/ef_exp.c",
"libm/math/ef_fmod.c",
"libm/math/ef_hypot.c",
"libm/math/ef_j0.c",
"libm/math/ef_j1.c",
"libm/math/ef_jn.c",
"libm/math/ef_lgamma.c",
"libm/math/ef_log10.c",
"libm/math/ef_log.c",
"libm/math/e_fmod.c",
"libm/math/ef_pow.c",
"libm/math/ef_remainder.c",
"libm/math/ef_rem_pio2.c",
"libm/math/ef_scalb.c",
"libm/math/ef_sinh.c",
"libm/math/ef_sqrt.c",
"libm/math/ef_tgamma.c",
"libm/math/e_hypot.c",
"libm/math/e_j0.c",
"libm/math/e_j1.c",
"libm/math/e_jn.c",
"libm/math/e_lgamma.c",
"libm/math/e_log10.c",
"libm/math/e_log.c",
"libm/math/e_pow.c",
"libm/math/e_remainder.c",
"libm/math/e_rem_pio2.c",
"libm/math/erf_lgamma.c",
"libm/math/er_lgamma.c",
"libm/math/e_scalb.c",
"libm/math/e_sinh.c",
"libm/math/e_sqrt.c",
"libm/math/e_tgamma.c",
"libm/math/s_asinh.c",
"libm/math/s_atan.c",
"libm/math/s_ceil.c",
"libm/math/s_cos.c",
"libm/math/s_erf.c",
"libm/math/s_fabs.c",
"libm/math/sf_asinh.c",
"libm/math/sf_atan.c",
"libm/math/sf_ceil.c",
"libm/math/sf_cos.c",
"libm/math/sf_erf.c",
"libm/math/sf_fabs.c",
"libm/math/sf_floor.c",
"libm/math/sf_frexp.c",
"libm/math/sf_ldexp.c",
"libm/math/s_floor.c",
"libm/math/s_frexp.c",
"libm/math/sf_signif.c",
"libm/math/sf_sin.c",
"libm/math/sf_tan.c",
"libm/math/sf_tanh.c",
"libm/math/s_ldexp.c",
"libm/math/s_signif.c",
"libm/math/s_sin.c",
"libm/math/s_tan.c",
"libm/math/s_tanh.c",
}
+90 -790
View File
@@ -1,32 +1,16 @@
package builder
import (
"bytes"
"debug/dwarf"
"debug/elf"
"debug/macho"
"debug/pe"
"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
Packages map[string]*packageSize
Sum *packageSize
Code uint64
ROData uint64
Data uint64
BSS uint64
}
@@ -42,16 +26,6 @@ func (ps *programSize) sortedPackageNames() []string {
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 {
@@ -71,792 +45,118 @@ 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
type symbolList []elf.Symbol
func (l symbolList) Len() int {
return len(l)
}
// 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
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
}
type memoryType int
const (
memoryCode memoryType = iota + 1
memoryData
memoryROData
memoryBSS
memoryStack
)
func (t memoryType) String() string {
return [...]string{
0: "-",
memoryCode: "code",
memoryData: "data",
memoryROData: "rodata",
memoryBSS: "bss",
memoryStack: "stack",
}[t]
}
// 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|embedfsfiles|embedfsslice|embedslice|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, skipTombstone bool) ([]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 && skipTombstone {
// 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
// The value 0 can however really occur in object files,
// that typically start at address 0. So don't skip
// tombstone values in object files (like when parsing MachO
// files).
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
}
// Read a MachO object file and return a line table.
// Also return an index from symbol name to start address in the line table.
func readMachOSymbolAddresses(path string) (map[string]int, []addressLine, error) {
// Some constants from mach-o/nlist.h
// See: https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/nlist.h.auto.html
const (
N_STAB = 0xe0
N_TYPE = 0x0e // bitmask for N_TYPE field
N_SECT = 0xe // one of the possible type in the N_TYPE field
)
// Read DWARF from the given object file.
file, err := macho.Open(path)
if err != nil {
return nil, nil, err
}
defer file.Close()
dwarf, err := file.DWARF()
if err != nil {
return nil, nil, err
}
lines, err := readProgramSizeFromDWARF(dwarf, 0, false)
if err != nil {
return nil, nil, err
}
// Make a map from start addresses to indices in the line table (because the
// line table is a slice, not a map).
addressToLine := make(map[uint64]int, len(lines))
for i, line := range lines {
if _, ok := addressToLine[line.Address]; ok {
addressToLine[line.Address] = -1
continue
}
addressToLine[line.Address] = i
}
// Make a map that for each symbol gives the start index in the line table.
addresses := make(map[string]int, len(addressToLine))
for _, symbol := range file.Symtab.Syms {
if symbol.Type&N_STAB != 0 {
continue // STABS entry, ignore
}
if symbol.Type&0x0e != N_SECT {
continue // undefined symbol
}
if index, ok := addressToLine[symbol.Value]; ok && index >= 0 {
if _, ok := addresses[symbol.Name]; ok {
// There is a duplicate. Mark it as unavailable.
addresses[symbol.Name] = -1
continue
}
addresses[symbol.Name] = index
}
}
return addresses, lines, nil
func (l symbolList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// 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)
func loadProgramSize(path string) (*programSize, error) {
file, err := elf.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
defer file.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, true)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
var sumCode uint64
var sumData uint64
var sumBSS uint64
for _, section := range file.Sections {
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
// 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
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
continue
}
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,
})
}
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
}
}
// 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,
})
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 {
// Regular .bss section.
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Type: memoryBSS,
})
pkgSize.Data += symbol.Size
}
} 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 := macho.NewFile(f); err == nil {
// Read segments, for use while reading through sections.
segments := map[string]*macho.Segment{}
for _, load := range file.Loads {
switch load := load.(type) {
case *macho.Segment:
segments[load.Name] = load
}
}
// Read MachO sections.
for _, section := range file.Sections {
sectionType := section.Flags & 0xff
sectionFlags := section.Flags >> 8
segment := segments[section.Seg]
// For the constants used here, see:
// https://github.com/llvm/llvm-project/blob/release/14.x/llvm/include/llvm/BinaryFormat/MachO.h
if sectionFlags&0x800000 != 0 { // S_ATTR_PURE_INSTRUCTIONS
// Section containing only instructions.
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Type: memoryCode,
})
} else if sectionType == 1 { // S_ZEROFILL
// Section filled with zeroes on demand.
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Type: memoryBSS,
})
} else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data)
// Protection doesn't allow writes, so mark this section read-only.
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Type: memoryROData,
})
} else {
// The rest is assumed to be regular data.
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Type: memoryData,
})
pkgSize.ROData += symbol.Size
}
}
// Read DWARF information.
// The data isn't stored directly in the binary as in most executable
// formats. Instead, it is left in the object files that were used as a
// basis for linking. The executable does however contain STABS debug
// information that points to the source object file and is used by
// debuggers.
// For more information:
// http://wiki.dwarfstd.org/index.php?title=Apple%27s_%22Lazy%22_DWARF_Scheme
var objSymbolNames map[string]int
var objAddresses []addressLine
var previousSymbol macho.Symbol
for _, symbol := range file.Symtab.Syms {
// STABS constants, from mach-o/stab.h:
// https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/stab.h.auto.html
const (
N_GSYM = 0x20
N_FUN = 0x24
N_STSYM = 0x26
N_SO = 0x64
N_OSO = 0x66
)
if symbol.Type == N_OSO {
// Found an object file. Now try to parse it.
objSymbolNames, objAddresses, err = readMachOSymbolAddresses(symbol.Name)
if err != nil && sizesDebug {
// Errors are normally ignored. If there is an error, it's
// simply treated as that the DWARF is not available.
fmt.Fprintf(os.Stderr, "could not read DWARF from file %s: %s\n", symbol.Name, err)
}
} else if symbol.Type == N_FUN {
// Found a function.
// The way this is encoded is a bit weird. MachO symbols don't
// have a length. What I've found is that the length is encoded
// by first having a N_FUN symbol as usual, and then having a
// symbol with a zero-length name that has the value not set to
// the address of the symbol but to the length. So in order to
// get both the address and the length, we look for a symbol
// with a name followed by a symbol without a name.
if symbol.Name == "" && previousSymbol.Type == N_FUN && previousSymbol.Name != "" {
// Functions are encoded as many small chunks in the line
// table (one or a few instructions per source line). But
// the symbol length covers the whole symbols, over many
// lines and possibly including inlined functions. So we
// continue to iterate through the objAddresses slice until
// we've found all the source lines that are part of this
// symbol.
address := previousSymbol.Value
length := symbol.Value
if index, ok := objSymbolNames[previousSymbol.Name]; ok && index >= 0 {
for length > 0 {
line := objAddresses[index]
line.Address = address
if line.Length > length {
// Line extends beyond the end of te symbol?
// Weird, shouldn't happen.
break
}
addresses = append(addresses, line)
index++
length -= line.Length
address += line.Length
}
}
}
} else if symbol.Type == N_GSYM || symbol.Type == N_STSYM {
// Global variables.
if index, ok := objSymbolNames[symbol.Name]; ok {
address := objAddresses[index]
address.Address = symbol.Value
addresses = append(addresses, address)
}
}
previousSymbol = symbol
}
} else if file, err := pe.NewFile(f); err == nil {
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0, true)
if err != nil {
// However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data.
return nil, err
}
}
// Read COFF sections.
optionalHeader := file.OptionalHeader.(*pe.OptionalHeader64)
for _, section := range file.Sections {
// For more information:
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
const (
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000
)
if section.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
// Debug sections, etc.
continue
}
address := uint64(section.VirtualAddress) + optionalHeader.ImageBase
if section.Characteristics&IMAGE_SCN_CNT_CODE != 0 {
// .text
sections = append(sections, memorySection{
Address: address,
Size: uint64(section.VirtualSize),
Type: memoryCode,
})
} else if section.Characteristics&IMAGE_SCN_CNT_INITIALIZED_DATA != 0 {
if section.Characteristics&IMAGE_SCN_MEM_WRITE != 0 {
// .data
sections = append(sections, memorySection{
Address: address,
Size: uint64(section.Size),
Type: memoryData,
})
if section.Size < section.VirtualSize {
// Equivalent of a .bss section.
// Note: because of how the PE/COFF format is
// structured, not all zero-initialized data is marked
// as such. A portion may be at the end of the .data
// section and is thus marked as initialized data.
sections = append(sections, memorySection{
Address: address + uint64(section.Size),
Size: uint64(section.VirtualSize) - uint64(section.Size),
Type: memoryBSS,
})
}
} else if section.Characteristics&IMAGE_SCN_MEM_READ != 0 {
// .rdata, .buildid, .pdata
sections = append(sections, memorySection{
Address: address,
Size: uint64(section.VirtualSize),
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, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, codeOffset, true)
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)
lastSymbolValue = symbol.Value
}
// 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,
}
sum := &packageSize{}
for _, pkg := range sizes {
program.Code += pkg.Code
program.ROData += pkg.ROData
program.Data += pkg.Data
program.BSS += pkg.BSS
sum.Code += pkg.Code
sum.ROData += pkg.ROData
sum.Data += pkg.Data
sum.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
if sizesDebug {
fmt.Printf("%08x..%08x %5d: %s\n", addr, sectionEnd, section.Size, section.Type)
}
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 %5d: 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 %5d: 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
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
}
-75
View File
@@ -1,75 +0,0 @@
//go:build byollvm
// +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_macho(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 {
if args[0] == "-m" && args[1] == "i386pep" {
linker = "mingw"
} else if args[0] == "-flavor" {
linker = args[1]
args = args[2:]
}
}
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 "darwin":
ok = C.tinygo_link_macho(C.int(len(args)), (**C.char)(buf))
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
}
-16
View File
@@ -1,16 +0,0 @@
//go:build !byollvm
// +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)
}
-50
View File
@@ -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()
}
+12 -35
View File
@@ -10,34 +10,27 @@ package builder
import (
"bytes"
"encoding/binary"
"os"
"strconv"
"io/ioutil"
)
// convertELFFileToUF2File converts an ELF file to a UF2 file.
func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
func convertELFFileToUF2File(infile, outfile string) error {
// Read the .text segment.
targetAddress, data, err := extractROM(infile)
if err != nil {
return err
}
output, _, err := convertBinToUF2(data, uint32(targetAddress), uf2FamilyID)
if err != nil {
return err
}
return os.WriteFile(outfile, output, 0644)
output, _ := convertBinToUF2(data, uint32(targetAddress))
return ioutil.WriteFile(outfile, output, 0644)
}
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byte, int, error) {
func convertBinToUF2(input []byte, targetAddr uint32) ([]byte, int) {
blocks := split(input, 256)
output := make([]byte, 0)
bl, err := newUF2Block(targetAddr, uf2FamilyID)
if err != nil {
return nil, 0, err
}
bl := newUF2Block(targetAddr)
bl.SetNumBlocks(len(blocks))
for i := 0; i < len(blocks); i++ {
@@ -48,7 +41,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
bl.IncrementAddress(bl.payloadSize)
}
return output, len(blocks), nil
return output, len(blocks)
}
const (
@@ -72,32 +65,18 @@ type uf2Block struct {
}
// newUF2Block returns a new uf2Block struct that has been correctly populated
func newUF2Block(targetAddr uint32, uf2FamilyID string) (*uf2Block, error) {
var flags uint32
var familyID uint32
if uf2FamilyID != "" {
flags |= flagFamilyIDPresent
v, err := strconv.ParseUint(uf2FamilyID, 0, 32)
if err != nil {
return nil, err
}
familyID = uint32(v)
}
func newUF2Block(targetAddr uint32) *uf2Block {
return &uf2Block{magicStart0: uf2MagicStart0,
magicStart1: uf2MagicStart1,
magicEnd: uf2MagicEnd,
targetAddr: targetAddr,
flags: flags,
familyID: familyID,
flags: 0x0,
familyID: 0x0,
payloadSize: 256,
data: make([]byte, 476),
}, nil
}
}
const (
flagFamilyIDPresent = 0x00002000
)
// Bytes converts the uf2Block to a slice of bytes that can be written to file.
func (b *uf2Block) Bytes() []byte {
buf := bytes.NewBuffer(make([]byte, 0, 512))
@@ -141,13 +120,11 @@ func split(input []byte, limit int) [][]byte {
var block []byte
output := make([][]byte, 0, len(input)/limit+1)
for len(input) >= limit {
// add all blocks
block, input = input[:limit], input[limit:]
output = append(output, block)
}
if len(input) > 0 {
// add remaining block (that isn't full sized)
output = append(output, input)
output = append(output, input[:len(input)])
}
return output
}
+665 -651
View File
File diff suppressed because it is too large Load Diff
+9 -23
View File
@@ -9,7 +9,7 @@ import (
"go/parser"
"go/token"
"go/types"
"os"
"io/ioutil"
"path/filepath"
"runtime"
"strings"
@@ -19,24 +19,10 @@ import (
// 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"}
var cflags = []string{"--target=armv6m-none-eabi"}
for _, name := range []string{
"basic",
"errors",
"types",
"symbols",
"flags",
"const",
} {
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) {
// Read the AST in memory.
@@ -48,7 +34,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "")
cgoAST, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
// Check the AST for type errors.
var typecheckErrors []error
@@ -88,22 +74,22 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := normalizeResult(buf.String())
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
expectedBytes, err := os.ReadFile(outfile)
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")
expected := strings.Replace(string(expectedBytes), "\r\n", "\n", -1)
// Check whether the output is as expected.
if expected != actual {
// It is not. Test failed.
if *flagUpdate {
// Update the file with the expected data.
err := os.WriteFile(outfile, []byte(actual), 0666)
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
if err != nil {
t.Error("could not write updated output file:", err)
}
@@ -137,7 +123,7 @@ func formatDiagnostic(err error) string {
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
msg = strings.Replace(msg, "testdata\\", "testdata/", -1)
}
return "// " + msg + "\n"
}
+74 -164
View File
@@ -11,184 +11,105 @@ import (
"strings"
)
var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
token.QUO: precedenceMul,
token.REM: precedenceMul,
}
)
const (
precedenceLowest = iota + 1
precedenceAdd
precedenceMul
precedencePrefix
)
func init() {
// This must be done in an init function to avoid an initialization order
// failure.
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
token.IDENT: parseIdent,
token.INT: parseBasicLit,
token.FLOAT: parseBasicLit,
token.STRING: parseBasicLit,
token.CHAR: parseBasicLit,
token.LPAREN: parseParenExpr,
token.SUB: parseUnaryExpr,
}
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value)
expr, err := parseConstExpr(t, precedenceLowest)
t.Next()
if t.curToken != token.EOF {
expr, err := parseConstExpr(t)
if t.token != token.EOF {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
Pos: t.fset.Position(t.pos),
Msg: "unexpected token " + t.token.String(),
}
}
return expr, err
}
// parseConstExpr parses a stream of C tokens to a Go expression.
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
if t.curToken == token.EOF {
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
switch t.token {
case token.LPAREN:
lparen := t.pos
t.Next()
x, err := parseConstExpr(t)
if err != nil {
return nil, err
}
if t.token != token.RPAREN {
return nil, unexpectedToken(t, token.RPAREN)
}
expr := &ast.ParenExpr{
Lparen: lparen,
X: x,
Rparen: t.pos,
}
t.Next()
return expr, nil
case token.INT, token.FLOAT, token.STRING, token.CHAR:
expr := &ast.BasicLit{
ValuePos: t.pos,
Kind: t.token,
Value: t.value,
}
t.Next()
return expr, nil
case token.IDENT:
expr := &ast.Ident{
NamePos: t.pos,
Name: "C." + t.value,
}
t.Next()
return expr, nil
case token.EOF:
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Pos: t.fset.Position(t.pos),
Msg: "empty constant",
}
}
prefix := prefixParseFns[t.curToken]
if prefix == nil {
default:
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s", t.token),
}
}
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),
Pos: t.fset.Position(t.pos),
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
}
}
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
pos token.Pos
fset *token.FileSet
token token.Token
value string
buf string
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
pos: start,
fset: fset,
buf: buf,
token: token.ILLEGAL,
}
// Parse the first two tokens (cur and peek).
t.Next()
t.Next()
t.Next() // Parse the first token.
return t
}
// Next consumes the next token in the stream. There is no return value, read
// the next token from the pos, token and value properties.
func (t *tokenizer) Next() {
// 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))
t.pos += token.Pos(len(t.value))
for {
if len(t.buf) == 0 {
t.peekToken = token.EOF
t.token = token.EOF
return
}
c := t.buf[0]
@@ -197,28 +118,17 @@ func (t *tokenizer) Next() {
// Skip whitespace.
// Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace
t.peekPos++
t.pos++
t.buf = t.buf[1:]
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
case c == '(' || c == ')':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
case '(':
t.peekToken = token.LPAREN
t.token = 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.token = token.RPAREN
}
t.peekValue = t.buf[:1]
t.value = t.buf[:1]
t.buf = t.buf[1:]
return
case c >= '0' && c <= '9':
@@ -236,17 +146,17 @@ func (t *tokenizer) Next() {
break
}
}
t.peekValue = t.buf[:tokenLen]
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
if hasDot {
// Integer constants are more complicated than this but this is
// a close approximation.
// https://en.cppreference.com/w/cpp/language/integer_literal
t.peekToken = token.FLOAT
t.peekValue = strings.TrimRight(t.peekValue, "f")
t.token = token.FLOAT
t.value = strings.TrimRight(t.value, "f")
} else {
t.peekToken = token.INT
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
t.token = token.INT
t.value = strings.TrimRight(t.value, "uUlL")
}
return
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
@@ -260,9 +170,9 @@ func (t *tokenizer) Next() {
break
}
}
t.peekValue = t.buf[:tokenLen]
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
t.peekToken = token.IDENT
t.token = token.IDENT
return
case c == '"':
// String constant. Find the first '"' character that is not
@@ -278,8 +188,8 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.peekToken = token.STRING
t.peekValue = t.buf[:tokenLen]
t.token = token.STRING
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
case c == '\'':
@@ -296,12 +206,12 @@ func (t *tokenizer) Next() {
escape = c == '\\'
}
}
t.peekToken = token.CHAR
t.peekValue = t.buf[:tokenLen]
t.token = token.CHAR
t.value = t.buf[:tokenLen]
t.buf = t.buf[tokenLen:]
return
default:
t.peekToken = token.ILLEGAL
t.token = token.ILLEGAL
return
}
}
+2 -19
View File
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
{`(5)`, `(5)`},
{`(((5)))`, `(5)`},
{`)`, `error: 1:1: unexpected token )`},
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
{`5)`, `error: 1:2: unexpected token )`},
{" \t)", `error: 1:4: unexpected token )`},
{`5.8f`, `5.8`},
{`foo`, `C.foo`},
@@ -30,24 +30,7 @@ func TestParseConst(t *testing.T) {
{`'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`},
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
+199 -494
View File
@@ -4,9 +4,6 @@ package cgo
// modification. It does not touch the AST itself.
import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"fmt"
"go/ast"
"go/scanner"
@@ -15,13 +12,10 @@ import (
"strconv"
"strings"
"unsafe"
"tinygo.org/x/go-llvm"
)
/*
#include <clang-c/Index.h> // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/
#include <llvm/Config/llvm-config.h>
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
#include <stdlib.h>
#include <stdint.h>
@@ -45,8 +39,6 @@ typedef struct {
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
CXString tinygo_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy);
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(GoCXCursor c);
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
CXType tinygo_clang_getCursorType(GoCXCursor c);
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
@@ -54,7 +46,6 @@ 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);
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
@@ -65,7 +56,6 @@ 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"
@@ -80,28 +70,17 @@ var diagnosticSeverity = [...]string{
C.CXDiagnostic_Fatal: "fatal",
}
// Alias so that cgo.go (which doesn't import Clang related stuff and is in
// theory decoupled from Clang) can also use this type.
type clangCursor = C.GoCXCursor
func init() {
// Check that we haven't messed up LLVM versioning.
// This can happen when llvm_config_*.go files in either this or the
// tinygo.org/x/go-llvm packages is incorrect. It should not ever happen
// with byollvm.
if C.LLVM_VERSION_STRING != llvm.Version {
panic("incorrect build: using LLVM version " + llvm.Version + " in the tinygo.org/x/llvm package, and version " + C.LLVM_VERSION_STRING + " in the ./cgo package")
}
}
func (f *cgoFile) readNames(fragment string, cflags []string, filename string, callback func(map[string]clangCursor)) {
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
index := C.clang_createIndex(0, 0)
defer C.clang_disposeIndex(index)
// pretend to be a .c file
filenameC := C.CString(filename + "!cgo.c")
filenameC := C.CString(posFilename + "!cgo.c")
defer C.free(unsafe.Pointer(filenameC))
// fix up error locations
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
fragmentC := C.CString(fragment)
defer C.free(unsafe.Pointer(fragmentC))
@@ -135,14 +114,13 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
}
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 := f.getClangLocationPosition(location, unit)
f.addError(pos, severity+": "+spelling)
pos := p.getClangLocationPosition(location, unit)
p.addError(pos, severity+": "+spelling)
}
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
@@ -156,109 +134,32 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
}
}
// Extract information required by CGo.
ref := storedRefs.Put(f)
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 := f.visitedFiles[path]; !ok {
// already stored
sum := sha512.Sum512_224(data)
f.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))
// Do all the C AST operations inside a callback. This makes sure that
// libclang related memory is only freed after it is not necessary anymore.
callback(f.names)
}
// Convert the AST node under the given Clang cursor to a Go AST node and return
// it.
func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
//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 := f.getCursorPosition(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))
obj := &ast.Object{
Kind: ast.Fun,
Name: "C." + name,
}
exportName := name
localName := name
var stringSignature string
if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
// A static function is assigned a globally unique symbol name based
// on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
// has a different Go name in the form of C.foo!symbols.go instead
// of just C.foo.
path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name())
staticIDBuf := sha256.Sum256([]byte(path))
staticID := hex.EncodeToString(staticIDBuf[:10])
exportName = "_Cgo_static_" + staticID + "_" + name
localName = name + "!" + filepath.Base(path)
// Create a signature. This is necessary for MacOS to forward the
// call, because MacOS doesn't support aliases like ELF and PE do.
// (There is N_INDR but __attribute__((alias("..."))) doesn't work).
policy := C.tinygo_clang_getCursorPrintingPolicy(c)
defer C.clang_PrintingPolicy_dispose(policy)
C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1)
stringSignature = getString(C.tinygo_clang_getCursorPrettyPrinted(c, policy))
stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1)
stringSignature = strings.TrimPrefix(stringSignature, "static ")
}
args := make([]*ast.Field, numArgs)
decl := &ast.FuncDecl{
Doc: &ast.CommentGroup{
List: []*ast.Comment{
{
Slash: pos - 1,
Text: "//export " + exportName,
},
},
},
Name: &ast.Ident{
NamePos: pos,
Name: "C." + localName,
Obj: obj,
},
Type: &ast.FuncType{
Func: pos,
Params: &ast.FieldList{
Opening: pos,
List: args,
Closing: pos,
},
},
}
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
Slash: pos - 1,
Text: "//go:variadic",
})
return C.CXChildVisit_Continue // not supported
}
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
fn := &functionInfo{
pos: pos,
}
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))
@@ -266,108 +167,50 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
if argName == "" {
argName = "$" + strconv.Itoa(i)
}
args[i] = &ast.Field{
Names: []*ast.Ident{
{
NamePos: pos,
Name: argName,
Obj: &ast.Object{
Kind: ast.Var,
Name: argName,
Decl: decl,
},
},
},
Type: f.makeDecayingASTType(argType, pos),
}
fn.args = append(fn.args, paramInfo{
name: argName,
typeExpr: p.makeASTType(argType, pos),
})
}
resultType := C.tinygo_clang_getCursorResultType(c)
if resultType.kind != C.CXType_Void {
decl.Type.Results = &ast.FieldList{
fn.results = &ast.FieldList{
List: []*ast.Field{
{
Type: f.makeASTType(resultType, pos),
&ast.Field{
Type: p.makeASTType(resultType, pos),
},
},
}
}
obj.Decl = decl
return decl, stringSignature
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos)
typeName := "C." + name
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// Convert to a single-field struct type.
typeExpr = f.makeUnionField(typ)
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
}
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: typ.pos,
Name: typeName,
Obj: obj,
},
Type: typeExpr,
}
obj.Decl = typeSpec
return typeSpec, typ
p.makeASTType(typ, pos)
case C.CXCursor_TypedefDecl:
typeName := "C." + name
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
if _, required := p.missingSymbols[name]; !required {
return C.CXChildVisit_Continue
}
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: obj,
},
Type: f.makeASTType(underlyingType, pos),
}
if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos
}
obj.Decl = typeSpec
return typeSpec, nil
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)
typeExpr := f.makeASTType(cursorType, pos)
gen := &ast.GenDecl{
TokPos: pos,
Tok: token.VAR,
Lparen: token.NoPos,
Rparen: token.NoPos,
Doc: &ast.CommentGroup{
List: []*ast.Comment{
{
Slash: pos - 1,
Text: "//go:extern " + name,
},
},
},
p.globals[name] = globalInfo{
typeExpr: p.makeASTType(cursorType, pos),
pos: pos,
}
obj := &ast.Object{
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
}},
Type: typeExpr,
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
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)
@@ -375,17 +218,17 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
f.addError(pos, "internal error: could not find file where macro is defined")
return nil, 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 {
f.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
return nil, nil
p.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
break
}
if startOffset > endOffset {
f.addError(pos, "internal error: start offset of macro is after end offset")
return nil, nil
p.addError(pos, "internal error: start offset of macro is after end offset")
break
}
// read file contents and extract the relevant byte range
@@ -393,94 +236,31 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
f.addError(pos, "internal error: end offset of macro lies after end of file")
return nil, nil
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) {
f.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
return nil, nil
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)), f.fset, value)
if scannerError != nil {
f.errors = append(f.errors, *scannerError)
return nil, nil
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
if err != nil {
p.errors = append(p.errors, err)
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
if expr != nil {
// Parsing was successful.
p.constants[name] = constantInfo{expr, pos}
}
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_EnumDecl:
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name,
}
underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
// TODO: gc's CGo implementation uses types such as `uint32` for enums
// instead of types such as C.int, which are used here.
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: "C." + name,
Obj: obj,
},
Assign: pos,
Type: f.makeASTType(underlying, pos),
}
obj.Decl = typeSpec
return typeSpec, nil
case C.CXCursor_EnumConstantDecl:
value := C.tinygo_clang_getEnumConstantDeclValue(c)
expr := &ast.BasicLit{
ValuePos: pos,
Kind: token.INT,
Value: strconv.FormatInt(int64(value), 10),
}
gen := &ast.GenDecl{
TokPos: token.NoPos,
Tok: token.CONST,
Lparen: token.NoPos,
Rparen: token.NoPos,
}
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
}
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
default:
f.addError(pos, fmt.Sprintf("internal error: unknown cursor type: %d", kind))
return nil, nil
// 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) {
@@ -490,49 +270,6 @@ func getString(clangString C.CXString) (s string) {
return
}
//export tinygo_clang_globals_visitor
func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
f := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoFile)
switch C.tinygo_clang_getCursorKind(c) {
case C.CXCursor_FunctionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
f.names[name] = c
case C.CXCursor_StructDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name != "" {
f.names["struct_"+name] = c
}
case C.CXCursor_UnionDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name != "" {
f.names["union_"+name] = c
}
case C.CXCursor_TypedefDecl:
typedefType := C.tinygo_clang_getCursorType(c)
name := getString(C.clang_getTypedefName(typedefType))
f.names[name] = c
case C.CXCursor_VarDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
f.names[name] = c
case C.CXCursor_MacroDefinition:
name := getString(C.tinygo_clang_getCursorSpelling(c))
f.names[name] = c
case C.CXCursor_EnumDecl:
name := getString(C.tinygo_clang_getCursorSpelling(c))
if name != "" {
// Named enum, which can be referenced from Go using C.enum_foo.
f.names["enum_"+name] = c
}
// The enum fields are in global scope, so recurse to visit them.
return C.CXChildVisit_Recurse
case C.CXCursor_EnumConstantDecl:
// We arrive here because of the "Recurse" above.
name := getString(C.tinygo_clang_getCursorSpelling(c))
f.names[name] = c
}
return C.CXChildVisit_Continue
}
// 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))
@@ -616,7 +353,7 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
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)
relpath, err := filepath.Rel(p.dir, position.Filename)
if err == nil {
position.Filename = relpath
}
@@ -627,44 +364,9 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
})
}
// makeDecayingASTType does the same as makeASTType but takes care of decaying
// types (arrays in function parameters, etc). It is otherwise identical to
// makeASTType.
func (f *cgoFile) 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: f.makeASTType(pointeeType, pos),
}
}
return f.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 (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
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:
@@ -725,7 +427,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
return &ast.StarExpr{
Star: pos,
X: f.makeASTType(pointeeType, pos),
X: p.makeASTType(pointeeType, pos),
}
case C.CXType_ConstantArray:
return &ast.ArrayType{
@@ -735,7 +437,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
Kind: token.INT,
Value: strconv.FormatInt(int64(C.clang_getArraySize(typ)), 10),
},
Elt: f.makeASTType(C.clang_getElementType(typ), pos),
Elt: p.makeASTType(C.clang_getElementType(typ), pos),
}
case C.CXType_FunctionProto:
// Be compatible with gc, which uses the *[0]byte type for function
@@ -756,21 +458,71 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
case C.CXType_Typedef:
name := getString(C.clang_getTypedefName(typ))
c := C.tinygo_clang_getTypeDeclaration(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: f.getASTDeclName(name, c, false),
Name: "C." + name,
}
case C.CXType_Elaborated:
underlying := C.clang_Type_getNamedType(typ)
switch underlying.kind {
case C.CXType_Record:
return f.makeASTType(underlying, pos)
return p.makeASTType(underlying, pos)
case C.CXType_Enum:
return f.makeASTType(underlying, pos)
return p.makeASTType(underlying, pos)
default:
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
p.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>"
}
case C.CXType_Record:
@@ -788,46 +540,63 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
if name == "" {
// Anonymous record, probably inside a typedef.
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
var column C.unsigned
C.clang_getFileLocation(clangLocation, &file, &line, &column, nil)
location := token.Position{
Filename: getString(C.clang_getFileName(file)),
Line: int(line),
Column: int(column),
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,
}
}
if location.Filename == "" || location.Line == 0 {
// Not sure when this would happen, but protect from it anyway.
f.addError(pos, "could not find file/line information")
}
name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location)
return typeInfo.typeExpr
} else {
name = cgoRecordPrefix + name
}
return &ast.Ident{
NamePos: pos,
Name: f.getASTDeclName(name, cursor, false),
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 == "" {
name = f.getUnnamedDeclName("_Ctype_enum___", cursor)
// 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 {
name = "enum_" + name
}
return &ast.Ident{
NamePos: pos,
Name: f.getASTDeclName(name, cursor, false),
// 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))
f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
p.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
typeName = "C.<unknown>"
}
return &ast.Ident{
@@ -836,80 +605,9 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
}
}
// getIntegerType returns an AST node that defines types such as C.int.
func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSpec {
pos := p.getCursorPosition(cursor)
// Find a Go type that matches the size and signedness of the given C type.
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(cursor)
var goName string
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch 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:
goName = "int8"
case C.CXType_Char_U:
goName = "uint8"
}
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typeSize {
case 1:
goName = "int8"
case 2:
goName = "int16"
case 4:
goName = "int32"
case 8:
goName = "int64"
}
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typeSize {
case 1:
goName = "uint8"
case 2:
goName = "uint16"
case 4:
goName = "uint32"
case 8:
goName = "uint64"
}
}
if goName == "" { // should not happen
p.addError(pos, "internal error: did not find Go type for C type "+name)
goName = "int"
}
// Construct an *ast.TypeSpec for this type.
obj := &ast.Object{
Kind: ast.Typ,
Name: name,
}
spec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: name,
Obj: obj,
},
Type: &ast.Ident{
NamePos: pos,
Name: goName,
},
}
obj.Decl = spec
return spec
}
// makeASTRecordType parses a C record (struct or union) and translates it into
// a Go struct type.
func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
func (p *cgoPackage) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elaboratedTypeInfo {
fieldList := &ast.FieldList{
Opening: pos,
Closing: pos,
@@ -919,11 +617,11 @@ func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elabora
bitfieldNum := 0
ref := storedRefs.Put(struct {
fieldList *ast.FieldList
file *cgoFile
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
}{fieldList, f, &inBitfield, &bitfieldNum, &bitfieldList})
}{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)
@@ -952,13 +650,13 @@ func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elabora
}
if bitfieldList != nil {
// This is valid C... but please don't do this.
f.addError(pos, "bitfield in a union is not supported")
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 {
f.addError(pos, "zero-length union is not supported")
p.addError(pos, "zero-length union is not supported")
}
typeInfo.unionSize = sizeInBytes
typeInfo.unionAlign = alignInBytes
@@ -966,7 +664,7 @@ func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elabora
default:
cursorKind := C.tinygo_clang_getCursorKind(cursor)
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
f.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
p.addError(pos, fmt.Sprintf("expected StructDecl or UnionDecl, not %s", cursorKindSpelling))
return &elaboratedTypeInfo{
typeExpr: &ast.StructType{
Struct: pos,
@@ -980,17 +678,17 @@ func (f *cgoFile) makeASTRecordType(cursor C.GoCXCursor, pos token.Pos) *elabora
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
file *cgoFile
pkg *cgoPackage
inBitfield *bool
bitfieldNum *int
bitfieldList *[]bitfieldInfo
})
fieldList := passed.fieldList
f := passed.file
p := passed.pkg
inBitfield := passed.inBitfield
bitfieldNum := passed.bitfieldNum
bitfieldList := passed.bitfieldList
pos := f.getCursorPosition(c)
pos := p.getCursorPosition(c)
switch cursorKind := C.tinygo_clang_getCursorKind(c); cursorKind {
case C.CXCursor_FieldDecl:
// Expected. This is a regular field.
@@ -999,7 +697,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
return C.CXChildVisit_Continue
default:
cursorKindSpelling := getString(C.clang_getCursorKindSpelling(cursorKind))
f.addError(pos, fmt.Sprintf("expected FieldDecl in struct or union, not %s", cursorKindSpelling))
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))
@@ -1010,14 +708,14 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
}
typ := C.tinygo_clang_getCursorType(c)
field := &ast.Field{
Type: f.makeASTType(typ, f.getCursorPosition(c)),
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 {
f.addError(pos, "expected a bitfield")
p.addError(pos, "expected a bitfield")
return C.CXChildVisit_Continue
}
if !*inBitfield {
@@ -1050,7 +748,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
}
*inBitfield = false
field.Names = []*ast.Ident{
{
&ast.Ident{
NamePos: pos,
Name: name,
Obj: &ast.Object{
@@ -1064,8 +762,15 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
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)
//export tinygo_clang_enum_visitor
func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientData) C.int {
p := storedRefs.Get(unsafe.Pointer(client_data)).(*cgoPackage)
name := getString(C.tinygo_clang_getCursorSpelling(c))
pos := p.getCursorPosition(c)
value := C.tinygo_clang_getEnumConstantDeclValue(c)
p.constants[name] = constantInfo{
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
pos: pos,
}
return C.CXChildVisit_Continue
}
+13
View File
@@ -0,0 +1,13 @@
// +build !byollvm
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
*/
import "C"
-16
View File
@@ -1,16 +0,0 @@
//go:build !byollvm
// +build !byollvm
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-14/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@14/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@14/include
#cgo freebsd CFLAGS: -I/usr/local/llvm14/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-14/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@14/lib -lclang -lffi
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@14/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm14/lib -lclang
*/
import "C"
+1 -13
View File
@@ -3,7 +3,7 @@
// are slightly different from the ones defined in libclang.go, but they
// should be ABI compatible.
#include <clang-c/Index.h> // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
return clang_getTranslationUnitCursor(tu);
@@ -17,14 +17,6 @@ CXString tinygo_clang_getCursorSpelling(CXCursor c) {
return clang_getCursorSpelling(c);
}
CXString tinygo_clang_getCursorPrettyPrinted(CXCursor c, CXPrintingPolicy policy) {
return clang_getCursorPrettyPrinted(c, policy);
}
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(CXCursor c) {
return clang_getCursorPrintingPolicy(c);
}
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
return clang_getCursorKind(c);
}
@@ -53,10 +45,6 @@ CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i);
}
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) {
return clang_Cursor_getStorageClass(c);
}
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c);
}
+20 -33
View File
@@ -4,36 +4,23 @@ import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
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
+22 -35
View File
@@ -4,39 +4,26 @@ import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
const C.foo = 3
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
+1 -9
View File
@@ -14,12 +14,6 @@ typedef someType noType; // undefined type
#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
@@ -27,7 +21,7 @@ import "C"
//line errors.go:100
var (
// constant too large
_ C.char = 2 << 10
_ C.uint8_t = 2 << 10
// z member does not exist
_ C.point_t = C.point_t{z: 3}
@@ -36,6 +30,4 @@ var (
_ = C.SOME_CONST_1
_ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4
)
+25 -42
View File
@@ -1,16 +1,13 @@
// CGo errors:
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go: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
// testdata/errors.go:13:23: unexpected token )
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go: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
// testdata/errors.go:110: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
package main
@@ -18,43 +15,29 @@ import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
const C.SOME_CONST_3 = 1234
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
type C._Ctype_struct___0 struct {
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
}
type C.point_t = C._Ctype_struct___0
const C.SOME_CONST_3 = 1234
-11
View File
@@ -9,9 +9,6 @@ package main
#cgo CFLAGS: -DFOO
#cgo CFLAGS: -Iinclude
#include "foo.h"
#if defined(FOO)
#define BAR 3
#else
@@ -21,17 +18,9 @@ package main
#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
)
+21 -36
View File
@@ -1,7 +1,6 @@
// 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
@@ -9,39 +8,25 @@ import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
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
-1
View File
@@ -1 +0,0 @@
#define FOO_H 1
-25
View File
@@ -1,25 +0,0 @@
package main
/*
// Function signatures.
int foo(int a, int b);
void variadic0();
void variadic2(int x, int y, ...);
static void staticfunc(int x);
// Global variable signatures.
extern int someValue;
*/
import "C"
// Test function signatures.
func accessFunctions() {
C.foo(3, 4)
C.variadic0()
C.variadic2(3, 5)
C.staticfunc(3)
}
func accessGlobals() {
_ = C.someValue
}
-64
View File
@@ -1,64 +0,0 @@
package main
import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
//export foo
func C.foo(a C.int, b C.int) C.int
var C.foo$funcaddr unsafe.Pointer
//export variadic0
//go:variadic
func C.variadic0()
var C.variadic0$funcaddr unsafe.Pointer
//export variadic2
//go:variadic
func C.variadic2(x C.int, y C.int)
var C.variadic2$funcaddr unsafe.Pointer
//export _Cgo_static_173c95a79b6df1980521_staticfunc
func C.staticfunc!symbols.go(x C.int)
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
//go:extern someValue
var C.someValue C.int
-4
View File
@@ -107,10 +107,6 @@ typedef struct {
*/
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
+89 -119
View File
@@ -4,146 +4,116 @@ import "unsafe"
var _ unsafe.Pointer
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
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
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
C.char uint8
C.schar int8
C.uchar uint8
C.short int16
C.ushort uint16
C.int int32
C.uint uint32
C.long int32
C.ulong uint32
C.longlong int64
C.ulonglong uint64
)
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._Ctype_struct___0 struct {
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.point2d_t = C._Ctype_struct___0
type C.struct_point3d struct {
x C.int
y C.int
z C.int
}
type C.point3d_t = C.struct_point3d
type C.struct_type1 struct {
_type C.int
__type C.int
___type C.int
}
type C.struct_type2 struct{ _type C.int }
type C._Ctype_union___1 struct{ i C.int }
type C.union1_t = C._Ctype_union___1
type C._Ctype_union___2 struct{ $union uint64 }
func (union *C._Ctype_union___2) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___2) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___2) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type C.union3_t = C._Ctype_union___2
type C.union_union2d 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.union2d_t = C.union_union2d
type C._Ctype_union___3 struct{ arr [10]C.uchar }
type C.unionarray_t = C._Ctype_union___3
type C._Ctype_union___5 struct{ $union [3]uint32 }
func (union *C._Ctype_union___5) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___5) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
type C._Ctype_struct___4 struct {
type C.struct_nested_t = struct {
begin C.point2d_t
end C.point2d_t
tag C.int
coord C._Ctype_union___5
coord C.union_2
}
type C.struct_nested_t = C._Ctype_struct___4
type C._Ctype_union___6 struct{ $union [2]uint64 }
func (union *C._Ctype_union___6) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___6) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___6) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union))
}
type C.union_nested_t = C._Ctype_union___6
type C.enum_option = C.int
type C.option_t = C.enum_option
type C._Ctype_enum___7 = C.uint
type C.option2_t = C._Ctype_enum___7
type C._Ctype_struct___8 struct {
type C.types_t = struct {
f float32
d float64
ptr *C.int
}
type C.types_t = C._Ctype_struct___8
type C.myIntArray = [10]C.int
type C._Ctype_struct___9 struct {
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 (s *C._Ctype_struct___9) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C._Ctype_struct___9) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
}
func (s *C._Ctype_struct___9) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *C._Ctype_struct___9) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *C._Ctype_struct___9) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
func (s *C._Ctype_struct___9) set_bitfield_c(value C.uchar,
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)) }
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.union_1 struct{ $union uint64 }
type C.bitfield_t = C._Ctype_struct___9
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
+37 -368
View File
@@ -5,12 +5,10 @@ package compileopts
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/google/shlex"
"github.com/tinygo-org/tinygo/goenv"
)
@@ -23,7 +21,7 @@ type Config struct {
TestConfig TestConfig
}
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
// Triple returns the LLVM target triple, like armv6m-none-eabi.
func (c *Config) Triple() string {
return c.Target.Triple
}
@@ -35,16 +33,10 @@ func (c *Config) CPU() string {
}
// 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
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
// empty list will be returned.
func (c *Config) Features() []string {
return c.Target.Features
}
// GOOS returns the GOOS of the target. This might not always be the actual OS:
@@ -61,28 +53,18 @@ 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()}...)
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
tags = append(tags, c.Options.Tags...)
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", and "conservative".
func (c *Config) GC() string {
@@ -98,22 +80,20 @@ func (c *Config) GC() string {
// 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":
for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
default:
if c.GC() != "conservative" {
return false
}
for _, tag := range c.BuildTags() {
if tag == "baremetal" {
return false
}
}
return true
}
// Scheduler returns the scheduler implementation. Valid values are "none",
// "asyncify" and "tasks".
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
// and "tasks".
func (c *Config) Scheduler() string {
if c.Options.Scheduler != "" {
return c.Options.Scheduler
@@ -121,41 +101,8 @@ func (c *Config) Scheduler() string {
if c.Target.Scheduler != "" {
return c.Target.Scheduler
}
// Fall back to none.
return "none"
}
// 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)
}
// Fall back to coroutines, which are supported everywhere.
return "coroutines"
}
// PanicStrategy returns the panic strategy selected for this target. Valid
@@ -165,178 +112,12 @@ 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
}
// StackSize returns the default stack size to be used for goroutines, if the
// stack size could not be determined automatically at compile time.
func (c *Config) StackSize() uint64 {
if c.Options.StackSize != 0 {
return c.Options.StackSize
}
return c.Target.DefaultStackSize
}
// UseThinLTO returns whether ThinLTO should be used for the given target. Some
// targets (such as wasm) are not yet supported.
// We should try and remove as many exceptions as possible in the future, so
// that this optimization can be applied in more places.
func (c *Config) UseThinLTO() bool {
parts := strings.Split(c.Triple(), "-")
if parts[0] == "wasm32" {
// wasm-ld doesn't seem to support ThinLTO yet.
return false
}
if parts[0] == "avr" || parts[0] == "xtensa" {
// These use external (GNU) linkers which might perhaps support ThinLTO
// through a plugin, but it's too much hassle to set up.
return false
}
// Other architectures support ThinLTO.
return true
}
// 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) {
archname := c.Triple()
if c.CPU() != "" {
archname += "-" + c.CPU()
}
// Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, 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.
return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
}
// DefaultBinaryExtension returns the default extension for binaries, such as
// .exe, .wasm, or no extension (depending on the target).
func (c *Config) DefaultBinaryExtension() string {
parts := strings.Split(c.Triple(), "-")
if parts[0] == "wasm32" {
// WebAssembly files always have the .wasm file extension.
return ".wasm"
}
if len(parts) >= 3 && parts[2] == "windows" {
// Windows uses .exe.
return ".exe"
}
if len(parts) >= 3 && parts[2] == "unknown" {
// There appears to be a convention to use the .elf file extension for
// ELF files intended for microcontrollers. I'm not aware of the origin
// of this, it's just something that is used by many projects.
// I think it's a good tradition, so let's keep it.
return ".elf"
}
// Linux, MacOS, etc, don't use a file extension. Use it as a fallback.
return ""
}
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags() []string {
var cflags []string
cflags := append([]string{}, c.Options.CFlags...)
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
}
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags,
"--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"),
)
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc")
cflags = append(cflags,
"--sysroot="+path,
"-isystem", filepath.Join(path, "include"), // necessary for Xtensa
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
)
case "musl":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("musl")
arch := MuslArchitecture(c.Triple())
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", 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,
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", 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)
}
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
}
return cflags
}
@@ -347,11 +128,17 @@ func (c *Config) CFlags() []string {
func (c *Config) LDFlags() []string {
root := goenv.Get("TINYGOROOT")
// Merge and adjust LDFlags.
var ldflags []string
ldflags := append([]string{}, c.Options.LDFlags...)
for _, flag := range c.Target.LDFlags {
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
}
ldflags = append(ldflags, "-L", root)
if c.Target.GOARCH == "wasm" {
// Round heap size to next multiple of 65536 (the WebAssembly page
// size).
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
}
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
@@ -376,50 +163,12 @@ 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.
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
// and similar.
func (c *Config) Debug() bool {
return c.Options.Debug
}
// 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 ".img":
// Image file. Only defined for the ESP32 at the moment, where it is a
// full (runnable) image that can be used in the Espressif QEMU fork.
if c.Target.BinaryFormat != "" {
return c.Target.BinaryFormat + "-img"
}
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.
@@ -431,9 +180,6 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, ""
default:
// The -programmer flag specifies something else, assume it specifies
// the OpenOCD interface name.
@@ -449,103 +195,26 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
if openocdInterface == "" {
return nil, errors.New("OpenOCD programmer not set")
}
if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(openocdInterface) {
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) {
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 != "" {
transport := c.Target.OpenOCDTransport
if transport == "swd" {
switch openocdInterface {
case "stlink-dap":
transport = "dapdirect_swd"
}
}
args = append(args, "-c", "transport select "+transport)
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
}
// EmulatorName is a shorthand to get the command for this emulator, something
// like qemu-system-arm or simavr.
func (c *Config) EmulatorName() string {
parts := strings.SplitN(c.Target.Emulator, " ", 2)
if len(parts) > 1 {
return parts[0]
}
return ""
}
// EmulatorFormat returns the binary format for the emulator and the associated
// file extension. An empty string means to pass directly whatever the linker
// produces directly without conversion (usually ELF format).
func (c *Config) EmulatorFormat() (format, fileExt string) {
switch {
case strings.Contains(c.Target.Emulator, "{img}"):
return "img", ".img"
default:
return "", ""
}
}
// Emulator returns a ready-to-run command to run the given binary in an
// emulator. Give it the format (returned by EmulatorFormat()) and the path to
// the compiled binary.
func (c *Config) Emulator(format, binary string) ([]string, error) {
parts, err := shlex.Split(c.Target.Emulator)
if err != nil {
return nil, fmt.Errorf("could not parse emulator command: %w", err)
}
var emulator []string
for _, s := range parts {
s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT"))
s = strings.ReplaceAll(s, "{"+format+"}", binary)
emulator = append(emulator, s)
}
return emulator, nil
}
type TestConfig struct {
CompileTestBinary bool
// TODO: Filter the test functions to run, include verbose flag, etc
+18 -115
View File
@@ -1,120 +1,23 @@
package compileopts
import (
"fmt"
"regexp"
"strings"
"time"
)
var (
validGCOptions = []string{"none", "leaking", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "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.
// usually passed from the command line.
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
StackSize uint64 // goroutine stack size (if none could be automatically determined)
Serial string
Work bool // -work flag to print temporary build directory
InterpTimeout time.Duration
PrintIR bool
DumpSSA bool
VerifyIR bool
PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap
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
Directory string
PrintJSON bool
Monitor bool
BaudRate int
}
// 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
Target string
Opt string
GC string
PanicStrategy string
Scheduler string
PrintIR bool
DumpSSA bool
VerifyIR bool
Debug bool
PrintSizes string
CFlags []string
LDFlags []string
Tags string
WasmAbi string
HeapSize int64
TestConfig TestConfig
Programmer string
}
-126
View File
@@ -1,126 +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, conservative`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, 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: "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: "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)
}
}
})
}
}
+148 -233
View File
@@ -5,12 +5,9 @@ package compileopts
import (
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strings"
@@ -26,84 +23,101 @@ type TargetSpec struct {
Inherits []string `json:"inherits"`
Triple string `json:"llvm-target"`
CPU string `json:"cpu"`
Features string `json:"features"`
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)
Compiler string `json:"compiler"`
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"`
Emulator []string `json:"emulator"`
FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"`
GDB string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
FlashMethod string `json:"flash-method"`
FlashVolume string `json:"msd-volume-name"`
FlashFilename string `json:"msd-firmware-name"`
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"`
OpenOCDVerify *bool `json:"openocd-verify"` // enable verify when flashing with openocd
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) error {
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, append the field and check for duplicates
dst.Set(reflect.AppendSlice(dst, src))
for i := 0; i < dst.Len(); i++ {
v := dst.Index(i).String()
for j := i + 1; j < dst.Len(); j++ {
w := dst.Index(j).String()
if v == w {
return fmt.Errorf("duplicate value '%s' in field %s", v, field.Name)
}
}
}
default:
return fmt.Errorf("unknown field type: %s", kind)
}
// copyProperties copies all properties that are set in spec2 into itself.
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
// TODO: simplify this using reflection? Inherits and BuildTags are special
// cases, but the rest can simply be copied if set.
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
if spec2.Triple != "" {
spec.Triple = spec2.Triple
}
if spec2.CPU != "" {
spec.CPU = spec2.CPU
}
spec.Features = append(spec.Features, spec2.Features...)
if spec2.GOOS != "" {
spec.GOOS = spec2.GOOS
}
if spec2.GOARCH != "" {
spec.GOARCH = spec2.GOARCH
}
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
if spec2.GC != "" {
spec.GC = spec2.GC
}
if spec2.Scheduler != "" {
spec.Scheduler = spec2.Scheduler
}
if spec2.Compiler != "" {
spec.Compiler = spec2.Compiler
}
if spec2.Linker != "" {
spec.Linker = spec2.Linker
}
if spec2.RTLib != "" {
spec.RTLib = spec2.RTLib
}
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
if spec2.LinkerScript != "" {
spec.LinkerScript = spec2.LinkerScript
}
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
if len(spec2.Emulator) != 0 {
spec.Emulator = spec2.Emulator
}
if spec2.FlashCommand != "" {
spec.FlashCommand = spec2.FlashCommand
}
if spec2.GDB != "" {
spec.GDB = spec2.GDB
}
if spec2.PortReset != "" {
spec.PortReset = spec2.PortReset
}
if spec2.FlashMethod != "" {
spec.FlashMethod = spec2.FlashMethod
}
if spec2.FlashVolume != "" {
spec.FlashVolume = spec2.FlashVolume
}
if spec2.FlashFilename != "" {
spec.FlashFilename = spec2.FlashFilename
}
if spec2.OpenOCDInterface != "" {
spec.OpenOCDInterface = spec2.OpenOCDInterface
}
if spec2.OpenOCDTarget != "" {
spec.OpenOCDTarget = spec2.OpenOCDTarget
}
if spec2.OpenOCDTransport != "" {
spec.OpenOCDTransport = spec2.OpenOCDTransport
}
return nil
}
// load reads a target specification from the JSON in the given io.Reader. It
@@ -118,10 +132,10 @@ func (spec *TargetSpec) load(r io.Reader) error {
}
// 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)
// - 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") {
@@ -151,212 +165,113 @@ func (spec *TargetSpec) resolveInherits() error {
if err != nil {
return err
}
err = newSpec.overrideProperties(subtarget)
if err != nil {
return err
}
newSpec.copyProperties(subtarget)
}
// When all properties are loaded, make sure they are properly inherited.
err := newSpec.overrideProperties(spec)
if err != nil {
return err
}
newSpec.copyProperties(spec)
*spec = *newSpec
return nil
}
// Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) {
if options.Target == "" {
func LoadTarget(target string) (*TargetSpec, error) {
if target == "" {
// Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
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
goos := goenv.Get("GOOS")
goarch := goenv.Get("GOARCH")
llvmos := goos
llvmarch := map[string]string{
"386": "i386",
"amd64": "x86_64",
"arm64": "aarch64",
}[goarch]
if llvmarch == "" {
llvmarch = 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.GOOS == "windows" {
target += "-gnu"
} else if options.GOARCH == "arm" {
target = llvmarch + "--" + llvmos
if goarch == "arm" {
target += "-gnueabihf"
}
return defaultTarget(options.GOOS, options.GOARCH, target)
return defaultTarget(goos, 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 {
err := spec.loadFromGivenStr(target)
if err == nil {
// Successfully loaded this target from a built-in .json file. Make sure
// it includes all parents as specified in the "inherits" key.
err = spec.resolveInherits()
if err != nil {
return nil, err
}
return spec, nil
} else if !os.IsNotExist(err) {
// Expected a 'file not found' error, got something else. Report it as
// an error.
return nil, err
} else {
// Load target from given triple, ignore GOOS/GOARCH environment
// variables.
tripleSplit := strings.Split(target, "-")
if len(tripleSplit) < 3 {
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
}
goos := tripleSplit[2]
if strings.HasPrefix(goos, "darwin") {
goos = "darwin"
}
goarch := map[string]string{ // map from LLVM arch to Go arch
"i386": "386",
"x86_64": "amd64",
"aarch64": "arm64",
}[tripleSplit[0]]
if goarch == "" {
goarch = tripleSplit[0]
}
return defaultTarget(goos, goarch, target)
}
// 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, fmt.Errorf("%s : %w", options.Target, 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"
spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
switch strings.Split(triple, "-")[0] {
case "armv5":
spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
case "armv6":
spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
case "armv7":
spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
}
case "arm64":
spec.CPU = "generic"
spec.Features = "+neon"
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
Compiler: "clang",
Linker: "cc",
GDB: "gdb",
PortReset: "false",
FlashMethod: "native",
}
if goos == "darwin" {
spec.Linker = "ld.lld"
spec.Libc = "darwin-libSystem"
arch := strings.Split(triple, "-")[0]
platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx")
spec.LDFlags = append(spec.LDFlags,
"-flavor", "darwin",
"-dead_strip",
"-arch", arch,
"-platform_version", "macos", platformVersion, platformVersion,
)
} 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"
// Note: using a medium code model, low image base and no ASLR
// because Go doesn't really need those features. ASLR patches
// around issues for unsafe languages like C/C++ that are not
// normally present in Go (without explicitly opting in).
// For more discussion:
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"-Bdynamic",
"--image-base", "0x400000",
"--gc-sections",
"--no-insert-timestamp",
"--no-dynamicbase",
)
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if goarch != "wasm" {
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/asm_"+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 goos == "linux" {
switch goarch {
case "386":
// amd64 can _usually_ run 32-bit programs, so skip the emulator in that case.
if runtime.GOARCH != "amd64" {
spec.Emulator = "qemu-i386 {}"
}
case "amd64":
spec.Emulator = "qemu-x86_64 {}"
case "arm":
spec.Emulator = "qemu-arm {}"
case "arm64":
spec.Emulator = "qemu-aarch64 {}"
}
if goarch == "arm" && goos == "linux" {
spec.Linker = "arm-linux-gnueabihf-gcc"
spec.GDB = "arm-linux-gnueabihf-gdb"
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
}
}
if goos != runtime.GOOS {
if goos == "windows" {
spec.Emulator = "wine {}"
if goarch == "arm64" && goos == "linux" {
spec.Linker = "aarch64-linux-gnu-gcc"
spec.GDB = "aarch64-linux-gnu-gdb"
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
}
if goarch == "386" {
spec.CFlags = []string{"-m32"}
spec.LDFlags = []string{"-m32"}
}
}
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, ", ") + ")")
}
+4 -68
View File
@@ -1,83 +1,19 @@
package compileopts
import (
"errors"
"io/fs"
"reflect"
"testing"
)
import "testing"
func TestLoadTarget(t *testing.T) {
_, err := LoadTarget(&Options{Target: "arduino"})
_, err := LoadTarget("arduino")
if err != nil {
t.Error("LoadTarget test failed:", err)
}
_, err = LoadTarget(&Options{Target: "notexist"})
_, err = LoadTarget("notexist")
if err == nil {
t.Error("LoadTarget should have failed with non existing target")
}
if !errors.Is(err, fs.ErrNotExist) {
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
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"},
DefaultStackSize: 42,
AutoStackSize: &baseAutoStackSize,
}
childAutoStackSize := false
child := &TargetSpec{
GOOS: "",
CPU: "chlidCpu",
CFlags: []string{"-child-foo", "-child-bar"},
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 *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)
}
}
-61
View File
@@ -1,61 +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/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric",
"crypto/ed25519/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric",
"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.archHypot": "math.hypot",
"math.archMax": "math.max",
"math.archMin": "math.min",
"math.archModf": "math.modf",
}
// 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 := b.ctx.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)
}
}
+101 -210
View File
@@ -4,45 +4,60 @@ package compiler
// required by the Go programming language.
import (
"fmt"
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"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
// cause a panic.
// The caller should make sure that index is at least as big as arrayLen.
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
if b.info.nobounds {
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
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
// correctly extend that type.
if indexType.Underlying().(*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 = b.CreateZExt(arrayLen, index.Type(), "")
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
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
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.
//
// 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
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
// logic is the same in both cases.
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if b.info.nobounds {
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
if frame.fn.IsNoBounds() {
// The //go:nobounds pragma was added to the function to avoid bounds
// checking.
return
@@ -60,216 +75,92 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
capacityType = max.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.
low = b.extendInteger(low, lowType, capacityType)
high = b.extendInteger(high, highType, capacityType)
max = b.extendInteger(max, maxType, capacityType)
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if lowType.Info()&types.IsUnsigned != 0 {
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, "")
}
}
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
if maxType.Info()&types.IsUnsigned != 0 {
max = c.builder.CreateZExt(max, capacityType, "")
} else {
max = c.builder.CreateSExt(max, capacityType, "")
}
}
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now do the bounds check: low > high || high > capacity
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.
c.builder.SetInsertPointAtEnd(faultBlock)
c.createRuntimeCall("slicePanic", nil, "")
c.builder.CreateUnreachable()
// Ok: this is a valid pointer.
c.builder.SetInsertPointAtEnd(nextBlock)
}
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
// conversions. This conversion was added in Go 1.17. For details, see:
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
// From the spec:
// > If the length of the slice is less than the length of the array, a
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// function must panic if the ptr/len parameters are invalid.
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
// From the documentation of unsafe.Slice:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
// not too big that a GEP wouldn't be possible without wrapping the pointer.
// These two checks (non-negative and not too big) can be merged into one
// using an unsiged greater than.
// Make sure the len value is at least as big as a uintptr.
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
// emitNilCheck 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.
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.
if !ptr.IsAGlobalValue().IsNil() {
return
}
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
}
}
// Check whether this is a nil pointer.
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Compare against nil.
// We previously used a hack to make sure this wouldn't break escape
// analysis, but this is not necessary anymore since
// https://reviews.llvm.org/D60047 has been merged.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
// Emit the nil check in IR.
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
}
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
// This function assumes that the shift value is signed.
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
if b.info.nobounds {
// Function disabled bounds checking - skip shift check.
return
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// 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, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// 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)
// 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
}
}
// Put the fault block at the end of the function and the next block at the
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(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 {
// Signed, so sign-extend to the target type.
value = b.CreateSExt(value, targetType, "")
}
}
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)
}
-92
View File
@@ -1,92 +0,0 @@
package compiler
import (
"fmt"
"strings"
"tinygo.org/x/go-llvm"
)
// createAtomicOp lowers a sync/atomic function by lowering it as an LLVM atomic
// operation. It returns the result of the operation, or a zero llvm.Value if
// the result is void.
func (b *builder) createAtomicOp(name string) llvm.Value {
switch name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1])
if strings.HasPrefix(b.Triple, "avr") {
// AtomicRMW does not work on AVR as intended:
// - There are some register allocation issues (fixed by https://reviews.llvm.org/D97127 which is not yet in a usable LLVM release)
// - The result is the new value instead of the old value
vType := val.Type()
name := fmt.Sprintf("__sync_fetch_and_add_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType}, false))
}
oldVal := b.createCall(fn, []llvm.Value{ptr, val}, "")
// Return the new value, not the original value returned.
return b.CreateAdd(oldVal, val, "")
}
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, "")
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1])
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
if isPointer {
// atomicrmw only supports integers, so cast to an integer.
// TODO: this is fixed in LLVM 15.
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
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(b.fn.Params[0])
old := b.getValue(b.fn.Params[1])
newVal := b.getValue(b.fn.Params[2])
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "")
return swapped
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
ptr := b.getValue(b.fn.Params[0])
val := b.CreateLoad(ptr, "")
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return val
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1])
if strings.HasPrefix(b.Triple, "avr") {
// SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores.
vType := val.Type()
isPointer := vType.TypeKind() == llvm.PointerTypeKind
if isPointer {
// libcalls only supports integers, so cast to an integer.
vType = b.uintptrType
val = b.CreatePtrToInt(val, vType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "")
}
name := fmt.Sprintf("__atomic_store_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType, b.uintptrType}, false))
}
b.createCall(fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, "")
return llvm.Value{}
}
store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return llvm.Value{}
default:
b.addError(b.fn.Pos(), "unknown atomic operation: "+b.fn.Name())
return llvm.Value{}
}
}
+69 -209
View File
@@ -1,9 +1,7 @@
package compiler
import (
"go/types"
"strconv"
"fmt"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
@@ -13,107 +11,62 @@ import (
// 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.
const maxFieldsPerParam = 3
const MaxFieldsPerParam = 3
// paramInfo contains some information collected about a function parameter,
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
flags paramFlags
}
// paramFlags identifies parameter attributes for flags. Most importantly, it
// determines which parameters are dereferenceable_or_null and which aren't.
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
)
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
// createRuntimeInvoke instead.
func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) 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))
// Shortcut: create a call to runtime.<fnName> with the given arguments.
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
runtimePkg := c.ir.Program.ImportedPackage("runtime")
member := runtimePkg.Members[fnName]
if member == nil {
panic("trying to call runtime." + fnName)
}
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
if isInvoke {
return b.createInvoke(llvmFn, args, name)
fn := c.ir.GetFunction(member.(*ssa.Function))
if fn.LLVMFn.IsNil() {
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
}
return b.createCall(llvmFn, args, name)
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)
}
// createRuntimeCall creates a new call to runtime.<fnName> with the given
// arguments.
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
return b.createRuntimeCallCommon(fnName, args, name, false)
}
// createRuntimeInvoke creates a new call to runtime.<fnName> with the given
// arguments. If the runtime call panics, control flow is diverted to the
// landing pad block.
// Note that "invoke" here is meant in the LLVM sense (a call that can
// panic/throw), not in the Go sense (an interface method call).
func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name string) llvm.Value {
return b.createRuntimeCallCommon(fnName, args, name, true)
}
// 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 {
// Create a call to the given function with the arguments possibly expanded.
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
expanded := make([]llvm.Value, 0, len(args))
for _, arg := range args {
fragments := b.expandFormalParam(arg)
fragments := c.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
return b.CreateCall(fn, expanded, name)
}
// createInvoke is like createCall but continues execution at the landing pad if
// the call resulted in a panic.
func (b *builder) createInvoke(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
if b.hasDeferFrame() {
b.createInvokeCheckpoint()
}
return b.createCall(fn, args, name)
return c.builder.CreateCall(fn, expanded, name)
}
// Expand an argument type to a list that can be used in a function call
// parameter 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() {
case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType)
if len(fieldInfos) <= maxFieldsPerParam {
// managed to expand this parameter
return fieldInfos
fields := c.flattenAggregateType(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
return []llvm.Type{t}
}
// failed to expand this parameter: too many fields
}
// TODO: split small arrays
return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
default:
// TODO: split small arrays
return []llvm.Type{t}
}
}
// 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 {
// Expand an argument type to a list of offsets from the start of the object.
// Used together with expandFormalParam to get the offset of each value from the
// start of the non-expanded value.
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
fields := b.flattenAggregateTypeOffsets(t)
if len(fields) <= maxFieldsPerParam {
fields := c.flattenAggregateTypeOffsets(t)
if len(fields) <= MaxFieldsPerParam {
return fields
} else {
// failed to lower
@@ -125,17 +78,14 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
}
}
// expandFormalParam splits a formal param value into pieces, so it can be
// passed directly as part of a function call. For example, it splits up small
// structs into individual fields. It is the equivalent of expandFormalParamType
// for parameter values.
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Equivalent of expandFormalParamType for parameter values.
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
if len(fieldInfos) <= maxFieldsPerParam {
fields := b.flattenAggregate(v)
if len(fields) != len(fieldInfos) {
fieldTypes := c.flattenAggregateType(v.Type())
if len(fieldTypes) <= MaxFieldsPerParam {
fields := c.flattenAggregate(v)
if len(fields) != len(fieldTypes) {
panic("type and value param lowering don't match")
}
return fields
@@ -151,108 +101,28 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible.
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
switch t.TypeKind() {
case llvm.StructTypeKind:
var paramInfos []paramInfo
for i, subfield := range t.StructElementTypes() {
if c.targetData.TypeAllocSize(subfield) == 0 {
continue
}
suffix := strconv.Itoa(i)
if goType != nil {
// Try to come up with a good suffix for this struct field,
// 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...)
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
for _, subfield := range t.StructElementTypes() {
subfields := c.flattenAggregateType(subfield)
fields = append(fields, subfields...)
}
return paramInfos
return fields
default:
return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
return []llvm.Type{t}
}
}
// 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 {
// Return the offsets from the start of the object if this object type were
// flattened like in flattenAggregate. Used together with flattenAggregate to
// know the start indices of each value in the non-flattened object.
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
switch t.TypeKind() {
case llvm.StructTypeKind:
var fields []uint64
fields := make([]uint64, 0, t.StructElementTypesCount())
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 {
@@ -266,18 +136,15 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
}
}
// flattenAggregate breaks down a struct into its elementary values for argument
// passing. It is the value equivalent of flattenAggregateType
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
// Break down a struct into its elementary types for argument passing. The value
// equivalent of flattenAggregateType
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
switch v.Type().TypeKind() {
case llvm.StructTypeKind:
var fields []llvm.Value
for i, field := range v.Type().StructElementTypes() {
if b.targetData.TypeAllocSize(field) == 0 {
continue
}
subfield := b.CreateExtractValue(v, i, "")
subfields := b.flattenAggregate(subfield)
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
for i := range v.Type().StructElementTypes() {
subfield := c.builder.CreateExtractValue(v, i, "")
subfields := c.flattenAggregate(subfield)
fields = append(fields, subfields...)
}
return fields
@@ -286,32 +153,25 @@ func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
}
}
// collapseFormalParam combines an aggregate object back into the original
// value. This is used to join multiple LLVM parameters into a single Go value
// in the function entry block.
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := b.collapseFormalParamInternal(t, fields)
// Collapse a list of fields into its original value.
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
param, remaining := c.collapseFormalParamInternal(t, fields)
if len(remaining) != 0 {
panic("failed to expand back all fields")
}
return param
}
// collapseFormalParamInternal is an implementation detail of
// collapseFormalParam: it works by recursing until there are no fields left.
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
// Returns (value, remainingFields). Used by collapseFormalParam.
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
switch t.TypeKind() {
case llvm.StructTypeKind:
flattened := b.flattenAggregateType(t, "", nil)
if len(flattened) <= maxFieldsPerParam {
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
value := llvm.ConstNull(t)
for i, subtyp := range t.StructElementTypes() {
if b.targetData.TypeAllocSize(subtyp) == 0 {
continue
}
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
fields = remaining
value = b.CreateInsertValue(value, structField, i, "")
value = c.builder.CreateInsertValue(value, structField, i, "")
}
return value, fields
} else {
+89 -123
View File
@@ -11,109 +11,73 @@ import (
"tinygo.org/x/go-llvm"
)
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
bufSize := b.getValue(expr.Size)
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
}
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
bufSize := c.getValue(frame, expr.Size)
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
}
// createChanSend emits a pseudo chan send operation. It is lowered to the
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X)
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
// channel send operation during goroutine lowering.
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
ch := c.getValue(frame, instr.Chan)
chanValue := c.getValue(frame, instr.X)
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
valueType := c.getLLVMType(instr.X.Type())
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
c.builder.CreateStore(chanValue, valueAlloca)
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
// End the lifetime of the allocas.
// End the lifetime of the alloca.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
c.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.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
ch := b.getValue(unop.X)
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
ch := c.getValue(frame, unop.X)
// Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate blockedlist buffer.
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
received := c.builder.CreateLoad(valueAlloca, "chan.received")
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
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
} else {
return received
}
}
// createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
// emitChanClose closes the given channel.
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
ch := c.getValue(frame, param)
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
}
// createSelect emits all IR necessary for a select statements. That's a
// emitSelect emits all IR necessary for a select statements. That's a
// non-trivial amount of code because select is very complex to implement.
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
if len(expr.States) == 0 {
// Shortcuts for some simple selects.
llvmType := b.getLLVMType(expr.Type())
llvmType := c.getLLVMType(expr.Type())
if expr.Blocking {
// Blocks forever:
// select {}
b.createRuntimeCall("deadlock", nil, "")
c.createRuntimeCall("deadlock", nil, "")
return llvm.Undef(llvmType)
} else {
// No-op:
@@ -121,7 +85,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// default:
// }
retval := llvm.Undef(llvmType)
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
return retval // {-1, false}
}
}
@@ -137,30 +101,32 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// determine the receive buffer size and alignment.
recvbufSize := uint64(0)
recvbufAlign := 0
hasReceives := false
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := b.getValue(state.Chan)
ch := c.getValue(frame, state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = b.CreateInsertValue(selectState, ch, 0, "")
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
case types.RecvOnly:
// Make sure the receive buffer is big enough and has the correct alignment.
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
recvbufSize = size
}
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
recvbufAlign = align
}
hasReceives = true
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
sendValue := 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, "")
sendValue := c.getValue(frame, state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
c.builder.CreateStore(sendValue, alloca)
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
@@ -168,74 +134,74 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbuf := llvm.Undef(c.i8ptrType)
if hasReceives {
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.recvbuf")
}
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
statesAlloca, statesI8, statesSize := c.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),
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
}, "")
b.CreateStore(state, gep)
c.builder.CreateStore(state, gep)
}
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.states")
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
// Do the select in the runtime.
var results llvm.Value
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(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),
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}, "select.block")
results = b.createRuntimeCall("chanSelect", []llvm.Value{
results = c.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)
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
}, "select.result")
}
// Terminate the lifetime of the states alloca.
b.emitLifetimeEnd(statesI8, statesSize)
c.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
// instruction will treat a *ssa.Select specially and load it there inline.
// Store the receive alloca in a sidetable until we hit this extract
// instruction.
if b.selectRecvBuf == nil {
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
if frame.selectRecvBuf == nil {
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
}
b.selectRecvBuf[expr] = recvbuf
frame.selectRecvBuf[expr] = recvbuf
return results
}
@@ -244,28 +210,28 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// 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 {
func (c *Compiler) getChanSelectResult(frame *Frame, 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, "")
value := c.getValue(frame, expr.Tuple)
index := c.builder.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
index = c.builder.CreateSExt(index, c.intType, "")
}
return index
} else if expr.Index == 1 {
// comma-ok
value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "")
value := c.getValue(frame, expr.Tuple)
return c.builder.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
// received values, depending on how many receive statements there
// are. They are all combined into one alloca (because only one
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
return c.builder.CreateLoad(ptr, "")
}
}
+20 -42
View File
@@ -1,8 +1,7 @@
// 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
package compiler
// This file implements a set of sanity checks for the IR that is generated.
// It can catch some mistakes that LLVM's verifier cannot.
import (
"errors"
@@ -11,11 +10,7 @@ import (
"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 {
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// prevent infinite recursion for self-referential types
if _, ok := checked[t]; ok {
return nil
@@ -31,7 +26,7 @@ func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, special
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.String(), t.Context(), c.ctx)
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
@@ -86,7 +81,7 @@ func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, special
return nil
}
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check type
if err := c.checkType(v.Type(), types, specials); err != nil {
return fmt.Errorf("failed to verify type of value: %s", err.Error())
@@ -100,30 +95,12 @@ func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, special
return nil
}
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
// check value properties
if err := c.checkValue(inst, types, specials); err != nil {
return errorAt(inst, err.Error())
}
// 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 {
@@ -134,7 +111,7 @@ func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}
return nil
}
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check basic block value and type
var errs []error
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
@@ -151,7 +128,7 @@ func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct
return errs
}
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
// check function value and type
var errs []error
if err := c.checkValue(fn, types, specials); err != nil {
@@ -166,25 +143,26 @@ func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, spe
return errs
}
// Module checks the given module and returns a slice of error, if there are
// any.
func Module(mod llvm.Module) []error {
func (c *Compiler) checkModule() []error {
// check for any context mismatches
var errs []error
c := checker{
ctx: mod.Context(),
}
if c.ctx == llvm.GlobalContext() {
switch {
case c.mod.Context() == c.ctx:
// this is correct
case c.mod.Context() == llvm.GlobalContext():
// somewhere we accidentally used the global context instead of a real context
errs = append(errs, errors.New("module uses global context"))
default:
// we used some other context by accident
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
}
types := map[llvm.Type]struct{}{}
specials := map[llvm.TypeKind]llvm.Type{}
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
for fn := c.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) {
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
if err := c.checkValue(g, types, specials); err != nil {
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
}
+1370 -1737
View File
File diff suppressed because it is too large Load Diff
-206
View File
@@ -1,206 +0,0 @@
package compiler
import (
"flag"
"go/types"
"os"
"strconv"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
// Pass -update to go test to update the output of the test files.
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
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()
// Determine which tests to run, depending on the Go and LLVM versions.
tests := []testCase{
{"basic.go", "", ""},
{"pointer.go", "", ""},
{"slice.go", "", ""},
{"string.go", "", ""},
{"float.go", "", ""},
{"interface.go", "", ""},
{"func.go", "", ""},
{"defer.go", "cortex-m-qemu", ""},
{"pragma.go", "", ""},
{"goroutine.go", "wasm", "asyncify"},
{"goroutine.go", "cortex-m-qemu", "tasks"},
{"channel.go", "", ""},
{"gc.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(),
Features: config.Features(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
NeedsStackObjects: config.NeedsStackObjects(),
}
machine, err := NewTargetMachine(compilerConfig)
if err != nil {
t.Fatal("failed to create target machine:", err)
}
defer machine.Dispose()
// Load entire program AST into memory.
lprogram, err := loader.Load(config, "./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 := os.WriteFile(outPath, []byte(mod.String()), 0666)
if err != nil {
t.Error("failed to write updated output file:", err)
}
return
}
expected, err := os.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
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
if err != nil {
// Note: this should never happen and if it does, it will always happen
// for a particular build because llvm.Version is a constant.
panic(err)
}
for _, line := range lines {
line = strings.Split(line, ";")[0] // strip out comments/info
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
if line == "" {
continue
}
if strings.HasPrefix(line, "source_filename = ") {
continue
}
if llvmVersion < 14 && strings.HasPrefix(line, "target datalayout = ") {
// The datalayout string may vary betewen LLVM versions.
// Right now test outputs are for LLVM 14 and higher.
continue
}
out = append(out, line)
}
return out
}
+132 -400
View File
@@ -14,200 +14,25 @@ package compiler
// frames.
import (
"go/types"
"strconv"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// supportsRecover returns whether the compiler supports the recover() builtin
// for the current architecture.
func (b *builder) supportsRecover() bool {
switch b.archFamily() {
case "wasm32":
// Probably needs to be implemented using the exception handling
// proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling
return false
case "riscv64", "xtensa":
// TODO: add support for these architectures
return false
default:
return true
}
}
// hasDeferFrame returns whether the current function needs to catch panics and
// run defers.
func (b *builder) hasDeferFrame() bool {
if b.fn.Recover == nil {
return false
}
return b.supportsRecover()
}
// deferInitFunc sets up this function for future deferred calls. It must be
// called from within the entry block when this function contains deferred
// calls.
func (b *builder) deferInitFunc() {
func (c *Compiler) deferInitFunc(frame *Frame) {
// Some setup.
b.deferFuncs = make(map[*ssa.Function]int)
b.deferInvokeFuncs = make(map[string]int)
b.deferClosureFuncs = make(map[*ssa.Function]int)
b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
frame.deferFuncs = make(map[*ir.Function]int)
frame.deferInvokeFuncs = make(map[string]int)
frame.deferClosureFuncs = make(map[*ir.Function]int)
// Create defer list pointer.
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer.
// This assumes that the stack pointer doesn't move outside of the
// function prologue/epilogue (an invariant maintained by TinyGo but
// possibly broken by the C alloca function).
// The frame pointer is _not_ saved, because it is marked as clobbered
// in the setjmp-like inline assembly.
deferFrameType := b.getLLVMRuntimeType("deferFrame")
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
stackPointer := b.readStackPointer()
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
// Create the landing pad block, which is where control transfers after
// a panic.
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
}
}
// createLandingPad fills in the landing pad block. This block runs the deferred
// functions and returns (by jumping to the recover block). If the function is
// still panicking after the defers are run, the panic will be re-raised in
// destroyDeferFrame.
func (b *builder) createLandingPad() {
b.SetInsertPointAtEnd(b.landingpad)
// Add debug info, if needed.
// The location used is the closing bracket of the function.
if b.Debug {
pos := b.program.Fset.Position(b.fn.Syntax().End())
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
}
b.createRunDefers()
// Continue at the 'recover' block, which returns to the parent in an
// appropriate way.
b.CreateBr(b.blockEntries[b.fn.Recover])
}
// createInvokeCheckpoint saves the function state at the given point, to
// continue at the landing pad if a panic happened. This is implemented using a
// setjmp-like construct.
func (b *builder) createInvokeCheckpoint() {
// Construct inline assembly equivalents of setjmp.
// The assembly works as follows:
// * All registers (both callee-saved and caller saved) are clobbered
// after the inline assembly returns.
// * The assembly stores the address just past the end of the assembly
// into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline
// assembly but set to an unspecified non-zero value when jumping using
// a longjmp.
var asmString, constraints string
resultType := b.uintptrType
switch b.archFamily() {
case "i386":
asmString = `
xorl %eax, %eax
movl $$1f, 4(%ebx)
1:`
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions.
case "x86_64":
asmString = `
leaq 1f(%rip), %rax
movq %rax, 8(%rbx)
xorq %rax, %rax
1:`
constraints = "={rax},{rbx},~{rbx},~{rcx},~{rdx},~{rsi},~{rdi},~{rbp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{xmm8},~{xmm9},~{xmm10},~{xmm11},~{xmm12},~{xmm13},~{xmm14},~{xmm15},~{xmm16},~{xmm17},~{xmm18},~{xmm19},~{xmm20},~{xmm21},~{xmm22},~{xmm23},~{xmm24},~{xmm25},~{xmm26},~{xmm27},~{xmm28},~{xmm29},~{xmm30},~{xmm31},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This list doesn't include AVX/AVX512 registers because TinyGo
// doesn't currently enable support for AVX instructions.
case "arm":
// Note: the following assembly takes into account that the PC is
// always 4 bytes ahead on ARM. The PC that is stored always points
// to the instruction just after the assembly fragment so that
// tinygo_longjmp lands at the correct instruction.
if b.isThumb() {
// Instructions are 2 bytes in size.
asmString = `
movs r0, #0
mov r2, pc
str r2, [r1, #4]`
} else {
// Instructions are 4 bytes in size.
asmString = `
str pc, [r1, #4]
movs r0, #0`
}
constraints = "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"
case "aarch64":
asmString = `
adr x2, 1f
str x2, [x1, #8]
mov x0, #0
1:
`
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}"
if b.GOOS != "darwin" {
// These registers cause the following warning when compiling for
// MacOS:
// warning: inline asm clobber list contains reserved registers:
// X18, FP
// Reserved registers on the clobber list may not be preserved
// across the asm statement, and clobbering them may lead to
// undefined behaviour.
constraints += ",~{x18},~{fp}"
}
// TODO: SVE registers, which we don't use in TinyGo at the moment.
case "avr":
// Note: the Y register (R28:R29) is a fixed register and therefore
// needs to be saved manually. TODO: do this only once per function with
// a defer frame, not for every call.
resultType = b.ctx.Int8Type()
asmString = `
ldi r24, pm_lo8(1f)
ldi r25, pm_hi8(1f)
std z+2, r24
std z+3, r25
std z+4, r28
std z+5, r29
ldi r24, 0
1:`
constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}"
case "riscv32":
asmString = `
la a2, 1f
sw a2, 4(a1)
li a0, 0
1:`
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
default:
// This case should have been handled by b.supportsRecover().
b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily())
}
asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false)
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
result := b.CreateCall(asm, []llvm.Value{b.deferFrame}, "setjmp")
result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0))
isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result")
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
b.blockExits[b.currentBlock] = continueBB
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
}
// isInLoop checks if there is a path from a basic block to itself.
@@ -244,52 +69,53 @@ func isInLoop(start *ssa.BasicBlock) bool {
return false
}
// createDefer emits a single defer instruction, to be run when this function
// emitDefer emits a single defer instruction, to be run when this function
// 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
// make a linked list.
next := b.CreateLoad(b.deferPtr, "defer.next")
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
methodName := instr.Call.Method.FullName()
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
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
// runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
itf := c.getValue(frame, instr.Call.Value) // interface
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, receiverValue}
valueTypes = append(valueTypes, c.i8ptrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg)
val := c.getValue(frame, arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
if _, ok := b.deferFuncs[callee]; !ok {
b.deferFuncs[callee] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, callee)
fn := c.ir.GetFunction(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
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -301,111 +127,65 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// pointer.
// TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation.
closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "")
closure := c.getValue(frame, instr.Call.Value)
context := c.builder.CreateExtractValue(closure, 0, "")
// Get the callback number.
fn := makeClosure.Fn.(*ssa.Function)
if _, ok := b.deferClosureFuncs[fn]; !ok {
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
if _, ok := frame.deferClosureFuncs[fn]; !ok {
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
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
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param)
llvmParam := c.getValue(frame, param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
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 {
funcValue := b.getValue(instr.Call.Value)
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())
}
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
return
}
// Make a struct out of the collected values to put in the deferred call
// struct.
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCall := llvm.ConstNull(deferredCallType)
// Make a struct out of the collected values to put in the defer frame.
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFrame := llvm.ConstNull(deferFrameType)
for i, value := range values {
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
}
// Put this struct in an allocation.
var alloca llvm.Value
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
} else {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType)
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(deferredCallType, 0), "defer.alloc")
size := c.targetData.TypeAllocSize(deferFrameType)
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
if c.NeedsStackObjects() {
c.trackPointer(alloca)
}
b.CreateStore(deferredCall, alloca)
c.builder.CreateStore(deferFrame, alloca)
// Push it on top of the linked list by replacing deferPtr.
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
c.builder.CreateStore(allocaCast, frame.deferPtr)
}
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
// emitRunDefers emits code to run all deferred functions.
func (c *Compiler) emitRunDefers(frame *Frame) {
// Add a loop like the following:
// for stack != nil {
// _stack := stack
@@ -421,200 +201,152 @@ func (b *builder) createRunDefers() {
// }
// }
// Create loop, in the order: loophead, loop, callback0, callback1, ..., unreachable, end.
end := b.insertBasicBlock("rundefers.end")
unreachable := b.ctx.InsertBasicBlock(end, "rundefers.default")
loop := b.ctx.InsertBasicBlock(unreachable, "rundefers.loop")
loophead := b.ctx.InsertBasicBlock(loop, "rundefers.loophead")
b.CreateBr(loophead)
// Create loop.
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
c.builder.CreateBr(loophead)
// Create loop head:
// for stack != nil {
b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop)
c.builder.SetInsertPointAtEnd(loophead)
deferData := c.builder.CreateLoad(frame.deferPtr, "")
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
c.builder.CreateCondBr(stackIsNil, end, loop)
// Create loop body:
// _stack := stack
// stack = stack.next
// switch stack.callback {
b.SetInsertPointAtEnd(loop)
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
c.builder.SetInsertPointAtEnd(loop)
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
c.builder.CreateStore(nextStack, frame.deferPtr)
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep")
callback := b.CreateLoad(gep, "callback")
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
callback := c.builder.CreateLoad(gep, "callback")
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:
// case 0:
// // run first deferred call
block := b.insertBasicBlock("rundefers.callback" + strconv.Itoa(i))
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
b.SetInsertPointAtEnd(block)
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
c.builder.SetInsertPointAtEnd(block)
switch callback := callback.(type) {
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.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() {
//Expect funcValue to be passed through the deferred call.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else {
//Expect typecode
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
}
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct (including receiver).
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++ {
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
var fnPtr llvm.Value
// Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
if !callback.IsInvoke() {
// Isolate the func value.
funcValue := forwardParams[0]
forwardParams = forwardParams[1:]
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
//Get function pointer and context
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
fnPtr = fp
fnPtr, _ := c.getInvokeCall(frame, callback)
c.createCall(fnPtr, forwardParams, "")
//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
// closure but we have to supply the parameter anyway for platforms
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
b.createCall(fnPtr, forwardParams, "")
case *ssa.Function:
case *ir.Function:
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
for _, param := range callback.Params {
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(gep, "param")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
for i := range callback.Params {
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := c.builder.CreateLoad(gep, "param")
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
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call real function.
b.createInvoke(b.getFunction(callback), forwardParams, "")
c.createCall(callback.LLVMFn, forwardParams, "")
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
valueTypes = append(valueTypes, c.i8ptrType) // closure
deferFrameType := c.ctx.StructType(valueTypes, false)
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
// Extract the params from the struct.
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++ {
gep := b.CreateInBoundsGEP(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(gep, "param")
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := c.builder.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Parent coroutine handle.
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
// Call deferred function.
b.createCall(b.getFunction(fn), forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
c.createCall(fn.LLVMFn, forwardParams, "")
//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()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// 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(deferredCallPtr, []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:
panic("unknown deferred function type")
}
// Branch back to the start of the loop.
b.CreateBr(loophead)
c.builder.CreateBr(loophead)
}
// Create default unreachable block:
// default:
// unreachable
// }
b.SetInsertPointAtEnd(unreachable)
b.CreateUnreachable()
c.builder.SetInsertPointAtEnd(unreachable)
c.builder.CreateUnreachable()
// End of loop.
b.SetInsertPointAtEnd(end)
c.builder.SetInsertPointAtEnd(end)
}
+14 -7
View File
@@ -1,8 +1,7 @@
package compiler
// This file contains some utility functions related to error handling.
import (
"go/scanner"
"go/token"
"go/types"
"path/filepath"
@@ -10,20 +9,28 @@ import (
"tinygo.org/x/go-llvm"
)
// makeError makes it easy to create an error from a token.Pos with a message.
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
return types.Error{
Fset: c.program.Fset,
Fset: c.ir.Program.Fset,
Pos: pos,
Msg: msg,
}
}
// addError adds a new compiler diagnostic with the given location and message.
func (c *compilerContext) addError(pos token.Pos, msg string) {
func (c *Compiler) addError(pos token.Pos, msg string) {
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
}
// errorAt returns an error value at the location of the instruction.
// The location information may not be complete as it depends on debug
// information in the IR.
func errorAt(inst llvm.Value, msg string) scanner.Error {
return scanner.Error{
Pos: getPosition(inst),
Msg: msg,
}
}
// getPosition returns the position information for the given value, as far as
// it is available.
func getPosition(val llvm.Value) token.Position {
+94 -48
View File
@@ -10,70 +10,119 @@ import (
"tinygo.org/x/go-llvm"
)
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
type funcValueImplementation int
const (
funcValueNone funcValueImplementation = iota
// A func value is implemented as a pair of pointers:
// {context, function pointer}
// where the context may be a pointer to a heap-allocated struct containing
// the free variables, or it may be undef if the function being pointed to
// doesn't need a context. The function pointer is a regular function
// pointer.
funcValueDoubleword
// As funcValueDoubleword, but with the function pointer replaced by a
// unique ID per function signature. Function values are called by using a
// switch statement and choosing which function to call.
funcValueSwitch
)
// funcImplementation picks an appropriate func value implementation for the
// target.
func (c *Compiler) funcImplementation() funcValueImplementation {
// Always pick the switch implementation, as it allows the use of blocking
// inside a function that is used as a func value.
switch c.Scheduler() {
case "coroutines":
return funcValueSwitch
case "tasks":
return funcValueDoubleword
default:
panic("unknown scheduler type")
}
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
// Closure is: {context, function pointer}
funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
var funcValueScalar llvm.Value
switch c.funcImplementation() {
case funcValueDoubleword:
// Closure is: {context, function pointer}
funcValueScalar = funcPtr
case funcValueSwitch:
sigGlobal := c.getTypeCode(sig)
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
if funcValueWithSignatureGlobal.IsNil() {
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
sigGlobal,
})
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
funcValueWithSignatureGlobal.SetGlobalConstant(true)
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
}
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
default:
panic("unimplemented func value variant")
}
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue
}
// getFuncSignatureID returns a new external global for a given signature. This
// global reference is not real, it is only used during func lowering to assign
// signature types to functions and will then be removed.
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
if sigGlobal.IsNil() {
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
sigGlobal.SetGlobalConstant(true)
}
return sigGlobal
}
// extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 1, "")
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 1, "")
}
// extractFuncContext extracts the context pointer from this function value. It
// is a cheap operation.
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
return b.CreateExtractValue(funcValue, 0, "")
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
return c.builder.CreateExtractValue(funcValue, 0, "")
}
// decodeFuncValue extracts the context and the function pointer from this func
// value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
bitcast := b.CreateExtractValue(funcValue, 1, "")
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
funcPtr = bitcast.Operand(0)
return
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = c.builder.CreateExtractValue(funcValue, 0, "")
switch c.funcImplementation() {
case funcValueDoubleword:
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
case funcValueSwitch:
llvmSig := c.getRawFuncType(sig)
sigGlobal := c.getTypeCode(sig)
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
default:
panic("unimplemented func value variant")
}
llvmSig := b.getRawFuncType(sig)
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
return
}
// getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
switch c.funcImplementation() {
case funcValueDoubleword:
rawPtr := c.getRawFuncType(typ)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
case funcValueSwitch:
return c.getLLVMRuntimeType("funcValue")
default:
panic("unimplemented func value variant")
}
}
// 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.
var returnType llvm.Type
switch typ.Results().Len() {
@@ -103,18 +152,15 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// The receiver is not an interface, but a i8* type.
recv = c.i8ptrType
}
for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
}
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.i8ptrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
// Make a func type out of the signature.
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
@@ -122,24 +168,24 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// parseMakeClosure makes a function value (with context) from the given
// 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 {
panic("unexpected: MakeClosure without bound variables")
}
f := expr.Fn.(*ssa.Function)
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
// Collect all bound variables.
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := b.getValue(binding)
llvmBoundVar := c.getValue(frame, binding)
boundVars[i] = llvmBoundVar
}
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := b.emitPointerPack(boundVars)
context := c.emitPointerPack(boundVars)
// Create the closure.
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
}
+17 -23
View File
@@ -12,59 +12,53 @@ import (
// 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) {
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
// There are uses of this expression, Make sure the pointers
// are tracked during GC.
switch expr := expr.(type) {
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
// These values are always of pointer type in IR.
b.trackPointer(value)
c.trackPointer(value)
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
if !value.IsNil() {
b.trackValue(value)
c.trackValue(value)
}
case *ssa.Select:
if alloca, ok := b.selectRecvBuf[expr]; ok {
if alloca, ok := frame.selectRecvBuf[expr]; ok {
if alloca.IsAUndefValue().IsNil() {
b.trackPointer(alloca)
c.trackPointer(alloca)
}
}
case *ssa.UnOp:
switch expr.Op {
case token.MUL:
// Pointer dereference.
b.trackValue(value)
c.trackValue(value)
case token.ARROW:
// Channel receive operator.
// It's not necessary to look at commaOk here, because in that
// case it's just an aggregate and trackValue will extract the
// pointer in there (if there is one).
b.trackValue(value)
}
case *ssa.BinOp:
switch expr.Op {
case token.ADD:
// String concatenation.
b.trackValue(value)
c.trackValue(value)
}
}
}
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
// individual pointers
func (b *builder) trackValue(value llvm.Value) {
func (c *Compiler) trackValue(value llvm.Value) {
typ := value.Type()
switch typ.TypeKind() {
case llvm.PointerTypeKind:
b.trackPointer(value)
c.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)
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
case llvm.ArrayTypeKind:
if !typeHasPointers(typ) {
@@ -72,19 +66,19 @@ func (b *builder) trackValue(value llvm.Value) {
}
numElements := typ.ArrayLength()
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
subValue := c.builder.CreateExtractValue(value, i, "")
c.trackValue(subValue)
}
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
func (c *Compiler) trackPointer(value llvm.Value) {
if value.Type() != c.i8ptrType {
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
}
// typeHasPointers returns whether this type is a pointer or contains pointers.
File diff suppressed because it is too large Load Diff
+62 -217
View File
@@ -3,212 +3,85 @@ 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"
import "tinygo.org/x/go-llvm"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// emitStartGoroutine starts a new goroutine with the provided function pointer
// and parameters.
// In general, you should pass all regular parameters plus the context parameter.
// There is one exception: the task-based scheduler needs to have the function
// pointer passed in as a parameter too in addition to the context.
//
// Because a go statement doesn't return anything, return undef.
func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
switch c.Scheduler() {
case "tasks":
paramBundle := c.emitPointerPack(params)
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
// 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))
calleeValue := c.createGoroutineStartWrapper(funcPtr)
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
case "coroutines":
// We roundtrip through runtime.makeGoroutine as a signal (to find these
// calls) and to break any optimizations LLVM will try to do: they are
// invalid if we called this as a regular function to be updated later.
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
default:
panic("unreachable")
}
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.
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, funcPtr)
hasContext = true
prefix = b.fn.RelString(nil)
}
paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value
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)}, "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)
}
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "")
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
}
// createGoroutineStartWrapper creates a wrapper for the task-based
// implementation of goroutines. For example, to call a function like this:
//
// func add(x, y int) int { ... }
// 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)
// }
// 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 {
func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) 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)
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetLinkage(llvm.PrivateLinkage)
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{})
}
c.builder.SetInsertPointAtEnd(entry)
// Create the list of params for the call.
paramTypes := fn.Type().ElementType().ParamTypes()
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 := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
params = append(params, llvm.Undef(c.i8ptrType))
// Create the call.
builder.CreateCall(fn, params, "")
if c.Scheduler == "asyncify" {
builder.CreateCall(deadlock, []llvm.Value{
llvm.Undef(c.i8ptrType),
}, "")
}
c.builder.CreateCall(fn, params, "")
} else {
// For a function pointer like this:
@@ -228,66 +101,38 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
// With a bit of luck, identical wrapper functions like these can be
// merged into one.
// Save the current position in the IR builder.
currentBlock := c.builder.GetInsertBlock()
defer c.builder.SetInsertPointAtEnd(currentBlock)
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
wrapper.SetLinkage(llvm.InternalLinkage)
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{})
}
c.builder.SetInsertPointAtEnd(entry)
// Get the list of parameters, with the extra parameters at the end.
paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
// Get the function pointer.
fnPtr := params[len(params)-1]
params = params[:len(params)-1]
// Ignore the last param, which isn't used anymore.
// TODO: avoid this extra "parent handle" parameter in most functions.
params[len(params)-1] = llvm.Undef(c.i8ptrType)
// Create the call.
builder.CreateCall(fnPtr, params, "")
if c.Scheduler == "asyncify" {
builder.CreateCall(deadlock, []llvm.Value{
llvm.Undef(c.i8ptrType),
}, "")
}
c.builder.CreateCall(fnPtr, params, "")
}
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()
}
// Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void.
c.builder.CreateRetVoid()
// Return a ptrtoint of the wrapper, not the function itself.
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+68 -154
View File
@@ -13,56 +13,77 @@ import (
"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(name string, args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
regname := constant.StringVal(args[0].(*ssa.Const).Value)
var asm string
switch name {
case "device/arm.ReadRegister":
asm = "mov $0, " + regname
case "device/riscv.ReadRegister":
asm = "mv $0, " + regname
default:
panic("unknown architecture")
}
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// 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
// ARM or sleep in AVR.
//
// func Asm(asm string)
// func Asm(asm string)
//
// 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.
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)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false)
return b.CreateCall(target, nil, ""), nil
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
return c.builder.CreateCall(target, nil, ""), nil
}
// This is a compiler builtin, which allows assembly to be called in a flexible
// 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
// provided immediately. For example:
//
// arm.AsmFull(
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
// arm.AsmFull(
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1
// "result": &dest,
// })
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
registers := map[string]llvm.Value{}
if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.Block() {
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
}
key := constant.StringVal(r.Key.(*ssa.Const).Value)
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
registerMap := instr.Args[1].(*ssa.MakeMap)
for _, r := range *registerMap.Referrers() {
switch r := r.(type) {
case *ssa.DebugRef:
// ignore
case *ssa.MapUpdate:
if r.Block() != registerMap.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)
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
}
default:
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
@@ -71,22 +92,13 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
argTypes := []llvm.Type{}
args := []llvm.Value{}
constraints := []string{}
hasOutput := false
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 {
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
// TODO: skip strings like {r4} etc. that look like ARM push/pop
// instructions.
name := s[1 : len(s)-1]
if _, ok := registers[name]; !ok {
if err == nil {
err = b.makeError(instr.Pos(), "unknown register name: "+name)
err = c.makeError(instr.Pos(), "unknown register name: "+name)
}
return s
}
@@ -98,12 +110,9 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
case llvm.IntegerTypeKind:
constraints = append(constraints, "r")
case llvm.PointerTypeKind:
// Memory references require a type in LLVM 14, probably as a
// preparation for opaque pointers.
err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23")
return s
constraints = append(constraints, "*m")
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
}
}
@@ -112,35 +121,23 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
if err != nil {
return llvm.Value{}, err
}
var outputType llvm.Type
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, false)
result := b.CreateCall(target, args, "")
if hasOutput {
return result, nil
} else {
// Make sure we return something valid.
return llvm.ConstInt(b.uintptrType, 0, false), nil
}
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
return c.builder.CreateCall(target, args, ""), 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
// 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.
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)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -153,7 +150,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg)
llvmValue := c.getValue(frame, arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -161,90 +158,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
// marked as clobbered.
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(target, llvmArgs, ""), nil
}
// This is a compiler builtin which emits an inline SVCall instruction. It can
// be one of:
//
// func SVCall0(num uintptr) uintptr
// func SVCall1(num uintptr, a1 interface{}) uintptr
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
// Same as emitSVCall but for AArch64
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10)
constraints := "={x0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints += ",0"
} else {
constraints += ",{x" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
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, false)
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, false)
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, false)
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, false)
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
return c.builder.CreateCall(target, llvmArgs, ""), nil
}
+196 -339
View File
@@ -11,39 +11,40 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/ir"
"golang.org/x/tools/go/ssa"
"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 will do an allocation of the right size and put that in the interface
// value field.
//
// An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := b.getTypeCode(typ)
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "")
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := c.emitPointerPack([]llvm.Value{val})
itfTypeCodeGlobal := c.getTypeCode(typ)
itfMethodSetGlobal := c.getTypeMethodSet(typ)
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.getLLVMRuntimeType("typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf
}
// extractValueFromInterface extract the value from an interface value
// (runtime._interface) under the assumption that it is of the type given in
// llvmType. The behavior is undefied if the interface is nil or llvmType
// doesn't match the underlying type of the interface.
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
}
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// 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)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
@@ -54,9 +55,6 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// reflect lowering simpler.
var references llvm.Value
var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
var typeAssert llvm.Value
switch typ := typ.(type) {
case *types.Named:
references = c.getTypeCode(typ.Underlying())
@@ -73,38 +71,18 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstBitCast(structGlobal, global.Type())
case *types.Interface:
methodSetGlobal := c.getInterfaceMethodSet(typ)
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
}
if _, ok := typ.Underlying().(*types.Interface); !ok {
methodSet = c.getTypeMethodSet(typ)
} 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() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := llvm.ConstNull(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
if !typeAssert.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
}
return global
@@ -113,7 +91,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// 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 {
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
@@ -126,8 +104,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
@@ -135,8 +113,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
@@ -152,27 +130,6 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
return structGlobal
}
var basicTypes = [...]string{
types.Bool: "bool",
types.Int: "int",
types.Int8: "int8",
types.Int16: "int16",
types.Int32: "int32",
types.Int64: "int64",
types.Uint: "uint",
types.Uint8: "uint8",
types.Uint16: "uint16",
types.Uint32: "uint32",
types.Uint64: "uint64",
types.Uintptr: "uintptr",
types.Float32: "float32",
types.Float64: "float64",
types.Complex64: "complex64",
types.Complex128: "complex128",
types.String: "string",
types.UnsafePointer: "unsafe.Pointer",
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
@@ -183,17 +140,54 @@ func getTypeCodeName(t types.Type) string {
case *types.Array:
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
return "basic:" + basicTypes[t.Kind()]
var kind string
switch t.Kind() {
case types.Bool:
kind = "bool"
case types.Int:
kind = "int"
case types.Int8:
kind = "int8"
case types.Int16:
kind = "int16"
case types.Int32:
kind = "int32"
case types.Int64:
kind = "int64"
case types.Uint:
kind = "uint"
case types.Uint8:
kind = "uint8"
case types.Uint16:
kind = "uint16"
case types.Uint32:
kind = "uint32"
case types.Uint64:
kind = "uint64"
case types.Uintptr:
kind = "uintptr"
case types.Float32:
kind = "float32"
case types.Float64:
kind = "float64"
case types.Complex64:
kind = "complex64"
case types.Complex128:
kind = "complex128"
case types.String:
kind = "string"
case types.UnsafePointer:
kind = "unsafeptr"
default:
panic("unknown basic type: " + t.Name())
}
return "basic:" + kind
case *types.Chan:
return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ {
name := t.Method(i).Name()
if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name
}
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
methods[i] = getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
@@ -234,7 +228,7 @@ func getTypeCodeName(t types.Type) string {
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// 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 {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
@@ -242,7 +236,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
ms := c.program.MethodSets.MethodSet(typ)
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
@@ -253,16 +247,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
panic("cannot find function: " + f.LinkName())
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
fn := c.getInterfaceInvokeWrapper(f)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(wrapper, c.uintptrType),
llvm.ConstPtrToInt(fn, c.uintptrType),
})
methods[i] = methodInfo
}
@@ -271,20 +264,15 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
}
global := c.mod.NamedGlobal(name + "$interface")
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
@@ -299,82 +287,57 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetLinkage(llvm.PrivateLinkage)
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
// external *i8 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
globalName := c.getMethodSignatureName(method)
signatureGlobal := c.mod.NamedGlobal(globalName)
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
// TODO: put something useful in these globals, such as the method
// signature. Useful to one day implement reflect.Value.Method(n).
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
signatureGlobal.SetGlobalConstant(true)
signatureGlobal.SetAlignment(1)
}
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
// 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.
//
// Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function.
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
itf := b.getValue(expr.X)
assertedType := b.getLLVMType(expr.AssertedType)
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
itf := c.getValue(frame, expr.X)
assertedType := c.getLLVMType(expr.AssertedType)
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{}
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type.
// This is a call to an interface type assert function.
// The interface lowering pass will define this function by filling it
// with a type switch over all concrete types that implement this
// interface, and returning whether it's one of the matched types.
// This pseudo call will be lowered in the interface lowering pass to a
// real call which checks whether the provided typecode is any of the
// concrete types that implements this interface.
// This is very different from how interface asserts are implemented in
// the main Go compiler, where the runtime checks whether the type
// implements each method of the interface. See:
// https://research.swtch.com/interfaces
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "")
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else {
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
assertedTypeCodeGlobal.SetGlobalConstant(true)
}
// Type assert on concrete type.
// Call runtime.typeAssert, which will be lowered to a simple icmp or
// const false in the interface lowering pass.
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
@@ -388,15 +351,15 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
// typeassert should return a zero value, not an incorrectly casted
// value.
prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next")
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
prevBlock := c.builder.GetInsertBlock()
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
// successful.
b.SetInsertPointAtEnd(okBlock)
c.builder.SetInsertPointAtEnd(okBlock)
var valueOk llvm.Value
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. Easy: just return the same
@@ -405,83 +368,75 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
} else {
// Type assert on concrete type. Extract the underlying type from
// the interface (but only after checking it matches).
valueOk = b.extractValueFromInterface(itf, assertedType)
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
}
b.CreateBr(nextBlock)
c.builder.CreateBr(nextBlock)
// Continue after the if statement.
b.SetInsertPointAtEnd(nextBlock)
phi := b.CreatePHI(assertedType, "typeassert.value")
c.builder.SetInsertPointAtEnd(nextBlock)
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple
} else {
// This is kind of dirty as the branch above becomes mostly useless,
// but hopefully this gets optimized away.
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return phi
}
}
// getMethodsString returns a string to be used in the "tinygo-methods" string
// attribute for interface functions.
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
methods := make([]string, itf.NumMethods())
for i := range methods {
methods[i] = c.getMethodSignatureName(itf.Method(i))
// getInvokeCall creates and returns the function pointer and parameters of an
// interface call. It can be used in a call or defer instruction.
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
// Call an interface method with dynamic dispatch.
itf := c.getValue(frame, instr.Value) // interface
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
// switch. The interface lowering pass will define this function.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
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
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
// createInterfaceInvokeWrapper. The former is called during IR construction
// itself and the latter is called when finishing up the IR.
type interfaceInvokeWrapper struct {
fn *ir.Function
wrapper llvm.Value
receiverType llvm.Type
}
// getInvokeFunction returns the thunk to call the given interface method. The
// thunk is declared, not defined: it will be defined by the interface lowering
// pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
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"
// Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created.
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
@@ -489,11 +444,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
}
// Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
var expandedReceiverType []llvm.Type
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
receiverType := c.getLLVMType(f.Params[0].Type())
expandedReceiverType := c.expandFormalParamType(receiverType)
// Does this method even need any wrapping?
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
@@ -501,147 +453,52 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
// Casting a function signature to a different signature and calling it
// with a receiver pointer bitcasted to *i8 (as done in calls on an
// interface) is hopefully a safe (defined) operation.
return llvmFn
return f.LLVMFn
}
// create wrapper function
fnType := llvmFn.Type().ElementType()
fnType := f.LLVMFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
// Create a new builder just to create this wrapper.
b := builder{
compilerContext: c,
Builder: c.ctx.NewBuilder(),
if f.LLVMFn.LastParam().Name() == "parentHandle" {
wrapper.LastParam().SetName("parentHandle")
}
defer b.Builder.Dispose()
// add debug info if needed
if c.Debug {
pos := c.program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
// set up IR builder
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
} else {
ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret)
}
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
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))
}
// 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)
// 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 += typestring(sig.Params().At(i).Type())
}
s += ")"
// add debug info if needed
if c.Debug() {
pos := c.ir.Program.Fset.Position(fn.Pos())
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + typestring(sig.Results().At(0).Type())
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += typestring(sig.Results().At(i).Type())
}
s += ")"
}
return s
}
// typestring returns a stable (human-readable) type string for the given type
// that can be used for interface equality checks. It is almost (but not
// exactly) the same as calling t.String(). The main difference is some
// normalization around `byte` vs `uint8` for example.
func typestring(t types.Type) string {
// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
switch t := t.(type) {
case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic:
return basicTypes[t.Kind()]
case *types.Chan:
switch t.Dir() {
case types.SendRecv:
return "chan (" + typestring(t.Elem()) + ")"
case types.SendOnly:
return "chan<- (" + typestring(t.Elem()) + ")"
case types.RecvOnly:
return "<-chan (" + typestring(t.Elem()) + ")"
default:
panic("unknown channel direction")
}
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := range methods {
method := t.Method(i)
methods[i] = method.Name() + signature(method.Type().(*types.Signature))
}
return "interface{" + strings.Join(methods, ";") + "}"
case *types.Map:
return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
case *types.Named:
return t.String()
case *types.Pointer:
return "*" + typestring(t.Elem())
case *types.Signature:
return "func" + signature(t)
case *types.Slice:
return "[]" + typestring(t.Elem())
case *types.Struct:
fields := make([]string, t.NumFields())
for i := range fields {
field := t.Field(i)
fields[i] = field.Name() + " " + typestring(field.Type())
if tag := t.Tag(i); tag != "" {
fields[i] += " " + strconv.Quote(tag)
}
}
return "struct{" + strings.Join(fields, ";") + "}"
default:
panic("unknown type: " + t.String())
// set up IR builder
block := c.ctx.AddBasicBlock(wrapper, "entry")
c.builder.SetInsertPointAtEnd(block)
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(fn.LLVMFn, params, "")
c.builder.CreateRetVoid()
} else {
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret)
}
}
+29 -30
View File
@@ -8,62 +8,61 @@ import (
"tinygo.org/x/go-llvm"
)
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
// will be lowered to a real interrupt during interrupt lowering.
//
// This two-stage approach allows unused interrupts to be optimized away if
// necessary.
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
// Get the interrupt number, which must be a compile-time constant.
id, ok := instr.Args[0].(*ssa.Const)
if !ok {
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant")
}
// Get the func value, which also must be a compile time constant.
// Note that bound functions are allowed if the function has a pointer
// receiver and is a global. This is rather strict but still allows for
// idiomatic Go code.
funcValue := b.getValue(instr.Args[1])
funcValue := c.getValue(frame, instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
switch instr.Args[1].(type) {
case *ssa.MakeClosure:
// This may also be a bound method.
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
return llvm.Value{}, c.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")
return llvm.Value{}, c.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)
globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
globalLLVMType := c.getLLVMType(globalType)
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
if global := c.mod.NamedGlobal(globalName); !global.IsNil() {
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program")
}
global := llvm.AddGlobal(c.mod, globalLLVMType, globalName)
global.SetLinkage(llvm.PrivateLinkage)
global.SetGlobalConstant(true)
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})
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0})
global.SetInitializer(initializer)
// Add debug info to the interrupt global.
if b.Debug {
pos := b.program.Fset.Position(instr.Pos())
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
if c.Debug() {
pos := c.ir.Program.Fset.Position(instr.Pos())
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
LinkageName: globalName,
File: b.getDIFile(pos.Filename),
File: c.getDIFile(pos.Filename),
Line: pos.Line,
Type: b.getDIType(globalType),
Expr: b.dibuilder.CreateExpression(nil),
Type: c.getDIType(globalType),
Expr: c.dibuilder.CreateExpression(nil),
LocalToUnit: false,
})
global.AddMetadata(0, diglobal)
@@ -71,21 +70,21 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// 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})
num := llvm.ConstPtrToInt(global, c.intType)
interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
// Add dummy "use" call for AVR, because interrupts may be used even though
// they are never referenced again. This is unlike Cortex-M or the RISC-V
// PLIC where each interrupt must be enabled using the interrupt number, and
// thus keeps the Interrupt object alive.
// This call is removed during interrupt lowering.
if strings.HasPrefix(b.Triple, "avr") {
useFn := b.mod.NamedFunction("runtime/interrupt.use")
if strings.HasPrefix(c.Triple(), "avr") {
useFn := c.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)
useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType)
}
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "")
}
return interrupt, nil
-124
View File
@@ -1,124 +0,0 @@
package compiler
// This file contains helper functions to create calls to LLVM intrinsics.
import (
"go/token"
"strconv"
"strings"
"tinygo.org/x/go-llvm"
)
// Define unimplemented intrinsic functions.
//
// Some functions are either normally implemented in Go assembly (like
// sync/atomic functions) or intentionally left undefined to be implemented
// directly in the compiler (like runtime/volatile functions). Either way, look
// for these and implement them if this is the case.
func (b *builder) defineIntrinsicFunction() {
name := b.fn.RelString(nil)
switch {
case name == "runtime.memcpy" || name == "runtime.memmove":
b.createMemoryCopyImpl()
case name == "runtime.memzero":
b.createMemoryZeroImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"):
b.createVolatileStore()
case strings.HasPrefix(name, "sync/atomic.") && token.IsExported(b.fn.Name()):
b.createFunctionStart(true)
returnValue := b.createAtomicOp(b.fn.Name())
if !returnValue.IsNil() {
b.CreateRet(returnValue)
} else {
b.CreateRetVoid()
}
}
}
// createMemoryCopyImpl 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) createMemoryCopyImpl() {
b.createFunctionStart(true)
fnName := "llvm." + b.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 b.fn.Params {
params = append(params, b.getValue(param))
}
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
}
// createMemoryZeroImpl 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) createMemoryZeroImpl() {
b.createFunctionStart(true)
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(b.fn.Params[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(b.fn.Params[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn, params, "")
b.CreateRetVoid()
}
var mathToLLVMMapping = map[string]string{
"math.Ceil": "llvm.ceil.f64",
"math.Exp": "llvm.exp.f64",
"math.Exp2": "llvm.exp2.f64",
"math.Floor": "llvm.floor.f64",
"math.Log": "llvm.log.f64",
"math.Sqrt": "llvm.sqrt.f64",
"math.Trunc": "llvm.trunc.f64",
}
// defineMathOp defines a math function body as a call to a LLVM intrinsic,
// instead of the regular Go implementation. This allows LLVM to reason about
// the math operation and (depending on the architecture) allows it to lower the
// operation to very fast floating point instructions. If this is not possible,
// LLVM will emit a call to a libm function that implements the same operation.
//
// One example of an optimization that LLVM can do is to convert
// float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is
// beneficial on architectures where 64-bit floating point operations are (much)
// more expensive than 32-bit ones.
func (b *builder) defineMathOp() {
b.createFunctionStart(true)
llvmName := mathToLLVMMapping[b.fn.RelString(nil)]
if llvmName == "" {
panic("unreachable: unknown math operation") // sanity check
}
llvmFn := b.mod.NamedFunction(llvmName)
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, llvmName, llvmType)
}
// Create a call to the intrinsic.
args := make([]llvm.Value, len(b.fn.Params))
for i, param := range b.fn.Params {
args[i] = b.getValue(param)
}
result := b.CreateCall(llvmFn, args, "")
b.CreateRet(result)
}
-48
View File
@@ -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{}
}
}
+25 -272
View File
@@ -1,12 +1,6 @@
package compiler
import (
"fmt"
"go/token"
"go/types"
"math/big"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -14,57 +8,55 @@ import (
// This file contains helper functions for LLVM that are not exposed in the Go
// bindings.
// Return a list of values (actually, instructions) where this value is used as
// an operand.
func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value
use := value.FirstUse()
for !use.IsNil() {
uses = append(uses, use.User())
use = use.NextUse()
}
return uses
}
// createTemporaryAlloca creates a new alloca in the entry block and adds
// lifetime start information in the IR signalling that the alloca won't be used
// 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 (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
}
// insertBasicBlock inserts a new basic block after the current basic block.
// This is useful when inserting new basic blocks while converting a
// *ssa.BasicBlock to a llvm.BasicBlock and the LLVM basic block needs some
// extra blocks.
// It does not update b.blockExits, this must be done by the caller.
func (b *builder) insertBasicBlock(name string) llvm.BasicBlock {
currentBB := b.Builder.GetInsertBlock()
nextBB := llvm.NextBasicBlock(currentBB)
if nextBB.IsNil() {
// Last basic block in the function, so add one to the end.
return b.ctx.AddBasicBlock(b.llvmFn, name)
}
// Insert a basic block before the next basic block - that is, at the
// current insert location.
return b.ctx.InsertBasicBlock(nextBB, name)
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
}
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
// llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca.
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
}
// emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values)
func (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
}
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
}
// makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
@@ -75,242 +67,3 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
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 {
kind := t.TypeKind()
if kind == llvm.ArrayTypeKind {
t = t.ElementType()
continue
}
if kind == llvm.StructTypeKind {
fields := t.StructElementTypes()
if len(fields) == 1 {
t = fields[0]
continue
}
}
break
}
// 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
// integer. Store it in a global instead.
// Try first whether the global already exists. All objects with a
// particular name have the same type, so this is possible.
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.
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
bitmap.FillBytes(bitmapBytes)
var bitmapByteValues []llvm.Value
for _, b := range bitmapBytes {
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
}
offset := c.targetData.ElementOffset(typ, i)
if offset%uint64(alignment) != 0 {
// This error will let the compilation fail, but by continuing
// the error can still easily be shown.
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
continue
}
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
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")
}
}
// archFamily returns the archtecture from the LLVM triple but with some
// architecture names ("armv6", "thumbv7m", etc) merged into a single
// architecture name ("arm").
func (c *compilerContext) archFamily() string {
arch := strings.Split(c.Triple, "-")[0]
if strings.HasPrefix(arch, "arm64") {
return "aarch64"
}
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
return "arm"
}
return arch
}
// isThumb returns whether we're in ARM or in Thumb mode. It panics if the
// features string is not one for an ARM architecture.
func (c *compilerContext) isThumb() bool {
var isThumb, isNotThumb bool
for _, feature := range strings.Split(c.Features, ",") {
if feature == "+thumb-mode" {
isThumb = true
}
if feature == "-thumb-mode" {
isNotThumb = true
}
}
if isThumb == isNotThumb {
panic("unexpected feature flags")
}
return isThumb
}
// readStackPointer emits a LLVM intrinsic call that returns the current stack
// pointer as an *i8.
func (b *builder) readStackPointer() llvm.Value {
stacksave := b.mod.NamedFunction("llvm.stacksave")
if stacksave.IsNil() {
fnType := llvm.FunctionType(b.i8ptrType, nil, false)
stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
}
return b.CreateCall(stacksave, nil, "")
}
-2
View File
@@ -34,7 +34,6 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm
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())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
@@ -47,7 +46,6 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
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())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name)
+33 -74
View File
@@ -5,19 +5,18 @@ package llvmutil
// itself if possible and legal.
import (
"github.com/tinygo-org/tinygo/compileopts"
"tinygo.org/x/go-llvm"
)
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// 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 {
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
@@ -26,6 +25,7 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needs
packedType := ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
var packedAlloc, packedHeapAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(i8ptrType)
@@ -38,88 +38,48 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needs
// 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
// it in an alloca first for bitcasting (store+bitcast+load).
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
} 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{
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.ConstNull(i8ptrType),
llvm.Undef(i8ptrType), // unused context parameter
llvm.Undef(i8ptrType), // unused context parameter
llvm.ConstPointerNull(i8ptrType), // coroutine handle
}, "")
if needsStackObjects {
if config.NeedsStackObjects() {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
llvm.Undef(i8ptrType), // unused context parameter
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)
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
}
// Store all values in the alloca or heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
// Return the original heap allocation pointer, which already is an *i8.
if packedHeapAlloc.IsNil() {
// Load value (as *i8) from the alloca.
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
result := builder.CreateLoad(packedAlloc, "")
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Get the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
@@ -128,9 +88,8 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needs
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
uintptrType := ctx.IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
packedType := ctx.StructType(valueTypes, false)
+51 -175
View File
@@ -6,237 +6,113 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// constants for hashmap algorithms; must match src/runtime/hashmap.go
const (
hashmapAlgorithmBinary = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
mapType := expr.Type().Underlying().(*types.Map)
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64 // must match values in src/runtime/hashmap.go
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmString
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmBinary
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = hashmapAlgorithmInterface
}
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)
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, 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, algEnum}, "")
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)
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := c.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)
}
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
params := []llvm.Value{m, key, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack
// growth.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else {
// Not trivially comparable using memcmp. Make it an interface instead.
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}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
// Not trivially comparable using memcmp.
return llvm.Value{}, c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(mapValueAlloca, "")
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
}
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
c.builder.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valuePtr}
b.createRuntimeCall("hashmapStringSet", params, "")
c.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
c.createRuntimeCall("hashmapBinarySet", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
} else {
// Key is not trivially comparable, so compare it as an interface 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, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
c.addError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
b.emitLifetimeEnd(valuePtr, valueSize)
c.emitLifetimeEnd(valuePtr, valueSize)
}
// createMapDelete deletes a key from a map by calling the appropriate runtime
// 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 {
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key}
b.createRuntimeCall("hashmapStringDelete", params, "")
c.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
c.builder.CreateStore(key, keyAlloca)
params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyPtr, keySize)
c.createRuntimeCall("hashmapBinaryDelete", params, "")
c.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
// Key is not trivially comparable, so compare it as an interface
// 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
return c.makeError(pos, "only strings, bools, ints, pointers or structs of bools/ints are supported as map keys, but got: "+keyType.String())
}
}
// createMapIteratorNext lowers the *ssa.Next instruction for iterating over a
// map. It returns a tuple of {bool, key, value} with the result of the
// iteration.
func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llvm.Value) llvm.Value {
// Determine the type of the values to return from the *ssa.Next
// instruction. It is returned as {bool, keyType, valueType}.
keyType := rangeVal.Type().Underlying().(*types.Map).Key()
valueType := rangeVal.Type().Underlying().(*types.Map).Elem()
llvmKeyType := b.getLLVMType(keyType)
llvmValueType := b.getLLVMType(valueType)
// There is a special case in which keys are stored as an interface value
// instead of the value they normally are. This happens for non-trivially
// comparable types such as float32 or some structs.
isKeyStoredAsInterface := false
if t, ok := keyType.Underlying().(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
} else {
// The key is stored as an interface value, and may or may not be an
// interface type (for example, float32 keys are stored as an interface
// value).
if _, ok := keyType.Underlying().(*types.Interface); !ok {
isKeyStoredAsInterface = true
}
// 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
}
// Determine the type of the key as stored in the map.
llvmStoredKeyType := llvmKeyType
if isKeyStoredAsInterface {
llvmStoredKeyType = b.getLLVMRuntimeType("_interface")
// 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
}
// Extract the key and value from the map.
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
mapKey := b.CreateLoad(mapKeyAlloca, "")
mapValue := b.CreateLoad(mapValueAlloca, "")
if isKeyStoredAsInterface {
// The key is stored as an interface but it isn't of interface type.
// Extract the underlying value.
mapKey = b.extractValueFromInterface(mapKey, llvmKeyType)
}
// End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
b.emitLifetimeEnd(mapValuePtr, mapValueSize)
// Construct the *ssa.Next return value: {ok, mapKey, mapValue}
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
tuple = b.CreateInsertValue(tuple, ok, 0, "")
tuple = b.CreateInsertValue(tuple, mapKey, 1, "")
tuple = b.CreateInsertValue(tuple, mapValue, 2, "")
return tuple
return tophash
}
// Returns true if this key type does not contain strings, interfaces etc., so
+164
View File
@@ -0,0 +1,164 @@
package compiler
import (
"errors"
"github.com/tinygo-org/tinygo/transform"
"tinygo.org/x/go-llvm"
)
// Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
builder := llvm.NewPassManagerBuilder()
defer builder.Dispose()
builder.SetOptLevel(optLevel)
builder.SetSizeLevel(sizeLevel)
if inlinerThreshold != 0 {
builder.UseInlinerWithThreshold(inlinerThreshold)
}
builder.AddCoroutinePassesToExtensionPoints()
if c.PanicStrategy() == "trap" {
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
}
// run a check of all of our code
if c.VerifyIR() {
errs := c.checkModule()
if errs != nil {
return errs
}
}
// Run function passes for each function.
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.AddGlobalDCEPass()
goPasses.AddGlobalOptimizerPass()
goPasses.AddConstantPropagationPass()
goPasses.AddAggressiveDCEPass()
goPasses.AddFunctionAttrsPass()
goPasses.Run(c.mod)
// Run Go-specific optimization passes.
transform.OptimizeMaps(c.mod)
transform.OptimizeStringToBytes(c.mod)
transform.OptimizeAllocs(c.mod)
transform.LowerInterfaces(c.mod)
errs := transform.LowerInterruptRegistrations(c.mod)
if len(errs) > 0 {
return errs
}
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
// After interfaces are lowered, there are many more opportunities for
// interprocedural optimizations. To get them to work, function
// attributes have to be updated first.
goPasses.Run(c.mod)
// Run TinyGo-specific interprocedural optimizations.
transform.OptimizeAllocs(c.mod)
transform.OptimizeStringToBytes(c.mod)
// 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 []error{err}
}
} else {
// Must be run at any optimization level.
transform.LowerInterfaces(c.mod)
if c.funcImplementation() == funcValueSwitch {
transform.LowerFuncValues(c.mod)
}
err := c.LowerGoroutines()
if err != nil {
return []error{err}
}
errs := transform.LowerInterruptRegistrations(c.mod)
if len(errs) > 0 {
return errs
}
}
if c.VerifyIR() {
if errs := c.checkModule(); errs != nil {
return errs
}
}
if err := c.Verify(); err != nil {
return []error{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)
}
}
// After TinyGo-specific transforms have finished, undo exporting these functions.
for _, name := range c.getFunctionsUsedInTransforms() {
fn := c.mod.NamedFunction(name)
if fn.IsNil() {
continue
}
fn.SetLinkage(llvm.InternalLinkage)
}
// Run function passes again, because without it, llvm.coro.size.i32()
// doesn't get lowered.
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
}
funcPasses.FinalizeFunc()
// Run module passes.
modPasses := llvm.NewPassManager()
defer modPasses.Dispose()
builder.Populate(modPasses)
modPasses.Run(c.mod)
hasGCPass := transform.AddGlobalsBitmap(c.mod)
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
if hasGCPass {
if err := c.Verify(); err != nil {
return []error{errors.New("GC pass caused a verification failure")}
}
}
return nil
}
+5 -12
View File
@@ -10,13 +10,13 @@ import (
// The original license can be found here:
// https://golang.org/LICENSE
type stdSizes struct {
type StdSizes struct {
IntSize int64
PtrSize 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
// of alignment of the elements and fields, respectively.
switch t := T.Underlying().(type) {
@@ -45,11 +45,7 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
if t.Info()&types.IsString != 0 {
return s.PtrSize
}
case *types.Signature:
// Even though functions in tinygo are 2 pointers, they are not 2 pointer aligned
return s.PtrSize
}
a := s.Sizeof(T) // may be 0
// spec: "For a variable x of any type: unsafe.Alignof(x) is at least 1."
if a < 1 {
@@ -65,7 +61,7 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
return a
}
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
offsets := make([]int64, len(fields))
var o int64
for i, f := range fields {
@@ -93,7 +89,7 @@ var basicSizes = [...]byte{
types.Complex128: 16,
}
func (s *stdSizes) Sizeof(T types.Type) int64 {
func (s *StdSizes) Sizeof(T types.Type) int64 {
switch t := T.Underlying().(type) {
case *types.Basic:
k := t.Kind()
@@ -152,11 +148,8 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
return align(offsets[n-1]+s.Sizeof(fields[n-1].Type()), maxAlign)
case *types.Interface:
return s.PtrSize * 2
case *types.Pointer, *types.Chan, *types.Map:
case *types.Pointer:
return s.PtrSize
case *types.Signature:
// Func values in TinyGo are two words in size.
return s.PtrSize * 2
default:
panic("unknown type: " + t.String())
}

Some files were not shown because too many files have changed in this diff Show More