Compare commits

..

1 Commits

Author SHA1 Message Date
BCG 4f84dcb92f Updated README to mention compiling for Windows 2023-02-28 22:36:53 -05:00
1303 changed files with 19797 additions and 86993 deletions
+23 -28
View File
@@ -10,12 +10,12 @@ commands:
steps:
- restore_cache:
keys:
- llvm-source-19-v1
- llvm-source-14-v3
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-19-v1
key: llvm-source-14-v3
paths:
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
@@ -33,13 +33,13 @@ commands:
steps:
- restore_cache:
keys:
- binaryen-linux-v3
- binaryen-linux-v2
- run:
name: "Build Binaryen"
command: |
make binaryen
- save_cache:
key: binaryen-linux-v3
key: binaryen-linux-v2
paths:
- build/wasm-opt
test-linux:
@@ -55,7 +55,7 @@ commands:
- run:
name: "Install apt dependencies"
command: |
echo 'deb https://apt.llvm.org/bullseye/ llvm-toolchain-bullseye-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list
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 \
@@ -69,10 +69,18 @@ commands:
- build-binaryen-linux
- restore_cache:
keys:
- go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v4-{{ checksum "go.mod" }}
- go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v3-{{ 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
- save_cache:
key: wasi-libc-sysroot-systemclang-v6
paths:
- lib/wasi-libc/sysroot
- when:
condition: <<parameters.fmt-check>>
steps:
@@ -80,40 +88,27 @@ commands:
# 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 lint
command: make fmt-check
- run: make gen-device -j4
# TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20
- run: go test -tags=llvm<<parameters.llvm>> -short -run='TestBuild|TestTest|TestGetList|TestTraceback'
- run: make smoketest XTENSA=0
- save_cache:
key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
jobs:
test-oldest:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
test-llvm14-go118:
docker:
- image: golang:1.23-bullseye
- image: golang:1.18-buster
steps:
- test-linux:
llvm: "15"
resource_class: large
test-newest:
# This tests the latest supported LLVM version when linking against system
# libraries.
docker:
- image: golang:1.26-bookworm
steps:
- test-linux:
llvm: "20"
llvm: "14"
resource_class: large
workflows:
test-all:
jobs:
- test-oldest
# disable this test, since CircleCI seems unable to download due to rate-limits on Dockerhub.
# - test-newest
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm14-go118
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
open_collective: tinygo
+42 -55
View File
@@ -14,39 +14,26 @@ concurrency:
jobs:
build-macos:
name: build-macos
strategy:
matrix:
# macos-14: arm64 (oldest supported version as of 18-11-2025)
# macos-15-intel: amd64 (last intel version to be supported by github runners)
# See https://github.com/actions/runner-images/issues/13046
os: [macos-14, macos-15-intel]
include:
- os: macos-14
goarch: arm64
- os: macos-15-intel
goarch: amd64
runs-on: ${{ matrix.os }}
runs-on: macos-11
steps:
- name: Install Dependencies
shell: bash
run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-20-${{ matrix.os }}-v1
key: llvm-source-15-macos-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -57,7 +44,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -68,78 +55,78 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-20-${{ matrix.os }}-v2
key: llvm-build-15-macos-v4
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 ninja
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# build!
make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: make gen-device
run: make -j3 gen-device
- 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
run: make test GOTESTFLAGS="-only-current-os"
shell: bash
run: make test GOTESTFLAGS="-short"
- name: Build TinyGo release tarball
run: make release -j3
- name: Test stdlib packages
run: make tinygo-test
- name: Make release artifact
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
shell: bash
run: cp -p build/release.tar.gz build/tinygo.darwin-amd64.tar.gz
- name: Publish release artifact
uses: actions/upload-artifact@v7
# 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: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
archive: false
name: release-double-zipped
path: build/tinygo.darwin-amd64.tar.gz
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo
test-macos-homebrew:
name: homebrew-install
runs-on: macos-latest
strategy:
matrix:
version: [16, 17, 18, 19, 20]
steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@main
- name: Fix Python symlinks
run: |
# Github runners have broken symlinks, so relink
# see: https://github.com/actions/setup-python/issues/577
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
- name: Install LLVM
shell: bash
run: |
brew install llvm@${{ matrix.version }}
brew link llvm@${{ matrix.version }}
HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@15
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
- name: Check binary
run: tinygo version
- name: Build TinyGo (default LLVM)
if: matrix.version == 20
- name: Build TinyGo
run: go install
- name: Check binary
if: matrix.version == 20
run: tinygo version
+50 -18
View File
@@ -19,50 +19,82 @@ jobs:
packages: write
contents: read
steps:
- name: Free Disk space
shell: bash
run: |
df -h
sudo rm -rf /opt/hostedtoolcache
sudo rm -rf /usr/local/lib/android
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/graalvm
sudo rm -rf /usr/local/share/boost
df -h
- name: Check out the repo
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@v2
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@v4
with:
images: |
tinygo/tinygo-dev
ghcr.io/${{ github.repository_owner }}/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@v4
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v4
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
uses: docker/build-push-action@v3
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-contexts: tinygo-llvm-build=docker-image://tinygo/llvm-15
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 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/tinyfs/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFont 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/tinyfont/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyDraw 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/tinydraw/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyTerm 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/tinyterm/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
+214 -118
View File
@@ -18,37 +18,32 @@ jobs:
# statically linked binary.
runs-on: ubuntu-latest
container:
image: golang:1.26-alpine
outputs:
version: ${{ steps.version.outputs.version }}
image: golang:1.20-alpine
steps:
- name: Install apk dependencies
# tar: needed for actions/cache@v5
# 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-dev mold
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@v6
uses: actions/checkout@v3
with:
submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Cache Go
uses: actions/cache@v5
uses: actions/cache@v3
with:
key: go-cache-linux-alpine-v2-${{ hashFiles('go.mod') }}
key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-20-linux-alpine-v2
key: llvm-source-15-linux-alpine-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -59,7 +54,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -70,10 +65,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-20-linux-alpine-v2
key: llvm-build-15-linux-alpine-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -88,80 +83,76 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v5
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-alpine-v2
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: Run linter
run: make lint
- name: Run spellcheck
run: make spell
- name: Build TinyGo release
run: |
make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
- name: "Publish release artifact: tarball"
uses: actions/upload-artifact@v7
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@v3
with:
name: tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
path: /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
archive: false
- name: "Publish release artifact: Debian package"
uses: actions/upload-artifact@v7
with:
name: tinygo_${{ steps.version.outputs.version }}_amd64.deb
path: /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
archive: false
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@v6
with:
submodules: true
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "29.0.1"
- name: Install wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
run: |
mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin
curl https://github.com/bytecodealliance/wasmtime/releases/download/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL
tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-x86_64-linux/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Download release artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@v3
with:
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
name: linux-amd64-double-zipped
- name: Extract release tarball
run: |
mkdir -p ~/lib
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast
- run: make tinygo-test-wasip2-fast
- run: make tinygo-test-wasm
- 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
@@ -169,7 +160,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: true
- name: Install apt dependencies
@@ -184,25 +175,25 @@ jobs:
simavr \
ninja-build
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Install Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v3
with:
node-version: '18'
node-version: '14'
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "29.0.1"
- name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1
run: |
mkdir -p $HOME/.wasmtime $HOME/.wasmtime/bin
curl -L https://github.com/bytecodealliance/wasmtime/releases/download/v5.0.0/wasmtime-v5.0.0-x86_64-linux.tar.xz -o wasmtime-v5.0.0-x86_64-linux.tar.xz -SfL
tar -C $HOME/.wasmtime/bin --wildcards -xf wasmtime-v5.0.0-x86_64-linux.tar.xz --strip-components=1 wasmtime-v5.0.0-x86_64-linux/*
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-20-linux-asserts-v1
key: llvm-source-15-linux-asserts-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -213,7 +204,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -224,10 +215,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-20-linux-asserts-v1
key: llvm-build-15-linux-asserts-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -240,13 +231,13 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v5
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-asserts-v1
@@ -254,6 +245,15 @@ jobs:
- 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
@@ -265,8 +265,8 @@ jobs:
run: make tinygo-test
- run: make smoketest
- run: make wasmtest
- run: make tinygo-test-baremetal
build-linux-cross:
- 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
@@ -275,41 +275,28 @@ jobs:
# 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.
strategy:
matrix:
goarch: [ arm, arm64 ]
include:
- goarch: arm64
toolchain: aarch64-linux-gnu
libc: arm64
- goarch: arm
toolchain: arm-linux-gnueabihf
libc: armhf
runs-on: ubuntu-22.04 # note: use the oldest image available! (see above)
runs-on: ubuntu-18.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
uses: actions/checkout@v3
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-${{ matrix.toolchain }} \
libc6-dev-${{ matrix.libc }}-cross
g++-arm-linux-gnueabihf \
libc6-dev-armhf-cross
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-20-linux-v1
key: llvm-source-15-linux-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -320,7 +307,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -331,10 +318,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-20-linux-${{ matrix.goarch }}-v1
key: llvm-build-15-linux-arm-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -345,27 +332,27 @@ jobs:
# Install build dependencies.
sudo apt-get install --no-install-recommends ninja-build
# build!
make llvm-build CROSS=${{ matrix.toolchain }}
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: Save LLVM build cache
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Binaryen
uses: actions/cache@v5
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-${{ matrix.goarch }}-v4
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=${{ matrix.toolchain }} binaryen
make CROSS=arm-linux-gnueabihf binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
@@ -373,33 +360,142 @@ jobs:
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=${{ matrix.toolchain }}
make CROSS=arm-linux-gnueabihf
- name: Download amd64 release
uses: actions/download-artifact@v8
uses: actions/download-artifact@v3
with:
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
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 ${{ matrix.goarch }} release
- name: Create arm release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
cp -p build/release.tar.gz /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
- name: "Publish release artifact: tarball"
uses: actions/upload-artifact@v7
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@v3
with:
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
path: /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
archive: false
- name: "Publish release artifact: Debian package"
uses: actions/upload-artifact@v7
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@v3
- 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:
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
path: /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
archive: false
go-version: '1.20'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-15-linux-v3
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: Save LLVM source cache
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore LLVM build cache
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-15-linux-arm64-v4
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: Save LLVM build cache
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- 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@v3
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@v3
with:
name: linux-arm64-double-zipped
path: |
/tmp/tinygo.linux-arm64.tar.gz
/tmp/tinygo_arm64.deb
-63
View File
@@ -1,63 +0,0 @@
# This is the Github action to build and push the LLVM Docker image
# used by the tinygo/tinygo-dev Docker image.
#
# It only needs to be rebuilt when updating the LLVM version.
#
# To update, make any needed changes to this file,
# then push to the "build-llvm-image" branch.
#
# The needed image will be rebuilt, which will very likely take at least 1-2 hours.
name: LLVM
on:
push:
branches: [ build-llvm-image ]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-push-llvm:
name: build-push-llvm
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: |
tinygo/llvm-20
ghcr.io/${{ github.repository_owner }}/llvm-20
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
with:
target: tinygo-llvm-build
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
-48
View File
@@ -1,48 +0,0 @@
name: Nix
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nix-test:
runs-on: ubuntu-latest
steps:
- name: Uninstall system LLVM
# Hack to work around issue where we still include system headers for
# some reason.
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
run: sudo apt-get remove llvm-18
- name: Checkout
uses: actions/checkout@v6
- name: Pull musl, bdwgc
run: |
git submodule update --init lib/musl lib/bdwgc
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-20-linux-nix-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/compiler-rt
- uses: cachix/install-nix-action@v31
- name: Test
run: |
nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo"
@@ -1,12 +0,0 @@
# Command that's part of sizediff.yml. This is put in a separate file so that it
# still works after checking out the dev branch (that is, when going from LLVM
# 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed).
echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-20-dev \
clang-20 \
libclang-20-dev \
lld-20
-93
View File
@@ -1,93 +0,0 @@
name: Binary size difference
on:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
sizediff:
# Note: when updating the Ubuntu version, also update the Ubuntu version in
# sizediff-install-pkgs.sh
runs-on: ubuntu-24.04
permissions:
pull-requests: write
steps:
# Prepare, install tools
- name: Add GOBIN to $PATH
run: |
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true
- name: Install apt dependencies
run: ./.github/workflows/sizediff-install-pkgs.sh
- name: Restore LLVM source cache
uses: actions/cache@v5
id: cache-llvm-source
with:
key: llvm-source-20-sizediff-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache Go
uses: actions/cache@v5
with:
key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- run: make gen-device -j4
- name: Download drivers repo
run: git clone https://github.com/tinygo-org/drivers.git
- name: Save HEAD
run: git branch github-actions-saved-HEAD HEAD
# Compute sizes for the PR branch
- name: Build tinygo binary for the PR branch
run: go install
- name: Determine binary sizes on the PR branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt)
# Compute sizes for the dev branch
- name: Checkout dev branch
run: |
git reset --hard origin/dev
git checkout --no-recurse-submodules `git merge-base HEAD origin/dev`
- name: Install apt dependencies on the dev branch
# this is only needed on a PR that changes the LLVM version
run: ./.github/workflows/sizediff-install-pkgs.sh
- name: Build tinygo binary for the dev branch
run: go install
- name: Determine binary sizes on the dev branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt)
# Create comment
# TODO: add a summary, something like:
# - overall size difference (percent)
# - number of binaries that grew / shrank / remained the same
# - don't show the full diff when no binaries changed
- name: Calculate size diff
run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt
- name: Create comment
run: |
echo "Size difference with the dev branch:" > comment.txt
echo "<details><summary>Binary size difference</summary>" >> comment.txt
echo "<pre>" >> comment.txt
cat sizediff.txt >> comment.txt
echo "</pre></details>" >> comment.txt
- name: Comment contents
run: cat comment.txt
- name: Add comment
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
uses: thollander/actions-comment-pull-request@v2.3.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
filePath: comment.txt
comment_tag: sizediff
@@ -1,4 +0,0 @@
#!/bin/sh
# Extract the version string from the source code, to be stored in a variable.
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
+72 -62
View File
@@ -14,39 +14,34 @@ concurrency:
jobs:
build-windows:
runs-on: windows-2022
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install ninja binaryen
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
with:
submodules: true
- name: Extract TinyGo version
id: version
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Restore cached LLVM source
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-source
with:
key: llvm-source-20-windows-v1
key: llvm-source-15-windows-v4
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -57,7 +52,7 @@ jobs:
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save cached LLVM source
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
@@ -68,10 +63,10 @@ jobs:
llvm-project/lld/include
llvm-project/llvm/include
- name: Restore cached LLVM build
uses: actions/cache/restore@v5
uses: actions/cache/restore@v3
id: cache-llvm-build
with:
key: llvm-build-20-windows-v2
key: llvm-build-15-windows-v5
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -85,70 +80,78 @@ jobs:
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save cached LLVM build
uses: actions/cache/save@v5
uses: actions/cache/save@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache Go cache
uses: actions/cache@v5
- name: Cache wasi-libc sysroot
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
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 config use_external_7zip true
scoop install wasmtime@29.0.1
- name: make gen-device
run: make -j3 gen-device
scoop install wasmtime
- name: Test TinyGo
shell: bash
run: make test GOTESTFLAGS="-only-current-os"
run: make test GOTESTFLAGS="-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 tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
run: 7z -tzip a release.zip tinygo
- name: Publish release artifact
uses: actions/upload-artifact@v7
# 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@v3
with:
name: tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
archive: false
name: release-double-zipped
path: build/release/release.zip
smoke-test-windows:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install binaryen
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
uses: actions/download-artifact@v2
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
name: release-double-zipped
path: build/
# This build is already unzipped.
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Smoke tests
shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -158,24 +161,27 @@ jobs:
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
uses: actions/download-artifact@v2
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
name: release-double-zipped
path: build/
# This build is already unzipped.
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
@@ -184,29 +190,33 @@ jobs:
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
uses: al-cheb/configure-pagefile-action@v1.3
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install binaryen wasmtime@29.0.1
scoop install binaryen wasmtime
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v6
uses: actions/setup-go@v3
with:
go-version: '1.26.2'
go-version: '1.20'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
uses: actions/download-artifact@v2
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
name: release-double-zipped
path: build/
# This build is already unzipped.
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Test stdlib packages on wasi
run: make tinygo-test-wasi-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+1 -12
View File
@@ -1,8 +1,3 @@
.DS_Store
.vscode
go.work
go.work.sum
docs/_build
src/device/avr/*.go
src/device/avr/*.ld
@@ -20,11 +15,9 @@ src/device/stm32/*.go
src/device/stm32/*.s
src/device/kendryte/*.go
src/device/kendryte/*.s
src/device/renesas/*.go
src/device/renesas/*.s
src/device/rp/*.go
src/device/rp/*.s
./vendor
vendor
llvm-build
llvm-project
build/*
@@ -37,9 +30,5 @@ test.exe
test.gba
test.hex
test.nro
test.uf2
test.wasm
wasm.wasm
*.uf2
*.elf
+4 -14
View File
@@ -9,20 +9,19 @@
url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"]
path = lib/cmsis-svd
url = https://github.com/cmsis-svd/cmsis-svd-data.git
branch = main
url = https://github.com/tinygo-org/cmsis-svd
[submodule "lib/wasi-libc"]
path = lib/wasi-libc
url = https://github.com/WebAssembly/wasi-libc
url = https://github.com/CraneStation/wasi-libc
[submodule "lib/picolibc"]
path = lib/picolibc
url = https://github.com/picolibc/picolibc.git
url = https://github.com/keith-packard/picolibc.git
[submodule "lib/stm32-svd"]
path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/musl"]
path = lib/musl
url = https://github.com/tinygo-org/musl-libc.git
url = git://git.musl-libc.org/musl
[submodule "lib/binaryen"]
path = lib/binaryen
url = https://github.com/WebAssembly/binaryen.git
@@ -32,12 +31,3 @@
[submodule "lib/macos-minimal-sdk"]
path = lib/macos-minimal-sdk
url = https://github.com/aykevl/macos-minimal-sdk.git
[submodule "src/net"]
path = src/net
url = https://github.com/tinygo-org/net.git
[submodule "lib/wasi-cli"]
path = lib/wasi-cli
url = https://github.com/WebAssembly/wasi-cli
[submodule "lib/bdwgc"]
path = lib/bdwgc
url = https://github.com/ivmai/bdwgc.git
+3 -25
View File
@@ -18,8 +18,7 @@ tarball. If you want to help with development of TinyGo itself, you should follo
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.19+)
* GNU Make
* Go (1.18+)
* Standard build tools (gcc/clang)
* git
* CMake
@@ -28,21 +27,6 @@ build tools to be built. Go is of course necessary to build TinyGo itself.
The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac.
## Using GNU Make
The static build of TinyGo is driven by GNUmakefile, which provides a help target for quick reference:
% make help
clean Remove build directory
fmt Reformat source
fmt-check Warn if any source needs reformatting
gen-device Generate microcontroller-specific sources
llvm-source Get LLVM sources
llvm-build Build LLVM
tinygo Build the TinyGo compiler
lint Lint source tree
spell Spellcheck source tree
## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone
@@ -85,17 +69,11 @@ Try running TinyGo:
./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. The command to check for
dynamic dependencies differs depending on your operating system.
On Linux, use `ldd` (not to be confused with `lld`):
Also, make sure the `tinygo` binary really is statically linked. Check this
using `ldd` (not to be confused with `lld`):
ldd ./build/tinygo
On macOS, use otool -L:
otool -L ./build/tinygo
The result should not contain libclang or libLLVM.
## Make a release tarball
+4 -1090
View File
File diff suppressed because it is too large Load Diff
+19 -22
View File
@@ -1,15 +1,10 @@
# tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.26 AS tinygo-llvm
FROM golang:1.20 AS tinygo-llvm
RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
rm -rf \
/var/lib/apt/lists/* \
/var/log/* \
/var/tmp/* \
/tmp/*
apt-get install -y apt-utils make cmake clang-11 ninja-build
COPY ./GNUmakefile /tinygo/GNUmakefile
COPY ./Makefile /tinygo/Makefile
RUN cd /tinygo/ && \
make llvm-source
@@ -20,24 +15,26 @@ FROM tinygo-llvm AS tinygo-llvm-build
RUN cd /tinygo/ && \
make llvm-build
# tinygo-compiler-build stage builds the compiler itself
FROM tinygo-llvm-build AS tinygo-compiler-build
# tinygo-compiler stage builds the compiler itself
FROM tinygo-llvm-build AS tinygo-compiler
COPY . /tinygo
# build the compiler and tools
# update submodules
RUN cd /tinygo/ && \
git submodule update --init && \
rm -rf ./lib/*/ && \
git submodule sync && \
git submodule update --init --recursive --force
RUN cd /tinygo/ && \
make
# tinygo-tools stage installs the needed dependencies to compile TinyGo programs for all platforms.
FROM tinygo-compiler AS tinygo-tools
RUN cd /tinygo/ && \
make wasi-libc binaryen && \
make gen-device -j4 && \
make build/release
cp build/* $GOPATH/bin/
# tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc).
FROM golang:1.26 AS tinygo-compiler
# Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
# Configure the container.
ENV PATH="${PATH}:/tinygo/bin"
CMD ["tinygo"]
-32
View File
@@ -1,32 +0,0 @@
TinyGo Team Members
===================
The team of humans who maintain TinyGo.
* **Purpose**: To maintain the community, code, documentation, and tools for the TinyGo compiler.
* **Board**: The group of people who share responsibility for key decisions for the TinyGo organization.
* **Majority Voting**: The board makes decisions by majority vote.
* **Membership**: The board elects its own members.
* **Do-ocracy**: Those who step forward to do a given task propose how it should be done. Then other interested people can make comments.
* **Proof of Work**: Power in decision-making is slightly weighted based on a participant's labor for the community.
* **Initiation**: We need to establish a procedure for how people join the team of maintainers.
* **Transparency**: Important information should be made publicly available, ideally in a way that allows for public comment.
* **Code of Conduct**: Participants agree to abide by the current project Code of Conduct.
## Members
* Ayke van Laethem (@aykevl)
* Daniel Esteban (@conejoninja)
* Ron Evans (@deadprogram)
* Damian Gryski (@dgryski)
* Masaaki Takasago (@sago35)
* Patricio Whittingslow (@soypat)
* Yurii Soldak (@ysoldak)
## Experimental
* **Monthly Meeting**: A monthly meeting for the team and any other interested participants.
Duration: 1 hour
Facilitation: @deadprogram
Schedule: See https://github.com/tinygo-org/tinygo/wiki/Meetings for more information
+2 -3
View File
@@ -1,8 +1,7 @@
Copyright (c) 2018-2026 The TinyGo Authors. All rights reserved.
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library.
Copyright 2009 The Go Authors. All rights reserved.
See https://github.com/golang/go/blob/master/LICENSE for license information.
Copyright (c) 2009-2022 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.
+121 -487
View File
File diff suppressed because it is too large Load Diff
+105 -59
View File
@@ -1,16 +1,11 @@
# 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) [![Nix](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml/badge.svg?branch=dev)](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml) [![CircleCI](https://circleci.com/gh/tinygo-org/tinygo/tree/dev.svg?style=svg)](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
[![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)
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/wasi), and command-line tools.
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
It reuses libraries used by the [Go language tools](https://golang.org/pkg/go/) alongside [LLVM](http://llvm.org) to provide an alternative way to compile programs written in the Go programming language.
> [!IMPORTANT]
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
## Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go
@@ -34,70 +29,121 @@ func main() {
}
```
The above program can be compiled and run without modification on an [Arduino Uno](https://tinygo.org/docs/reference/microcontrollers/boards/arduino-uno), an [Adafruit Circuit Playground Express](https://tinygo.org/docs/reference/microcontrollers/featured/circuitplay-express), a [Seeed Studio XIAO-ESP32S3](https://tinygo.org/docs/reference/microcontrollers/featured/xiao-esp32s3) or any of the many supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
```shell
tinygo flash -target arduino-uno examples/blinky1
```
## WebAssembly
TinyGo is very useful for compiling programs both for use in browsers (WASM) as well as for use on servers and other edge devices (WASI).
TinyGo programs can run in [Fastly Compute](https://www.fastly.com/documentation/guides/compute/go/), [Fermyon Spin](https://developer.fermyon.com/spin/go-components), [wazero](https://wazero.io/languages/tinygo/) and many other WebAssembly runtimes.
Here is a small TinyGo program for use by a WASI host application:
```go
package main
//go:wasmexport add
func add(x, y uint32) uint32 {
return x + y
}
```
This compiles the above TinyGo program for use on any WASI Preview 1 runtime:
```shell
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go
```
You can also use the same syntax as Go 1.24+:
```shell
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go
tinygo flash -target arduino examples/blinky1
```
## Installation
See the [getting started instructions](https://tinygo.org/getting-started/) for information on how to install TinyGo, as well as how to run the TinyGo compiler using our Docker container.
## Supported targets
## Supported boards/targets
### Embedded
You can compile TinyGo programs for microcontrollers, WebAssembly, Windows, and Linux.
You can compile TinyGo programs for over 150 different microcontroller boards.
The following 94 microcontroller boards are currently supported:
For more information, please see https://tinygo.org/docs/reference/microcontrollers/
* [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 M0 Express](https://www.adafruit.com/product/3403)
* [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 KB2040](https://www.adafruit.com/product/5302)
* [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 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)
* [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)
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
* [Waveshare RP2040-Zero](https://www.waveshare.com/wiki/RP2040-Zero)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
### WebAssembly
TinyGo programs can be compiled for both WASM and WASI targets.
For more information, see https://tinygo.org/docs/guides/webassembly/
### Operating Systems
You can also compile programs for Linux, macOS, and Windows targets.
For more information:
- Linux https://tinygo.org/docs/guides/linux/
- macOS https://tinygo.org/docs/guides/macos/
- Windows https://tinygo.org/docs/guides/windows/
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
## Currently supported features:
@@ -142,7 +188,7 @@ Non-goals:
## Why this project exists
> We never expected Go to be an embedded language, and so its got serious problems...
> We never expected Go to be an embedded language and so its got serious problems...
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
+4 -47
View File
@@ -3,7 +3,6 @@ package builder
import (
"bytes"
"debug/elf"
"debug/macho"
"debug/pe"
"encoding/binary"
"errors"
@@ -13,11 +12,10 @@ import (
"path/filepath"
"time"
wasm "github.com/aykevl/go-wasm"
"github.com/blakesmith/ar"
)
// makeArchive creates an archive for static linking from a list of object files
// 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...>
@@ -63,20 +61,6 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int
}{symbol.Name, i})
}
} else if dbg, err := macho.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symtab.Syms {
// See mach-o/nlist.h
if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT
continue // undefined symbol
}
if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0
continue // internal symbol (static, etc)
}
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 {
@@ -90,35 +74,8 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int
}{symbol.Name, i})
}
} else if dbg, err := wasm.Parse(objfile); err == nil {
for _, s := range dbg.Sections {
switch section := s.(type) {
case *wasm.SectionLinking:
for _, symbol := range section.Symbols {
if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 {
// Don't list undefined functions.
continue
}
if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 {
// Don't include local symbols.
continue
}
if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData {
// Link functions and data symbols.
// Some data symbols need to be included, such as
// __log_data.
continue
}
// Include in the archive.
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
}
}
} else {
return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err)
return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err)
}
// Close file, to avoid issues with too many open files (especially on
@@ -165,7 +122,7 @@ func makeArchive(arfile *os.File, objs []string) error {
}
// Keep track of the start of the symbol table.
symbolTableStart, err := arfile.Seek(0, io.SeekCurrent)
symbolTableStart, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
@@ -187,7 +144,7 @@ func makeArchive(arfile *os.File, objs []string) error {
// Store the start index, for when we'll update the symbol table with
// the correct file start indices.
offset, err := arfile.Seek(0, io.SeekCurrent)
offset, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil {
return err
}
-91
View File
@@ -1,91 +0,0 @@
package builder
// The well-known conservative Boehm-Demers-Weiser GC.
// This file provides a way to compile this GC for use with TinyGo.
import (
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var BoehmGC = Library{
name: "bdwgc",
cflags: func(target, headerPath string) []string {
libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
flags := []string{
// use a modern environment
"-DUSE_MMAP", // mmap is available
"-DUSE_MUNMAP", // return memory to the OS using munmap
"-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations
"-DNO_EXECUTE_PERMISSION", // don't make the heap executable
// specific flags for TinyGo
"-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go)
"-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment
"-DNO_GETCONTEXT", // musl doesn't support getcontext()
"-DGC_DISABLE_INCREMENTAL", // don't mess with SIGSEGV and such
// Use a minimal environment.
"-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows
"-DDONT_USE_ATEXIT",
"-DNO_GETENV", // smaller binary, more predictable configuration
"-DNO_CLOCK", // don't use system clock
"-DNO_DEBUGGING", // reduce code size
"-DGC_NO_FINALIZATION", // finalization is not used at the moment
// Special flag to work around the lack of __data_start in ld.lld.
// TODO: try to fix this in LLVM/lld directly so we don't have to
// work around it anymore.
"-DGC_DONT_REGISTER_MAIN_STATIC_DATA",
// Do not scan the stack. We have our own mechanism to do this.
"-DSTACK_NOT_SCANNED",
"-DNO_PROC_STAT", // we scan the stack manually (don't read /proc/self/stat on Linux)
"-DSTACKBOTTOM=0", // dummy value, we scan the stack manually
// Assertions can be enabled while debugging GC issues.
//"-DGC_ASSERTIONS",
// We use our own way of dealing with threads (that is a bit hacky).
// See src/runtime/gc_boehm.go.
//"-DGC_THREADS",
//"-DTHREAD_LOCAL_ALLOC",
"-I" + libdir + "/include",
}
return flags
},
needsLibc: true,
sourceDir: func() string {
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
},
librarySources: func(target string, _ bool) ([]string, error) {
sources := []string{
"allchblk.c",
"alloc.c",
"blacklst.c",
"dbg_mlc.c",
"dyn_load.c",
"headers.c",
"mach_dep.c",
"malloc.c",
"mark.c",
"mark_rts.c",
"misc.c",
"new_hblk.c",
"os_dep.c",
"reclaim.c",
}
if strings.Split(target, "-")[2] == "windows" {
// Due to how the linker on Windows works (that doesn't allow
// undefined functions), we need to include these extra files.
sources = append(sources,
"mallocx.c",
"ptr_chck.c",
)
}
return sources, nil
},
}
+141 -296
View File
@@ -14,12 +14,12 @@ import (
"fmt"
"go/types"
"hash/crc32"
"io/fs"
"math/bits"
"os"
"os/exec"
"path/filepath"
"runtime"
"slices"
"sort"
"strconv"
"strings"
@@ -61,10 +61,6 @@ type BuildResult struct {
// correctly printing test results: the import path isn't always the same as
// the path listed on the command line.
ImportPath string
// Map from path to package name. It is needed to attribute binary size to
// the right Go package.
PackagePathMap map[string]string
}
// packageAction is the struct that is serialized to JSON and hashed, to work as
@@ -87,7 +83,8 @@ type packageAction struct {
FileHashes map[string]string // hash of every file that's part of the package
EmbeddedFiles map[string]string // hash of all the //go:embed files in the package
Imports map[string]string // map from imported package to action ID hash
OptLevel string // LLVM optimization level (O0, O1, O2, Os, Oz)
OptLevel int // LLVM optimization level (0-3)
SizeLevel int // LLVM optimization for size level (0-2)
UndefinedGlobals []string // globals that are left as external globals (no initializer)
}
@@ -118,30 +115,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
cacheDir = tmpdir
}
// Create default global values.
globalValues := map[string]map[string]string{
"runtime": {
"buildVersion": goenv.Version(),
},
"testing": {},
}
if config.TestConfig.CompileTestBinary {
// The testing.testBinary is set to "1" when in a test.
// This is needed for testing.Testing() to work correctly.
globalValues["testing"]["testBinary"] = "1"
}
// Copy over explicitly set global values, like
// -ldflags="-X main.Version="1.0"
for pkgPath, vals := range config.Options.GlobalValues {
if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{}
}
for k, v := range vals {
globalValues[pkgPath][k] = v
}
}
// Check for a libc dependency.
// As a side effect, this also creates the headers for the given libc, if
// the libc needs them.
@@ -149,45 +122,35 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var libcDependencies []*compileJob
switch config.Target.Libc {
case "darwin-libSystem":
libcJob := makeDarwinLibSystemJob(config, tmpdir)
libcDependencies = append(libcDependencies, libcJob)
job := makeDarwinLibSystemJob(config, tmpdir)
libcDependencies = append(libcDependencies, job)
case "musl":
var unlock func()
libcJob, unlock, err := libMusl.load(config, tmpdir)
job, unlock, err := Musl.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(libcJob.result), "crt1.o")))
libcDependencies = append(libcDependencies, libcJob)
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
libcDependencies = append(libcDependencies, job)
case "picolibc":
libcJob, unlock, err := libPicolibc.load(config, tmpdir)
libcJob, unlock, err := Picolibc.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc":
libcJob, unlock, err := libWasiLibc.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) {
return BuildResult{}, errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
}
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "wasmbuiltins":
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "mingw-w64":
libcJob, unlock, err := libMinGW.load(config, tmpdir)
_, unlock, err := MinGW.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
unlock()
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...)
case "":
// no library specified, so nothing to do
@@ -195,7 +158,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc)
}
optLevel, speedLevel, sizeLevel := config.OptLevel()
optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{
Triple: config.Triple(),
CPU: config.CPU(),
@@ -203,20 +166,16 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
ABI: config.ABI(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
BuildMode: config.BuildMode(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version(),
TinyGoVersion: goenv.Version,
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
MaxStackAlloc: config.MaxStackAlloc(),
NeedsStackObjects: config.NeedsStackObjects(),
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
Nobounds: config.Options.Nobounds,
PanicStrategy: config.PanicStrategy(),
Debug: true,
}
// Load the target machine, which is the LLVM object that contains all
@@ -229,7 +188,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer machine.Dispose()
// Load entire program AST into memory.
lprogram, err := loader.Load(config, pkgName, types.Config{
lprogram, err := loader.Load(config, pkgName, config.ClangHeaders, types.Config{
Sizes: compiler.Sizes(machine),
})
if err != nil {
@@ -249,12 +208,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return result, err
}
// Store which filesystem paths map to which package name.
result.PackagePathMap = make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
// Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA()
@@ -264,12 +217,26 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var packageJobs []*compileJob
packageActionIDJobs := make(map[string]*compileJob)
if config.Options.GlobalValues["runtime"]["buildVersion"] == "" {
version := goenv.Version
if strings.HasSuffix(goenv.Version, "-dev") && goenv.GitSha1 != "" {
version += "-" + goenv.GitSha1
}
if config.Options.GlobalValues == nil {
config.Options.GlobalValues = make(map[string]map[string]string)
}
if config.Options.GlobalValues["runtime"] == nil {
config.Options.GlobalValues["runtime"] = make(map[string]string)
}
config.Options.GlobalValues["runtime"]["buildVersion"] = version
}
var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string
for name := range globalValues[pkg.Pkg.Path()] {
for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name)
}
sort.Strings(undefinedGlobals)
@@ -282,13 +249,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
allFiles[file.Name] = append(allFiles[file.Name], file)
}
}
// Sort embedded files by name to maintain output determinism.
embedNames := make([]string, 0, len(allFiles))
for _, files := range allFiles {
embedNames = append(embedNames, files[0].Name)
}
slices.Sort(embedNames)
for _, name := range embedNames {
for name, files := range allFiles {
name := name
files := files
job := &compileJob{
description: "make object file for " + name,
run: func(job *compileJob) error {
@@ -303,7 +266,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
sum := sha256.Sum256(data)
hexSum := hex.EncodeToString(sum[:16])
for _, file := range allFiles[name] {
for _, file := range files {
file.Size = uint64(len(data))
file.Hash = hexSum
if file.NeedsData {
@@ -350,6 +313,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals,
}
for filePath, hash := range pkg.FileHashes {
@@ -394,7 +358,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer mod.Context().Dispose()
defer mod.Dispose()
if errs != nil {
return newMultiError(errs, pkg.ImportPath)
return newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after compiling package " + pkg.ImportPath)
@@ -456,15 +420,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if global.IsNil() {
return errors.New("global not found: " + globalName)
}
globalType := global.GlobalValueType()
if globalType.TypeKind() != llvm.StructTypeKind || globalType.StructName() != "runtime._string" {
// Verify this is indeed a string. This is needed so
// that makeGlobalsModule can just create the right
// globals of string type without checking.
return fmt.Errorf("%s: not a string", globalName)
}
name := global.Name()
newGlobal := llvm.AddGlobal(mod, globalType, name+".tmp")
newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal()
newGlobal.SetName(name)
@@ -546,38 +503,12 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return fmt.Errorf("failed to load bitcode file: %w", err)
}
// Resolve duplicate function definitions before linking.
// This can happen when a newer Go version adds a function
// body in a standard library package that was previously
// just a declaration provided by //go:linkname from the
// runtime. In that case, keep the existing (runtime)
// definition by weakening the new one's linkage so the
// LLVM linker discards it in favor of the existing one.
for fn := pkgMod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
if fn.IsDeclaration() {
continue
}
existing := mod.NamedFunction(fn.Name())
if existing.IsNil() || existing.IsDeclaration() {
continue
}
fn.SetLinkage(llvm.LinkOnceODRLinkage)
}
err = llvm.LinkModules(mod, pkgMod)
if err != nil {
return fmt.Errorf("failed to link module: %w", err)
}
}
// Insert values from -ldflags="-X ..." into the IR.
// This is a separate module, so that the "runtime._string" type
// doesn't need to match precisely. LLVM tends to rename that type
// sometimes, leading to errors. But linking in a separate module
// works fine. See:
// https://github.com/tinygo-org/tinygo/issues/4810
globalsMod := makeGlobalsModule(ctx, globalValues, machine)
llvm.LinkModules(mod, globalsMod)
// Create runtime.initAll function that calls the runtime
// initializer of each package.
llvmInitFn := mod.NamedFunction("runtime.initAll")
@@ -589,13 +520,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block)
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path())
}
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "")
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
}
irbuilder.CreateRetVoid()
@@ -644,11 +575,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
},
}
// Create the output directory, if needed
if err := os.MkdirAll(filepath.Dir(outpath), 0777); err != nil {
return result, err
}
// Check whether we only need to create an object file.
// If so, we don't need to link anything and will be finished quickly.
outext := filepath.Ext(outpath)
@@ -705,27 +631,10 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
result.Binary = result.Executable // final file
ldflags := append(config.LDFlags(), "-o", result.Executable)
if config.Options.BuildMode == "c-shared" {
if !strings.HasPrefix(config.Triple(), "wasm32-") {
return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment")
}
ldflags = append(ldflags, "--no-entry")
}
if config.Options.BuildMode == "wasi-legacy" {
if !strings.HasPrefix(config.Triple(), "wasm32-") {
return result, fmt.Errorf("buildmode wasi-legacy is only supported on wasm")
}
if config.Options.Scheduler != "none" {
return result, fmt.Errorf("buildmode wasi-legacy only supports scheduler=none")
}
}
// Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache.
if config.Target.RTLib == "compiler-rt" {
job, unlock, err := libCompilerRT.load(config, tmpdir)
job, unlock, err := CompilerRT.load(config, tmpdir)
if err != nil {
return result, err
}
@@ -733,16 +642,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
linkerDependencies = append(linkerDependencies, job)
}
// The Boehm collector is stored in a separate C library.
if config.GC() == "boehm" {
job, unlock, err := BoehmGC.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
linkerDependencies = append(linkerDependencies, job)
}
// Add jobs to compile extra files. These files are in C or assembly and
// contain things like the interrupt vector table and low level operations
// such as stack switching.
@@ -751,7 +650,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{
description: "compile extra file " + path,
run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands)
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(), config.Options.PrintCommands)
job.result = result
return err
},
@@ -765,7 +664,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.OriginalDir(), filename)
abspath := filepath.Join(pkg.Dir, filename)
job := &compileJob{
description: "compile CGo file " + abspath,
run: func(job *compileJob) error {
@@ -829,21 +728,20 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
ldflags = append(ldflags, dependency.result)
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"-Xlink=/opt:lldlto="+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
} else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"--lto-O"+strconv.Itoa(optLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"--lto-O"+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
@@ -854,7 +752,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if sizeLevel >= 2 {
// Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 19.
// Can hopefully be removed in LLVM 15.
ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
@@ -863,7 +761,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
err = link(config.Target.Linker, ldflags...)
if err != nil {
return err
return &commandError{"failed to link", result.Executable, err}
}
var calculatedStacks []string
@@ -887,12 +785,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return fmt.Errorf("could not modify stack sizes: %w", err)
}
}
// Apply patches of bootloader in the order they appear.
if len(config.Target.BootPatches) > 0 {
err = applyPatches(result.Executable, config.Target.BootPatches)
}
if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(result.Executable)
@@ -903,8 +795,21 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run wasm-opt for wasm binaries
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
optLevel, _, _ := config.OptLevel()
opt := "-" + optLevel
var opt string
switch config.Options.Opt {
case "none", "0":
opt = "-O0"
case "1":
opt = "-O1"
case "2":
opt = "-O2"
case "s":
opt = "-Os"
case "z":
opt = "-Oz"
default:
return fmt.Errorf("unknown opt level: %q", config.Options.Opt)
}
var args []string
@@ -912,20 +817,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
args = append(args, "--asyncify")
}
inputFile := result.Binary
result.Binary = result.Executable + ".wasmopt"
args = append(args,
opt,
"-g",
inputFile,
"--output", result.Binary,
result.Executable,
"--output", result.Executable,
)
wasmopt := goenv.Get("WASMOPT")
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(wasmopt, args...)
}
cmd := exec.Command(wasmopt, args...)
cmd := exec.Command(goenv.Get("WASMOPT"), args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
@@ -935,77 +834,20 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
}
// Run wasm-tools for component-model binaries
witPackage := strings.ReplaceAll(config.Target.WITPackage, "{root}", goenv.Get("TINYGOROOT"))
if config.Options.WITPackage != "" {
witPackage = config.Options.WITPackage
}
witWorld := config.Target.WITWorld
if config.Options.WITWorld != "" {
witWorld = config.Options.WITWorld
}
if witPackage != "" && witWorld != "" {
// wasm-tools component embed -w wasi:cli/command
// $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm
componentEmbedInputFile := result.Binary
result.Binary = result.Executable + ".wasm-component-embed"
args := []string{
"component",
"embed",
"-w", witWorld,
witPackage,
componentEmbedInputFile,
"-o", result.Binary,
}
wasmtools := goenv.Get("WASMTOOLS")
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(wasmtools, args...)
}
cmd := exec.Command(wasmtools, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("`wasm-tools component embed` failed: %w", err)
}
// wasm-tools component new embedded.wasm -o component.wasm
componentNewInputFile := result.Binary
result.Binary = result.Executable + ".wasm-component-new"
args = []string{
"component",
"new",
componentNewInputFile,
"-o", result.Binary,
}
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(wasmtools, args...)
}
cmd = exec.Command(wasmtools, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return fmt.Errorf("`wasm-tools component new` failed: %w", err)
}
}
// Print code size if requested.
if config.Options.PrintSizes != "" {
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
packagePathMap := make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
}
sizes, err := loadProgramSize(result.Executable, packagePathMap)
if err != nil {
return err
}
switch config.Options.PrintSizes {
case "short":
if config.Options.PrintSizes == "short" {
fmt.Printf(" code data bss | flash ram\n")
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM())
case "full":
} else {
if !config.Debug() {
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
}
@@ -1017,13 +859,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
fmt.Printf("------------------------------- | --------------- | -------\n")
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS)
case "html":
const filename = "size-report.html"
err := writeSizeReport(sizes, filename, pkgName)
if err != nil {
return err
}
fmt.Println("Wrote size report to", filename)
}
}
@@ -1064,18 +899,23 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil {
return result, err
}
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
case "esp32", "esp32-img", "esp32c3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM
// bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext)
err := makeESPFirmwareImage(result.Executable, result.Binary, outputBinaryFormat)
err := makeESPFirmareImage(result.Executable, result.Binary, outputBinaryFormat)
if err != nil {
return result, err
}
case "nrf-dfu":
// special format for nrfutil for Nordic chips
tmphexpath := filepath.Join(tmpdir, "main.hex")
err := objcopy(result.Executable, tmphexpath, "hex")
if err != nil {
return result, err
}
result.Binary = filepath.Join(tmpdir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary)
err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary)
if err != nil {
return result, err
}
@@ -1213,11 +1053,29 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
}
}
// Run most of the whole-program optimizations (including the whole
// O0/O1/O2/Os/Oz optimization pipeline).
errs := transform.Optimize(mod, config)
// Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil {
return err
}
// Browsers cannot handle external functions that have type i64 because it
// cannot be represented exactly in JavaScript (JS only has doubles). To
// keep functions interoperable, pass int64 types as pointers to
// stack-allocated values.
if config.WasmAbi() == "js" {
err := transform.ExternalInt64AsPtr(mod, config)
if err != nil {
return err
}
}
// Optimization levels here are roughly the same as Clang, but probably not
// exactly.
optLevel, sizeLevel, inlinerThreshold := config.OptLevels()
errs := transform.Optimize(mod, config, optLevel, sizeLevel, inlinerThreshold)
if len(errs) > 0 {
return newMultiError(errs, "")
return newMultiError(errs)
}
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes")
@@ -1226,19 +1084,10 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
return nil
}
func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, machine llvm.TargetMachine) llvm.Module {
mod := ctx.NewModule("cmdline-globals")
targetData := machine.CreateTargetData()
defer targetData.Dispose()
mod.SetDataLayout(targetData.String())
stringType := ctx.StructCreateNamed("runtime._string")
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
stringType.StructSetBody([]llvm.Type{
llvm.PointerType(ctx.Int8Type(), 0),
uintptrType,
}, false)
// setGlobalValues sets the global values from the -ldflags="-X ..." compiler
// option in the given module. An error may be returned if the global is not of
// the expected type.
func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) error {
var pkgPaths []string
for pkgPath := range globals {
pkgPaths = append(pkgPaths, pkgPath)
@@ -1254,6 +1103,24 @@ func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, m
for _, name := range names {
value := pkg[name]
globalName := pkgPath + "." + name
global := mod.NamedGlobal(globalName)
if global.IsNil() || !global.Initializer().IsNil() {
// The global either does not exist (optimized away?) or has
// some value, in which case it has already been initialized at
// package init time.
continue
}
// A strin is a {ptr, len} pair. We need these types to build the
// initializer.
initializerType := global.GlobalValueType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName)
}
elementTypes := initializerType.StructElementTypes()
if len(elementTypes) != 2 {
return fmt.Errorf("%s: not a string", globalName)
}
// Create a buffer for the string contents.
bufInitializer := mod.Context().ConstString(value, false)
@@ -1264,20 +1131,22 @@ func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, m
buf.SetLinkage(llvm.PrivateLinkage)
// Create the string value, which is a {ptr, len} pair.
length := llvm.ConstInt(uintptrType, uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(stringType, []llvm.Value{
buf,
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName)
}
length := llvm.ConstInt(elementTypes[1], uint64(len(value)), false)
initializer := llvm.ConstNamedStruct(initializerType, []llvm.Value{
ptr,
length,
})
// Create the string global.
global := llvm.AddGlobal(mod, stringType, globalName)
// Set the initializer. No initializer should be set at this point.
global.SetInitializer(initializer)
global.SetAlignment(targetData.PrefTypeAlignment(stringType))
}
}
return mod
return nil
}
// functionStackSizes keeps stack size information about a single function
@@ -1333,7 +1202,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
}
// Goroutines need to be started and finished and take up some stack space
// that way. This can be measured by measuring the stack size of
// that way. This can be measured by measuing the stack size of
// tinygo_startTask.
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
@@ -1496,23 +1365,6 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
}
}
func applyPatches(executable string, bootPatches []string) (err error) {
for _, patch := range bootPatches {
switch patch {
case "rp2040":
err = patchRP2040BootCRC(executable)
// case "rp2350":
// err = patchRP2350BootIMAGE_DEF(executable)
default:
err = errors.New("undefined boot patch name")
}
if err != nil {
return fmt.Errorf("apply boot patch %q: %w", patch, err)
}
}
return nil
}
// RP2040 second stage bootloader CRC32 calculation
//
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
@@ -1524,7 +1376,7 @@ func patchRP2040BootCRC(executable string) error {
}
if len(bytes) != 256 {
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes))
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
}
// From the 'official' RP2040 checksum script:
@@ -1563,10 +1415,3 @@ func lock(path string) func() {
return func() { flock.Close() }
}
func b2u8(b bool) uint8 {
if b {
return 1
}
return 0
}
+10 -22
View File
@@ -8,6 +8,7 @@ import (
"testing"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
@@ -28,17 +29,13 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32s3",
"fe310",
"gameboy-advance",
"k210",
"nintendoswitch",
"riscv-qemu",
"tkey",
"wasip1",
"wasip2",
"wasi",
"wasm",
"wasm-unknown",
}
if hasBuiltinTools {
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
@@ -55,20 +52,12 @@ func TestClangAttributes(t *testing.T) {
for _, options := range []*compileopts.Options{
{GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "amd64"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5"},
{GOOS: "linux", GOARCH: "arm", GOARM: "6"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7"},
{GOOS: "linux", GOARCH: "arm64"},
{GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"},
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"},
{GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"},
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
{GOOS: "darwin", GOARCH: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "windows", GOARCH: "386"},
{GOOS: "windows", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "arm64"},
} {
@@ -76,9 +65,6 @@ func TestClangAttributes(t *testing.T) {
if options.GOARCH == "arm" {
name += ",GOARM=" + options.GOARM
}
if options.GOARCH == "mips" || options.GOARCH == "mipsle" {
name += ",GOMIPS=" + options.GOMIPS
}
t.Run(name, func(t *testing.T) {
testClangAttributes(t, options)
})
@@ -87,6 +73,7 @@ func TestClangAttributes(t *testing.T) {
func testClangAttributes(t *testing.T, options *compileopts.Options) {
testDir := t.TempDir()
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
ctx := llvm.NewContext()
defer ctx.Dispose()
@@ -96,8 +83,9 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
t.Fatalf("could not load target: %s", err)
}
config := compileopts.Config{
Options: options,
Target: target,
Options: options,
Target: target,
ClangHeaders: clangHeaderPath,
}
// Create a very simple C input file.
@@ -109,7 +97,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// Compile this file using Clang.
outpath := filepath.Join(testDir, "test.bc")
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...)
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.
+10 -34
View File
@@ -77,15 +77,6 @@ func ReadBuildID() ([]byte, error) {
}
return raw[4:], nil
}
// Normally we would have found a build ID by now. But not on Nix,
// unfortunately, because Nix adds -no_uuid for some reason:
// https://github.com/NixOS/nixpkgs/issues/178366
// Fall back to the same implementation that we use for Windows.
id, err := readRawGoBuildID(f, 32*1024)
if len(id) != 0 || err != nil {
return id, err
}
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
@@ -97,31 +88,16 @@ func ReadBuildID() ([]byte, error) {
// 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
id, err := readRawGoBuildID(f, 4096)
if len(id) != 0 || err != nil {
return id, err
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 %v", executable)
}
// The Go toolchain stores a build ID in the binary that we can use, as a
// fallback if binary file specific build IDs can't be obtained.
// This function reads that build ID from the binary.
func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) {
fileStart := make([]byte, prefixSize)
_, err := io.ReadFull(f, fileStart)
if err != nil {
return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err)
}
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", f.Name())
}
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, nil
return nil, fmt.Errorf("could not find build ID in %s", executable)
}
+14 -56
View File
@@ -5,18 +5,17 @@ import (
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
)
// These are the GENERIC_SOURCES according to CMakeList.txt except for
// divmodsi4.c and udivmodsi4.c.
// These are the GENERIC_SOURCES according to CMakeList.txt.
var genericBuiltins = []string{
"absvdi2.c",
"absvsi2.c",
"absvti2.c",
"adddf3.c",
"addsf3.c",
"addtf3.c",
"addvdi3.c",
"addvsi3.c",
"addvti3.c",
@@ -41,12 +40,12 @@ var genericBuiltins = []string{
"divdf3.c",
"divdi3.c",
"divmoddi4.c",
//"divmodsi4.c",
"divmodti4.c",
"divsc3.c",
"divsf3.c",
"divsi3.c",
"divtc3.c",
"divti3.c",
"divtf3.c",
"extendsfdf2.c",
"extendhfsf2.c",
"ffsdi2.c",
@@ -92,6 +91,7 @@ var genericBuiltins = []string{
"mulsc3.c",
"mulsf3.c",
"multi3.c",
"multf3.c",
"mulvdi3.c",
"mulvsi3.c",
"mulvti3.c",
@@ -111,11 +111,13 @@ var genericBuiltins = []string{
"popcountti2.c",
"powidf2.c",
"powisf2.c",
"powitf2.c",
"subdf3.c",
"subsf3.c",
"subvdi3.c",
"subvsi3.c",
"subvti3.c",
"subtf3.c",
"trampoline_setup.c",
"truncdfhf2.c",
"truncdfsf2.c",
@@ -124,7 +126,6 @@ var genericBuiltins = []string{
"ucmpti2.c",
"udivdi3.c",
"udivmoddi4.c",
//"udivmodsi4.c",
"udivmodti4.c",
"udivsi3.c",
"udivti3.c",
@@ -133,38 +134,6 @@ var genericBuiltins = []string{
"umodti3.c",
}
// These are the GENERIC_TF_SOURCES as of LLVM 18.
// They are not needed on all platforms (32-bit platforms usually don't need
// these) but they seem to compile fine so it's easier to include them.
var genericBuiltins128 = []string{
"addtf3.c",
"comparetf2.c",
"divtc3.c",
"divtf3.c",
"extenddftf2.c",
"extendhftf2.c",
"extendsftf2.c",
"fixtfdi.c",
"fixtfsi.c",
"fixtfti.c",
"fixunstfdi.c",
"fixunstfsi.c",
"fixunstfti.c",
"floatditf.c",
"floatsitf.c",
"floattitf.c",
"floatunditf.c",
"floatunsitf.c",
"floatuntitf.c",
"multc3.c",
"multf3.c",
"powitf2.c",
"subtf3.c",
"trunctfdf2.c",
"trunctfhf2.c",
"trunctfsf2.c",
}
var aeabiBuiltins = []string{
"arm/aeabi_cdcmp.S",
"arm/aeabi_cdcmpeq_check_nan.c",
@@ -202,17 +171,12 @@ var avrBuiltins = []string{
"avr/udivmodqi4.S",
}
// Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{
"i386/chkstk.S", // also _alloca
}
// libCompilerRT is a library with symbols required by programs compiled with
// LLVM. These symbols are for operations that cannot be emitted with a single
// 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 libCompilerRT = Library{
var CompilerRT = Library{
name: "compiler-rt",
cflags: func(target, headerPath string) []string {
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
@@ -226,19 +190,13 @@ var libCompilerRT = Library{
// Development build.
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins")
},
librarySources: func(target string, _ bool) ([]string, error) {
librarySources: func(target string) ([]string, error) {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
switch compileopts.CanonicalArchName(target) {
case "arm":
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
builtins = append(builtins, aeabiBuiltins...)
case "avr":
}
if strings.HasPrefix(target, "avr") {
builtins = append(builtins, avrBuiltins...)
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
builtins = append(builtins, genericBuiltins128...)
case "i386":
if strings.Split(target, "-")[2] == "windows" {
builtins = append(builtins, windowsI386Builtins...)
}
}
return builtins, nil
},
+31 -65
View File
@@ -1,12 +1,5 @@
//go:build byollvm
// Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp
// This file needs to be updated each LLVM release.
// There are a few small modifications to make, like:
// * ExecuteAssembler is made non-static.
// * The struct AssemblerImplementation is moved to cc1as.h so it can be
// included elsewhere.
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -28,8 +21,8 @@
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h"
@@ -53,6 +46,7 @@
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormattedStream.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Process.h"
@@ -61,10 +55,7 @@
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include <memory>
#include <optional>
#include <system_error>
using namespace clang;
using namespace clang::driver;
@@ -82,10 +73,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Parse the arguments.
const OptTable &OptTbl = getDriverOptTable();
llvm::opt::Visibility VisibilityMask(options::CC1AsOption);
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount;
InputArgList Args =
OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask);
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
IncludedFlagsBitmask);
// Check for missing argument error.
if (MissingArgCount) {
@@ -98,7 +89,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, VisibilityMask) > 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)
@@ -112,14 +103,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) {
VersionTuple Version;
if (Version.tryParse(A->getValue()))
Diags.Report(diag::err_drv_invalid_value)
<< A->getAsString(Args) << A->getValue();
else
Opts.DarwinTargetVariantSDKVersion = Version;
}
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
Opts.Features = Args.getAllArgValues(OPT_target_feature);
@@ -139,11 +122,11 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.CompressDebugSections =
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
.Case("none", llvm::DebugCompressionType::None)
.Case("zlib", llvm::DebugCompressionType::Zlib)
.Case("zstd", llvm::DebugCompressionType::Zstd)
.Case("zlib", llvm::DebugCompressionType::Z)
.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);
@@ -158,7 +141,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
auto Split = StringRef(Arg).split('=');
Opts.DebugPrefixMap.emplace_back(Split.first, Split.second);
Opts.DebugPrefixMap.insert(
{std::string(Split.first), std::string(Split.second)});
}
// Frontend Options
@@ -205,7 +189,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
Opts.NoTypeCheck = Args.hasArg(OPT_mno_type_check);
Opts.RelocationModel =
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
@@ -231,15 +214,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
.Case("default", EmitDwarfUnwindType::Default);
}
Opts.EmitCompactUnwindNonCanonical =
Args.hasArg(OPT_femit_compact_unwind_non_canonical);
Opts.Crel = Args.hasArg(OPT_crel);
Opts.ImplicitMapsyms = Args.hasArg(OPT_mmapsyms_implicit);
Opts.X86RelaxRelocations = !Args.hasArg(OPT_mrelax_relocations_no);
Opts.X86Sse2Avx = Args.hasArg(OPT_msse2avx);
Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file);
return Success;
}
@@ -273,8 +247,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
if (std::error_code EC = Buffer.getError()) {
return Diags.Report(diag::err_fe_error_reading)
<< Opts.InputFile << EC.message();
Error = EC.message();
return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
}
SourceMgr SrcMgr;
@@ -290,16 +264,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
assert(MRI && "Unable to create target register info!");
MCTargetOptions MCOptions;
MCOptions.MCRelaxAll = Opts.RelaxAll;
MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind;
MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical;
MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels;
MCOptions.Crel = Opts.Crel;
MCOptions.ImplicitMapSyms = Opts.ImplicitMapsyms;
MCOptions.X86RelaxRelocations = Opts.X86RelaxRelocations;
MCOptions.X86Sse2Avx = Opts.X86Sse2Avx;
MCOptions.CompressDebugSections = Opts.CompressDebugSections;
MCOptions.AsSecureLogFile = Opts.AsSecureLogFile;
std::unique_ptr<MCAsmInfo> MAI(
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
@@ -307,7 +272,9 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
// may be created with a combination of default and explicit settings.
MAI->setCompressDebugSections(Opts.CompressDebugSections);
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
if (Opts.OutputPath.empty())
@@ -345,8 +312,12 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI(
TheTarget->createMCObjectFileInfo(Ctx, PIC));
if (Opts.DarwinTargetVariantTriple)
MOFI->setDarwinTargetVariantTriple(*Opts.DarwinTargetVariantTriple);
Ctx.setObjectFileInfo(MOFI.get());
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfForAssembly(true);
if (!Opts.DwarfDebugFlags.empty())
@@ -382,10 +353,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.MCNoTypeCheck = Opts.NoTypeCheck;
MCOptions.ShowMCInst = Opts.ShowInst;
MCOptions.AsmVerbose = true;
MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory;
MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
@@ -400,8 +367,10 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP,
std::move(CE), std::move(MAB)));
Str.reset(TheTarget->createAsmStreamer(
Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
Opts.ShowInst));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx));
} else {
@@ -424,15 +393,10 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
Triple T(Opts.Triple);
Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI));
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
Str.get()->initSections(Opts.NoExecStack, *STI);
if (T.isOSBinFormatMachO() && T.isOSDarwin()) {
Triple *TVT = Opts.DarwinTargetVariantTriple
? &*Opts.DarwinTargetVariantTriple
: nullptr;
Str->emitVersionForTarget(T, VersionTuple(), TVT,
Opts.DarwinTargetVariantSDKVersion);
}
}
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
@@ -444,6 +408,9 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
Str.get()->emitZeros(1);
}
// Assembly to object compilation should leverage assembly info.
Str->setUseAssemblerInfoForParsing(true);
bool Failed = false;
std::unique_ptr<MCAsmParser> Parser(
@@ -526,10 +493,9 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
if (Asm.ShowHelp) {
getDriverOptTable().printHelp(
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler", /*ShowHidden=*/false,
/*ShowAllAliases=*/false,
llvm::opt::Visibility(driver::options::CC1AsOption));
"Clang Integrated Assembler",
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
/*ShowAllAliases=*/false);
return 0;
}
+3 -49
View File
@@ -1,6 +1,3 @@
// Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp
// See cc1as.cpp for details.
//===-- cc1as.h - Clang Assembler ----------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -38,19 +35,16 @@ struct AssemblerInvocation {
/// @{
std::vector<std::string> IncludePaths;
LLVM_PREFERRED_TYPE(bool)
unsigned NoInitialTextSection : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned SaveTemporaryLabels : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned GenDwarfForAssembly : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned RelaxELFRelocations : 1;
unsigned Dwarf64 : 1;
unsigned DwarfVersion;
std::string DwarfDebugFlags;
std::string DwarfDebugProducer;
std::string DebugCompilationDir;
llvm::SmallVector<std::pair<std::string, std::string>, 0> DebugPrefixMap;
std::map<const std::string, const std::string> DebugPrefixMap;
llvm::DebugCompressionType CompressDebugSections =
llvm::DebugCompressionType::None;
std::string MainFileName;
@@ -69,9 +63,7 @@ struct AssemblerInvocation {
FT_Obj ///< Object file output.
};
FileType OutputType;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowHelp : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowVersion : 1;
/// @}
@@ -79,48 +71,23 @@ struct AssemblerInvocation {
/// @{
unsigned OutputAsmVariant;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowEncoding : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowInst : 1;
/// @}
/// @name Assembler Options
/// @{
LLVM_PREFERRED_TYPE(bool)
unsigned RelaxAll : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoExecStack : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned FatalWarnings : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoWarn : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoTypeCheck : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned IncrementalLinkerCompatible : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned EmbedBitcode : 1;
/// Whether to emit DWARF unwind info.
EmitDwarfUnwindType EmitDwarfUnwind;
// Whether to emit compact-unwind for non-canonical entries.
// Note: maybe overridden by other constraints.
LLVM_PREFERRED_TYPE(bool)
unsigned EmitCompactUnwindNonCanonical : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Crel : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ImplicitMapsyms : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86RelaxRelocations : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86Sse2Avx : 1;
/// The name of the relocation model to use.
std::string RelocationModel;
@@ -130,14 +97,7 @@ struct AssemblerInvocation {
/// Darwin target variant triple, the variant of the deployment target
/// for which the code is being compiled.
std::optional<llvm::Triple> DarwinTargetVariantTriple;
/// The version of the darwin target variant SDK which was used during the
/// compilation
llvm::VersionTuple DarwinTargetVariantSDKVersion;
/// The name of a file to use with \c .secure_log_unique directives.
std::string AsSecureLogFile;
llvm::Optional<llvm::Triple> DarwinTargetVariantTriple;
/// @}
public:
@@ -154,17 +114,11 @@ public:
NoExecStack = 0;
FatalWarnings = 0;
NoWarn = 0;
NoTypeCheck = 0;
IncrementalLinkerCompatible = 0;
Dwarf64 = 0;
DwarfVersion = 0;
EmbedBitcode = 0;
EmitDwarfUnwind = EmitDwarfUnwindType::Default;
EmitCompactUnwindNonCanonical = false;
Crel = false;
ImplicitMapsyms = 0;
X86RelaxRelocations = 0;
X86Sse2Avx = 0;
}
static bool CreateFromArgs(AssemblerInvocation &Res,
+2 -2
View File
@@ -11,7 +11,7 @@
#include <clang/FrontendTool/Utils.h>
#include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/Option/Option.h>
#include <llvm/TargetParser/Host.h>
#include <llvm/Support/Host.h>
using namespace llvm;
using namespace clang;
@@ -60,7 +60,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
}
// Create the actual diagnostics engine.
Clang->createDiagnostics(*llvm::vfs::getRealFileSystem());
Clang->createDiagnostics();
if (!Clang->hasDiagnostics()) {
return false;
}
+12
View File
@@ -3,6 +3,7 @@ package builder
import (
"errors"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
@@ -75,3 +76,14 @@ func LookupCommand(name string) (string, error) {
}
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()
}
+13 -27
View File
@@ -1,8 +1,8 @@
package builder
import (
"errors"
"fmt"
"runtime"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
@@ -24,40 +24,26 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec.OpenOCDCommands = options.OpenOCDCommands
}
// Version range supported by TinyGo.
const minorMin = 19
const minorMax = 26
goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
// Check that we support this Go toolchain version.
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil {
return nil, err
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 18 || minor > 20 {
return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor)
}
if options.GoCompatibility {
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor)
}
}
// Check that the Go toolchain version isn't too new, if we haven't been
// compiled with the latest Go version.
// This may be a bit too aggressive: if the newer version doesn't change the
// Go language we will most likely be able to compile it.
buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version())
if err != nil {
return nil, err
}
if buildMajor != 1 || buildMinor < gorootMinor {
return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version())
}
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{
Options: options,
Target: spec,
GoMinorVersion: gorootMinor,
GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig,
}, nil
}
+105
View File
@@ -0,0 +1,105 @@
package builder
import (
"errors"
"io/fs"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"tinygo.org/x/go-llvm"
)
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
// multiple locations, which should make it find the directory when installed in
// various ways.
func getClangHeaderPath(TINYGOROOT string) string {
// Check whether we're running from the source directory.
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
if _, err := os.Stat(path); !errors.Is(err, fs.ErrNotExist) {
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) {
return path
}
// It looks like we are built with a system-installed LLVM. Do a last
// attempt: try to use Clang headers relative to the clang binary.
llvmMajor := strings.Split(llvm.Version, ".")[0]
for _, cmdName := range commands["clang"] {
binpath, err := exec.LookPath(cmdName)
if err == nil {
// This should be the command that will also be used by
// execCommand. To avoid inconsistencies, make sure we use the
// headers relative to this command.
binpath, err = filepath.EvalSymlinks(binpath)
if err != nil {
// Unexpected.
return ""
}
// Example executable:
// /usr/lib/llvm-9/bin/clang
// Example include path:
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
llvmRoot := filepath.Dir(filepath.Dir(binpath))
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
// Example include path:
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
if err64 != nil && err32 != nil {
// Unexpected.
continue
}
dirnames := make([]string, len(dirs64)+len(dirs32))
dirCount := 0
for _, d := range dirs32 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
dirCount++
}
}
for _, d := range dirs64 {
name := d.Name()
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
dirCount++
}
}
sort.Strings(dirnames)
// Check for the highest version first.
for i := dirCount - 1; i >= 0; i-- {
path := filepath.Join(dirnames[i], "include")
_, err := os.Stat(filepath.Join(path, "stdint.h"))
if err == nil {
return path
}
}
}
}
// 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 ""
}
+5 -7
View File
@@ -3,8 +3,7 @@ package builder
// MultiError is a list of multiple errors (actually: diagnostics) returned
// during LLVM IR generation.
type MultiError struct {
ImportPath string
Errs []error
Errs []error
}
func (e *MultiError) Error() string {
@@ -15,16 +14,15 @@ func (e *MultiError) Error() string {
// newMultiError returns a *MultiError if there is more than one error, or
// returns that error directly when there is only one. Passing an empty slice
// will return nil (because there is no error).
// The importPath may be passed if this error is for a single package.
func newMultiError(errs []error, importPath string) error {
// will lead to a panic.
func newMultiError(errs []error) error {
switch len(errs) {
case 0:
return nil
panic("attempted to create empty MultiError")
case 1:
return errs[0]
default:
return &MultiError{importPath, errs}
return &MultiError{errs}
}
}
+3 -4
View File
@@ -23,7 +23,7 @@ type espImageSegment struct {
data []byte
}
// makeESPFirmwareImage converts an input ELF file to an image file for an ESP32 or
// 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.
//
@@ -31,7 +31,7 @@ type espImageSegment struct {
// 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 makeESPFirmwareImage(infile, outfile, format string) error {
func makeESPFirmareImage(infile, outfile, format string) error {
inf, err := elf.Open(infile)
if err != nil {
return err
@@ -100,12 +100,11 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{
"esp32": 0x0000,
"esp32c3": 0x0005,
"esp32s3": 0x0009,
}[chip]
// Image header.
switch chip {
case "esp32", "esp32c3", "esp32s3":
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
+17 -7
View File
@@ -17,6 +17,14 @@ import (
// 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.
@@ -47,11 +55,12 @@ func dummyCompileJob(result string) *compileJob {
// 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.
// 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 run 0 jobs at a time")
return errors.New("cannot 0 jobs at a time")
}
// Create a slice of jobs to run, where all dependencies are run in order.
@@ -72,10 +81,10 @@ func runJobs(job *compileJob, sema chan struct{}) error {
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
dependents := make(map[*compileJob][]*compileJob, len(jobs))
compileJobs := make(map[*compileJob]int)
jidx := make(map[*compileJob]int)
var ready intHeap
for i, job := range jobs {
compileJobs[job] = i
jidx[job] = i
if len(job.dependencies) == 0 {
// This job is ready to run.
ready.Push(i)
@@ -96,7 +105,8 @@ func runJobs(job *compileJob, sema chan struct{}) error {
// 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.
// Send each job in the jobs slice to a worker, taking care of job
// dependencies.
numRunningJobs := 0
var totalTime time.Duration
start := time.Now()
@@ -124,7 +134,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
numRunningJobs--
<-sema
if jobRunnerDebug {
fmt.Println("## finished:", completed.description, "(time "+completed.duration.String()+")")
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
}
if completed.err != nil {
// Wait for any current jobs to finish.
@@ -146,7 +156,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
delete(wait, completed)
if len(wait) == 0 {
// This job is now ready to run.
ready.Push(compileJobs[j])
ready.Push(jidx[j])
delete(waiting, j)
}
}
+38 -62
View File
@@ -15,9 +15,6 @@ import (
// Library is a container for information about a single C library, such as a
// compiler runtime or libc.
//
// Note: whenever a library gets changed, the version in compileopts/config.go
// probably also needs to be incremented.
type Library struct {
// The library name, such as compiler-rt or picolibc.
name string
@@ -28,22 +25,29 @@ type Library struct {
// cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string
// cflagsForFile returns additional C flags for a particular source file.
cflagsForFile func(path string) []string
// needsLibc is set to true if this library needs libc headers.
needsLibc bool
// The source directory.
sourceDir func() string
// The source files, relative to sourceDir.
librarySources func(target string, libcNeedsMalloc bool) ([]string, error)
librarySources func(target string) ([]string, error)
// 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
@@ -53,8 +57,13 @@ type Library struct {
// 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 := config.LibraryPath(l.name)
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.
@@ -134,10 +143,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run.
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir)
}
cpu := config.CPU()
if cpu != "" {
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
@@ -153,40 +158,31 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
if config.ABI() != "" {
args = append(args, "-mabi="+config.ABI())
}
switch compileopts.CanonicalArchName(target) {
case "arm":
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")
}
case "avr":
}
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")
case "riscv32":
args = append(args, "-march="+riscvMarch(config, "rv32imac"), "-fforce-enable-int128")
case "riscv64":
args = append(args, "-march="+riscvMarch(config, "rv64gc"))
case "mips":
args = append(args, "-fno-pic")
}
if config.Target.SoftFloat {
// Use softfloat instead of floating point instructions. This is
// supported on many architectures.
args = append(args, "-msoft-float")
} else {
if strings.HasPrefix(target, "armv5") {
// On ARMv5 we need to explicitly enable hardware floating point
// instructions: Clang appears to assume the hardware doesn't have a
// FPU otherwise.
args = append(args, "-mfpu=vfpv2")
}
if strings.HasPrefix(target, "riscv32-") {
args = append(args, "-march=rv32imac", "-fforce-enable-int128")
}
if l.needsLibc {
args = append(args, config.LibcCFlags()...)
if strings.HasPrefix(target, "riscv64-") {
args = append(args, "-march=rv64gc")
}
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
@@ -226,13 +222,12 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first.
paths, err := l.librarySources(target, config.LibcNeedsMalloc())
paths, err := l.librarySources(target)
if err != nil {
return nil, nil, err
}
for _, path := range paths {
// Strip leading "../" parts off the path.
path := path
cleanpath := path
for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:]
@@ -241,14 +236,11 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath)
objfile := &compileJob{
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
compileArgs = append(compileArgs, args...)
if l.cflagsForFile != nil {
compileArgs = append(compileArgs, l.cflagsForFile(path)...)
}
compileArgs = append(compileArgs, "-o", objpath, srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
@@ -259,8 +251,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}
return nil
},
}
job.dependencies = append(job.dependencies, objfile)
})
}
// Create crt1.o job, if needed.
@@ -269,7 +260,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// won't make much of a difference in speed).
if l.crt1Source != "" {
srcpath := filepath.Join(sourceDir, l.crt1Source)
crt1Job := &compileJob{
job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath,
run: func(*compileJob) error {
var compileArgs []string
@@ -289,8 +280,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
}
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
},
}
job.dependencies = append(job.dependencies, crt1Job)
})
}
ok = true
@@ -298,17 +288,3 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
once.Do(unlock)
}, nil
}
// riscvMarch returns the -march value for RISC-V library compilation.
// It extracts the value from the target's cflags if present, otherwise
// falls back to the provided default. This ensures libraries are compiled
// with the correct ISA extensions for each target (e.g. rv32imc for
// ESP32-C3 which lacks the atomic extension).
func riscvMarch(config *compileopts.Config, defaultMarch string) string {
for _, flag := range config.Target.CFlags {
if strings.HasPrefix(flag, "-march=") {
return flag[len("-march="):]
}
}
return defaultMarch
}
+17 -20
View File
@@ -3,30 +3,27 @@
// This file provides C wrappers for liblld.
#include <lld/Common/Driver.h>
#include <llvm/Support/Parallel.h>
LLD_HAS_DRIVER(coff)
LLD_HAS_DRIVER(elf)
LLD_HAS_DRIVER(mingw)
LLD_HAS_DRIVER(macho)
LLD_HAS_DRIVER(wasm)
static void configure() {
#if _WIN64
// This is a hack to work around a hang in the LLD linker on Windows, with
// -DLLVM_ENABLE_THREADS=ON. It has a similar effect as the -threads=1
// linker flag, but with support for the COFF linker.
llvm::parallel::strategy = llvm::hardware_concurrency(1);
#endif
}
extern "C" {
bool tinygo_link(int argc, char **argv) {
configure();
bool tinygo_link_elf(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
lld::Result r = lld::lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS);
return !r.retCode;
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);
}
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);
}
} // external "C"
+28 -98
View File
@@ -10,7 +10,7 @@ import (
"github.com/tinygo-org/tinygo/goenv"
)
var libMinGW = Library{
var MinGW = Library{
name: "mingw-w64",
makeHeaders: func(target, includeDir string) error {
// copy _mingw.h
@@ -27,67 +27,14 @@ var libMinGW = Library{
_, err = io.Copy(outf, inf)
return err
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") },
sourceDir: func() string { return "" }, // unused
cflags: func(target, headerPath string) []string {
mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64")
flags := []string{
"-nostdlibinc",
"-isystem", mingwDir + "/mingw-w64-crt/include",
"-isystem", mingwDir + "/mingw-w64-headers/crt",
"-isystem", mingwDir + "/mingw-w64-headers/include",
"-I", mingwDir + "/mingw-w64-headers/defaults/include",
"-I" + headerPath,
}
if strings.Split(target, "-")[0] == "i386" {
flags = append(flags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
"-D_CRTBLD",
"-Wno-pragma-pack",
)
}
return flags
// No flags necessary because there are no files to compile.
return nil
},
librarySources: func(target string, _ bool) ([]string, error) {
// These files are needed so that printf and the like are supported.
var sources []string
if strings.Split(target, "-")[0] == "i386" {
// Old 32-bit x86 systems use msvcrt.dll.
sources = []string{
"mingw-w64-crt/crt/pseudo-reloc.c",
"mingw-w64-crt/gdtoa/dmisc.c",
"mingw-w64-crt/gdtoa/gdtoa.c",
"mingw-w64-crt/gdtoa/gmisc.c",
"mingw-w64-crt/gdtoa/misc.c",
"mingw-w64-crt/math/x86/exp2.S",
"mingw-w64-crt/math/x86/trunc.S",
"mingw-w64-crt/misc/___mb_cur_max_func.c",
"mingw-w64-crt/misc/lc_locale_func.c",
"mingw-w64-crt/misc/mbrtowc.c",
"mingw-w64-crt/misc/strnlen.c",
"mingw-w64-crt/misc/wcrtomb.c",
"mingw-w64-crt/misc/wcsnlen.c",
"mingw-w64-crt/stdio/acrt_iob_func.c",
"mingw-w64-crt/stdio/mingw_lock.c",
"mingw-w64-crt/stdio/mingw_pformat.c",
"mingw-w64-crt/stdio/mingw_vfprintf.c",
"mingw-w64-crt/stdio/mingw_vsnprintf.c",
}
} else {
// Anything somewhat modern (amd64, arm64) uses UCRT.
sources = []string{
"mingw-w64-crt/stdio/ucrt_fprintf.c",
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
"mingw-w64-crt/stdio/ucrt_printf.c",
"mingw-w64-crt/stdio/ucrt_snprintf.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
}
}
return sources, nil
librarySources: func(target string) ([]string, error) {
// We only use the UCRT DLL file. No source files necessary.
return nil, nil
},
}
@@ -100,41 +47,27 @@ var libMinGW = Library{
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
var jobs []*compileJob
root := goenv.Get("TINYGOROOT")
var libs []string
if goarch == "386" {
libs = []string{
// x86 uses msvcrt.dll instead of UCRT for compatibility with old
// Windows versions.
"advapi32.def.in",
"kernel32.def.in",
"msvcrt.def.in",
}
} else {
// Use the modernized UCRT on new systems.
// 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.
libs = []string{
"advapi32.def.in",
"kernel32.def.in",
"api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def.in",
"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",
}
}
for _, name := range libs {
// 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.in",
"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{
@@ -144,9 +77,6 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
defpath := inpath
var archDef, emulation string
switch goarch {
case "386":
archDef = "-DDEF_I386"
emulation = "i386pe"
case "amd64":
archDef = "-DDEF_X64"
emulation = "i386pep"
+39 -67
View File
@@ -6,52 +6,15 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm"
)
// Create the alltypes.h file from the appropriate alltypes.h.in files.
func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(outputBitsDir, "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)
}
_, err := f.WriteString(line + "\n")
if err != nil {
return err
}
}
}
return f.Close()
}
var libMusl = Library{
var Musl = Library{
name: "musl",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
@@ -63,13 +26,41 @@ var libMusl = Library{
arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
err = buildMuslAllTypes(arch, muslDir, bits)
// 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"))
f, err = os.Create(filepath.Join(bits, "syscall.h"))
if err != nil {
return err
}
@@ -102,9 +93,6 @@ var libMusl = Library{
"-Wno-string-plus-int",
"-Wno-ignored-pragmas",
"-Wno-tautological-constant-out-of-range-compare",
"-Wno-deprecated-non-prototype",
"-Wno-format",
"-Wno-parentheses",
"-Qunused-arguments",
// Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications),
@@ -118,58 +106,42 @@ var libMusl = Library{
"-I" + muslDir + "/include",
"-fno-stack-protector",
}
llvmMajor, _ := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if llvmMajor >= 15 {
// This flag was added in Clang 15. It is not present in LLVM 14.
cflags = append(cflags, "-Wno-deprecated-non-prototype")
}
return cflags
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
librarySources: func(target string, _ bool) ([]string, error) {
librarySources: func(target string) ([]string, error) {
arch := compileopts.MuslArchitecture(target)
globs := []string{
"conf/*.c",
"ctype/*.c",
"env/*.c",
"errno/*.c",
"exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c",
"internal/intscan.c",
"internal/libc.c",
"internal/shgetc.c",
"internal/syscall_ret.c",
"internal/vdso.c",
"legacy/*.c",
"locale/*.c",
"linux/*.c",
"locale/*.c",
"malloc/*.c",
"malloc/mallocng/*.c",
"mman/*.c",
"math/*.c",
"misc/*.c",
"multibyte/*.c",
"sched/*.c",
"signal/" + arch + "/*.s",
"signal/*.c",
"stdio/*.c",
"stdlib/*.c",
"string/*.c",
"thread/" + arch + "/*.s",
"thread/*.c",
"time/*.c",
"unistd/*.c",
"process/*.c",
}
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
if arch != "aarch64" && arch != "mips" {
//aarch64 and mips have no architecture specific code, either they
// are not supported or don't need any?
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
}
var sources []string
seenSources := map[string]struct{}{}
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
+13 -103
View File
@@ -1,117 +1,27 @@
package builder
import (
"archive/zip"
"bytes"
"encoding/binary"
"encoding/json"
"os"
"fmt"
"io"
"os/exec"
"github.com/sigurn/crc16"
"github.com/tinygo-org/tinygo/compileopts"
)
// Structure of the manifest.json file.
type jsonManifest struct {
Manifest struct {
Application struct {
BinaryFile string `json:"bin_file"`
DataFile string `json:"dat_file"`
InitPacketData nrfInitPacket `json:"init_packet_data"`
} `json:"application"`
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
} `json:"manifest"`
}
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
// Structure of the init packet.
// Source:
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
type nrfInitPacket struct {
ApplicationVersion uint32 `json:"application_version"`
DeviceRevision uint16 `json:"device_revision"`
DeviceType uint16 `json:"device_type"`
FirmwareCRC16 uint16 `json:"firmware_crc16"`
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
}
// Create the init packet (the contents of application.dat).
func (p nrfInitPacket) createInitPacket() []byte {
buf := &bytes.Buffer{}
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
return buf.Bytes()
}
// Make a Nordic DFU firmware image from an ELF file.
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
// Read ELF file as input and convert it to a binary image file.
_, data, err := extractROM(infile)
if err != nil {
return err
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:]...)
}
// Create the zip file in memory.
// It won't be very large anyway.
buf := &bytes.Buffer{}
w := zip.NewWriter(buf)
// Write the application binary to the zip file.
binw, err := w.Create("application.bin")
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
cmd.Stdout = io.Discard
err := cmd.Run()
if err != nil {
return err
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
}
_, err = binw.Write(data)
if err != nil {
return err
}
// Create the init packet.
initPacket := nrfInitPacket{
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
}
// Write the init packet to the zip file.
datw, err := w.Create("application.dat")
if err != nil {
return err
}
_, err = datw.Write(initPacket.createInitPacket())
if err != nil {
return err
}
// Create the JSON manifest.
manifest := &jsonManifest{}
manifest.Manifest.Application.BinaryFile = "application.bin"
manifest.Manifest.Application.DataFile = "application.dat"
manifest.Manifest.Application.InitPacketData = initPacket
manifest.Manifest.DFUVersion = 0.5
// Write the JSON manifest to the file.
jsonw, err := w.Create("manifest.json")
if err != nil {
return err
}
enc := json.NewEncoder(jsonw)
enc.SetIndent("", " ")
err = enc.Encode(manifest)
if err != nil {
return err
}
// Finish the zip file.
err = w.Close()
if err != nil {
return err
}
return os.WriteFile(outfile, buf.Bytes(), 0o666)
return nil
}
+82 -104
View File
@@ -3,14 +3,13 @@ package builder
import (
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// libPicolibc is a C library for bare metal embedded devices. It was originally
// Picolibc is a C library for bare metal embedded devices. It was originally
// based on newlib.
var libPicolibc = Library{
var Picolibc = Library{
name: "picolibc",
makeHeaders: func(target, includeDir string) error {
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
@@ -29,12 +28,10 @@ var libPicolibc = Library{
"-D_HAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default
"-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
"-nostdlibinc",
"-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio",
@@ -43,24 +40,92 @@ var libPicolibc = Library{
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
librarySources: func(target string, _ bool) ([]string, error) {
sources := append([]string(nil), picolibcSources...)
if !strings.HasPrefix(target, "avr") {
// Small chips without long jumps can't compile many files (printf,
// pow, etc). Therefore exclude those source files for those chips.
// Unfortunately it's difficult to exclude only some chips, so this
// excludes those files on all AVR chips for now.
// More information:
// https://github.com/llvm/llvm-project/issues/67042
sources = append(sources, picolibcSourcesLarge...)
}
return sources, nil
librarySources: func(target string) ([]string, error) {
return picolibcSources, nil
},
}
var picolibcSources = []string{
"../../picolibc-stdio.c",
// srcs_tinystdio
"libc/tinystdio/asprintf.c",
"libc/tinystdio/bufio.c",
"libc/tinystdio/clearerr.c",
"libc/tinystdio/ecvt_r.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvtf_r.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvt_r.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fcvtf_r.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/fclose.c",
"libc/tinystdio/fdevopen.c",
"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/filestrput.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/fmemopen.c",
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
//"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/fseeko.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftello.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/mktemp.c",
"libc/tinystdio/perror.c",
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/rewind.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setbuffer.c",
"libc/tinystdio/setlinebuf.c",
"libc/tinystdio/setvbuf.c",
"libc/tinystdio/snprintf.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strtof.c",
"libc/tinystdio/strtof_l.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libc/string/bcmp.c",
"libc/string/bcopy.c",
"libc/string/bzero.c",
@@ -164,87 +229,6 @@ var picolibcSources = []string{
"libc/string/wmempcpy.c",
"libc/string/wmemset.c",
"libc/string/xpg_strerror_r.c",
}
// Parts of picolibc that are too large for small AVRs.
var picolibcSourcesLarge = []string{
// srcs_tinystdio
"libc/tinystdio/asprintf.c",
"libc/tinystdio/bufio.c",
"libc/tinystdio/clearerr.c",
"libc/tinystdio/ecvt_r.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvtf_r.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvt_r.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fcvtf_r.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/fclose.c",
"libc/tinystdio/fdevopen.c",
"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/filestrput.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/fmemopen.c",
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
//"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/fseeko.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftello.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/mktemp.c",
"libc/tinystdio/perror.c",
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/rewind.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setbuffer.c",
"libc/tinystdio/setlinebuf.c",
"libc/tinystdio/setvbuf.c",
"libc/tinystdio/snprintf.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strtof.c",
"libc/tinystdio/strtof_l.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libm/common/sf_finite.c",
"libm/common/sf_copysign.c",
@@ -339,12 +323,6 @@ var picolibcSourcesLarge = []string{
"libm/common/math_err_may_uflow.c",
"libm/common/math_err_check_uflow.c",
"libm/common/math_err_check_oflow.c",
"libm/common/math_errf_divzerof.c",
"libm/common/math_errf_invalidf.c",
"libm/common/math_errf_may_uflowf.c",
"libm/common/math_errf_oflowf.c",
"libm/common/math_errf_uflowf.c",
"libm/common/math_errf_with_errnof.c",
"libm/common/math_inexact.c",
"libm/common/math_inexactf.c",
"libm/common/log.c",
-56
View File
@@ -1,56 +0,0 @@
package builder
import (
_ "embed"
"fmt"
"html/template"
"os"
)
//go:embed size-report.html
var sizeReportBase string
func writeSizeReport(sizes *programSize, filename, pkgName string) error {
tmpl, err := template.New("report").Parse(sizeReportBase)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("could not open report file: %w", err)
}
defer f.Close()
// Prepare data for the report.
type sizeLine struct {
Name string
Size *packageSize
}
programData := []sizeLine{}
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
programData = append(programData, sizeLine{
Name: name,
Size: pkgSize,
})
}
sizeTotal := map[string]uint64{
"code": sizes.Code,
"rodata": sizes.ROData,
"data": sizes.Data,
"bss": sizes.BSS,
"flash": sizes.Flash(),
}
// Write the report.
err = tmpl.Execute(f, map[string]any{
"pkgName": pkgName,
"sizes": programData,
"sizeTotal": sizeTotal,
})
if err != nil {
return fmt.Errorf("could not create report file: %w", err)
}
return nil
}
-109
View File
@@ -1,109 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Size Report for {{.pkgName}}</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
.table-vertical-border {
border-left: calc(var(--bs-border-width) * 2) solid currentcolor;
}
/* Hover on only the rows that are clickable. */
.row-package:hover > * {
--bs-table-color-state: var(--bs-table-hover-color);
--bs-table-bg-state: var(--bs-table-hover-bg);
}
</style>
</head>
<body>
<div class="container-xxl">
<h1>Size Report for {{.pkgName}}</h1>
<p>How much space is used by Go packages, C libraries, and other bits to set up the program environment.</p>
<ul>
<li><strong>Code</strong> is the actual program code (machine code instructions).</li>
<li><strong>Read-only data</strong> are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.</li>
<li><strong>Data</strong> are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.</li>
<li><strong>BSS</strong> are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.</li>
</ul>
<p>The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using <code>-no-debug</code> strips most of those.</p>
<h2>Program breakdown</h2>
<p>You can click on the rows below to see which files contribute to the binary size.</p>
<div class="table-responsive">
<table class="table w-auto">
<thead>
<tr>
<th>Package</th>
<th class="table-vertical-border">Code</th>
<th>Read-only data</th>
<th>Data</th>
<th title="zero-initialized data">BSS</th>
<th class="table-vertical-border" style="min-width: 16em">Binary size</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{range $i, $pkg := .sizes}}
<tr class="row-package" data-collapse=".collapse-row-{{$i}}">
<td>{{.Name}}</td>
<td class="table-vertical-border">{{.Size.Code}}</td>
<td>{{.Size.ROData}}</td>
<td>{{.Size.Data}}</td>
<td>{{.Size.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{.Size.FlashPercent}}%, var(--bs-table-bg) {{.Size.FlashPercent}}%)">
{{.Size.Flash}}
</td>
</tr>
{{range $filename, $sizes := .Size.Sub}}
<tr class="table-secondary collapse collapse-row-{{$i}}">
<td class="ps-4">
{{if eq $filename ""}}
(unknown file)
{{else}}
{{$filename}}
{{end}}
</td>
<td class="table-vertical-border">{{$sizes.Code}}</td>
<td>{{$sizes.ROData}}</td>
<td>{{$sizes.Data}}</td>
<td>{{$sizes.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{$sizes.FlashPercent}}%, var(--bs-table-bg) {{$sizes.FlashPercent}}%)">
{{$sizes.Flash}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
<tfoot class="table-group-divider">
<tr>
<th>Total</th>
<td class="table-vertical-border">{{.sizeTotal.code}}</td>
<td>{{.sizeTotal.rodata}}</td>
<td>{{.sizeTotal.data}}</td>
<td>{{.sizeTotal.bss}}</td>
<td class="table-vertical-border">{{.sizeTotal.flash}}</td>
</tr>
</tfoot>
</table>
</div>
</div>
<script>
// Make table rows toggleable to show filenames.
for (let clickable of document.querySelectorAll('.row-package')) {
clickable.addEventListener('click', e => {
for (let row of document.querySelectorAll(clickable.dataset.collapse)) {
row.classList.toggle('show');
}
});
}
</script>
</body>
</html>
+70 -207
View File
@@ -12,7 +12,6 @@ import (
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
@@ -25,7 +24,7 @@ const sizesDebug = false
// programSize contains size statistics per package of a compiled program.
type programSize struct {
Packages map[string]*packageSize
Packages map[string]packageSize
Code uint64
ROData uint64
Data uint64
@@ -53,29 +52,13 @@ func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Return the package size information for a given package path, creating it if
// it doesn't exist yet.
func (ps *programSize) getPackage(path string) *packageSize {
if field, ok := ps.Packages[path]; ok {
return field
}
field := &packageSize{
Program: ps,
Sub: map[string]*packageSize{},
}
ps.Packages[path] = field
return field
}
// packageSize contains the size of a package, calculated from the linked object
// file.
type packageSize struct {
Program *programSize
Code uint64
ROData uint64
Data uint64
BSS uint64
Sub map[string]*packageSize
Code uint64
ROData uint64
Data uint64
BSS uint64
}
// Flash usage in regular microcontrollers.
@@ -88,36 +71,10 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS
}
// Flash usage in regular microcontrollers, as a percentage of the total flash
// usage of the program.
func (ps *packageSize) FlashPercent() float64 {
return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100
}
// Add a single size data point to this package.
// This must only be called while calculating package size, not afterwards.
func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) {
if size == 0 {
return
}
// Add size for the package.
*getField(ps, isVariable) += size
// Add size for file inside package.
sub, ok := ps.Sub[filename]
if !ok {
sub = &packageSize{Program: ps.Program}
ps.Sub[filename] = sub
}
*getField(sub, isVariable) += size
}
// 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
Align uint64 // (maximum) alignment of this line
File string // file path as stored in DWARF
IsVariable bool // true if this is a variable (or constant), false if it is code
}
@@ -129,7 +86,6 @@ type memorySection struct {
Type memoryType
Address uint64
Size uint64
Align uint64
}
type memoryType int
@@ -161,13 +117,13 @@ var (
// alloc: heap allocations during init interpretation
// pack: data created when storing a constant in an interface for example
// string: buffer behind strings
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`)
)
// 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, codeAlignment uint64, skipTombstone bool) ([]addressLine, error) {
func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset uint64, skipTombstone bool) ([]addressLine, error) {
r := data.Reader()
var lines []*dwarf.LineFile
var addresses []addressLine
@@ -236,22 +192,10 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to
// lineEntry.
path := prevLineEntry.File.Name
if runtime.GOOS == "windows" {
// Work around a Clang bug on Windows:
// https://github.com/llvm/llvm-project/issues/117317
path = strings.ReplaceAll(path, "\\\\", "\\")
// wasi-libc likes to use forward slashes, but we
// canonicalize everything to use backwards slashes as
// is common on Windows.
path = strings.ReplaceAll(path, "/", "\\")
}
line := addressLine{
Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address,
Align: codeAlignment,
File: path,
File: prevLineEntry.File.Name,
}
if line.Length != 0 {
addresses = append(addresses, line)
@@ -275,9 +219,20 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
// 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.
addr, err := readDWARFConstant(r.AddressSize(), location.Val.([]uint8))
if err != nil {
continue // ignore the error, we don't know what to do with it
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)
@@ -288,16 +243,9 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
return nil, err
}
// Read alignment, if it's stored as part of the debug information.
var alignment uint64
if attr := e.AttrField(dwarf.AttrAlignment); attr != nil {
alignment = uint64(attr.Val.(int64))
}
addresses = append(addresses, addressLine{
Address: addr,
Length: uint64(typ.Size()),
Align: alignment,
File: lines[file.Val.(int64)].Name,
IsVariable: true,
})
@@ -308,52 +256,6 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
return addresses, nil
}
// Parse a DWARF constant. For addresses, this is usually a very simple
// expression.
func readDWARFConstant(addressSize int, bytecode []byte) (uint64, error) {
var addr uint64
for len(bytecode) != 0 {
op := bytecode[0]
bytecode = bytecode[1:]
switch op {
case 0x03: // DW_OP_addr
switch addressSize {
case 2:
addr = uint64(binary.LittleEndian.Uint16(bytecode))
case 4:
addr = uint64(binary.LittleEndian.Uint32(bytecode))
case 8:
addr = binary.LittleEndian.Uint64(bytecode)
default:
panic("unexpected address size")
}
bytecode = bytecode[addressSize:]
case 0x23: // DW_OP_plus_uconst
offset, n := readULEB128(bytecode)
addr += offset
bytecode = bytecode[n:]
default:
return 0, fmt.Errorf("unknown DWARF opcode: 0x%x", op)
}
}
return addr, nil
}
// Source: https://en.wikipedia.org/wiki/LEB128#Decode_unsigned_integer
func readULEB128(buf []byte) (result uint64, n int) {
var shift uint8
for {
b := buf[n]
n++
result |= uint64(b&0x7f) << shift
if b&0x80 == 0 {
break
}
shift += 7
}
return
}
// 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) {
@@ -375,7 +277,7 @@ func readMachOSymbolAddresses(path string) (map[string]int, []addressLine, error
if err != nil {
return nil, nil, err
}
lines, err := readProgramSizeFromDWARF(dwarf, 0, 0, false)
lines, err := readProgramSizeFromDWARF(dwarf, 0, false)
if err != nil {
return nil, nil, err
}
@@ -432,15 +334,10 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Load the binary file, which could be in a number of file formats.
var sections []memorySection
if file, err := elf.NewFile(f); err == nil {
var codeAlignment uint64
switch file.Machine {
case elf.EM_ARM:
codeAlignment = 4 // usually 2, but can be 4
}
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0, codeAlignment, true)
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.
@@ -474,7 +371,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
if section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if packageSymbolRegexp.MatchString(symbol.Name) || symbol.Name == "__isr_vector" {
if packageSymbolRegexp.MatchString(symbol.Name) {
addresses = append(addresses, addressLine{
Address: symbol.Value,
Length: symbol.Size,
@@ -490,7 +387,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
continue
}
if section.Type == elf.SHT_NOBITS {
if strings.HasPrefix(section.Name, ".stack") {
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
@@ -498,7 +395,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Align: section.Addralign,
Type: memoryStack,
})
} else {
@@ -506,7 +402,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Align: section.Addralign,
Type: memoryBSS,
})
}
@@ -515,7 +410,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Align: section.Addralign,
Type: memoryCode,
})
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
@@ -523,7 +417,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Align: section.Addralign,
Type: memoryData,
})
} else if section.Type == elf.SHT_PROGBITS {
@@ -531,7 +424,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: section.Size,
Align: section.Addralign,
Type: memoryROData,
})
}
@@ -558,7 +450,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryCode,
})
} else if sectionType == 1 { // S_ZEROFILL
@@ -566,7 +457,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryBSS,
})
} else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data)
@@ -574,7 +464,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryROData,
})
} else {
@@ -582,7 +471,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{
Address: section.Addr,
Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryData,
})
}
@@ -666,7 +554,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0, 0, true)
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.
@@ -738,7 +626,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF()
if data != nil {
addresses, err = readProgramSizeFromDWARF(data, codeOffset, 0, true)
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.
@@ -826,40 +714,49 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Now finally determine the binary/RAM size usage per package by going
// through each allocated section.
sizes := make(map[string]*packageSize)
program := &programSize{
Packages: sizes,
}
sizes := make(map[string]packageSize)
for _, section := range sections {
switch section.Type {
case memoryCode:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
if isVariable {
return &ps.ROData
field.ROData += size
} else {
field.Code += size
}
return &ps.Code
sizes[path] = field
}, packagePathMap)
case memoryROData:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.ROData
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, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.Data
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, program, func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
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.
program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 {
return &ps.BSS
}, "", section.Size, false)
sizes["C stack"] = packageSize{
BSS: section.Size,
}
}
}
// ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes {
program.Code += pkg.Code
program.ROData += pkg.ROData
@@ -870,8 +767,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
}
// readSection determines for each byte in this section to which package it
// belongs.
func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) {
// 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
@@ -889,18 +786,10 @@ func readSection(section memorySection, addresses []addressLine, program *progra
if addr < line.Address {
// There is a gap: there is a space between the current and the
// previous line entry.
// Check whether this is caused by alignment requirements.
addrAligned := (addr + line.Align - 1) &^ (line.Align - 1)
if line.Align > 1 && addrAligned >= line.Address {
// It is, assume that's what causes the gap.
program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true)
} else {
program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
}
addSize("(unknown)", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap)\n", addr, line.Address, line.Address-addr)
}
addr = line.Address
}
if addr > line.Address+line.Length {
// The current line is already covered by a previous line entry.
@@ -917,56 +806,32 @@ func readSection(section memorySection, addresses []addressLine, program *progra
length = line.Length - (addr - line.Address)
}
// Finally, mark this chunk of memory as used by the given package.
packagePath, filename := findPackagePath(line.File, packagePathMap)
program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable)
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.
addrAligned := (addr + section.Align - 1) &^ (section.Align - 1)
if section.Align > 1 && addrAligned >= sectionEnd {
// The gap is caused by the section alignment.
// For example, if a .rodata section ends with a non-aligned string.
program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true)
} else {
program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
}
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) (packagePath, filename string) {
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 {
// Directory is known as a Go package.
// Add the file itself as well.
filename = filepath.Base(path)
} else {
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 picolibc" for the
// baremetal libc.
libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator))
parts := strings.SplitN(libPath, string(os.PathSeparator), 2)
packagePath = "C " + parts[0]
if len(parts) > 1 {
filename = parts[1]
} else {
filename = parts[0]
}
} else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) {
packagePath = "C compiler-rt"
filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator))
// 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 path == "__isr_vector" {
packagePath = "C interrupt vector"
} else if path == "<Go type>" {
packagePath = "Go types"
} else if path == "<Go interface assert>" {
@@ -984,11 +849,9 @@ func findPackagePath(path string, packagePathMap map[string]string) (packagePath
// fixed in the compiler.
packagePath = "-"
} else {
// This is some other path. Not sure what it is, so just emit its
// directory as a fallback.
packagePath = filepath.Dir(path)
filename = filepath.Base(path)
// This is some other path. Not sure what it is, so just emit its directory.
packagePath = filepath.Dir(path) // fallback
}
}
return
return packagePath
}
-140
View File
@@ -1,140 +0,0 @@
package builder
import (
"regexp"
"runtime"
"testing"
"time"
"github.com/tinygo-org/tinygo/compileopts"
)
var sema = make(chan struct{}, runtime.NumCPU())
type sizeTest struct {
target string
path string
codeSize uint64
rodataSize uint64
dataSize uint64
bssSize uint64
}
// Test whether code and data size is as expected for the given targets.
// This tests both the logic of loadProgramSize and checks that code size
// doesn't change unintentionally.
//
// If you find that code or data size is reduced, then great! You can reduce the
// number in this test.
// If you find that the code or data size is increased, take a look as to why
// this is. It could be due to an update (LLVM version, Go version, etc) which
// is fine, but it could also mean that a recent change introduced this size
// increase. If so, please consider whether this new feature is indeed worth the
// size increase for all users.
func TestBinarySize(t *testing.T) {
if runtime.GOOS == "linux" && !hasBuiltinTools {
// Debian LLVM packages are modified a bit and tend to produce
// different machine code. Ideally we'd fix this (with some attributes
// or something?), but for now skip it.
t.Skip("Skip: using external LLVM version so binary size might differ")
}
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
{"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
// TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the
// output varies by binaryen version.
}
for _, tc := range tests {
tc := tc
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, tc.target, tc.path)
// Check whether the size of the binary matches the expected size.
sizes, err := loadProgramSize(result.Executable, nil)
if err != nil {
t.Fatal("could not read program size:", err)
}
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
t.Errorf(" code rodata data bss")
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
}
})
}
}
// Check that the -size=full flag attributes binary size to the correct package
// without filesystem paths and things like that.
func TestSizeFull(t *testing.T) {
tests := []string{
"microbit",
"wasip1",
}
libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector"
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests {
target := target
t.Run(target, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, target, "examples/serial")
// Check whether the binary doesn't contain any unexpected package
// names.
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
t.Fatal("could not read program size:", err)
}
for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" {
// TODO: correctly attribute all unknown binary size.
continue
}
if libMatch.MatchString(pkg) {
continue
}
if pkgMatch.MatchString(pkg) {
continue
}
t.Error("unexpected package name in size output:", pkg)
}
})
}
}
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
options := compileopts.Options{
Target: targetString,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(pkgName, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
return result
}
+27 -4
View File
@@ -12,7 +12,10 @@ import (
#include <stdbool.h>
#include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv);
bool tinygo_link(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"
@@ -23,7 +26,16 @@ const hasBuiltinTools = true
// 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 {
args = append([]string{tool}, args...)
linker := "elf"
if tool == "ld.lld" && len(args) >= 2 {
if args[0] == "-m" && (args[1] == "i386pep" || args[1] == "arm64pe") {
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)))
@@ -39,8 +51,19 @@ func RunTool(tool string, args ...string) error {
switch tool {
case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld", "wasm-ld":
ok = C.tinygo_link(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)
}
+27 -168
View File
@@ -1,191 +1,50 @@
package builder
import (
"bytes"
"fmt"
"go/scanner"
"go/token"
"errors"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
// runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(flags ...string) error {
// Find the right command to run Clang.
var cmd *exec.Cmd
if hasBuiltinTools {
// Compile this with the internal Clang compiler.
cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
} else {
// Compile this with an external invocation of the Clang compiler.
name, err := LookupCommand("clang")
if err != nil {
return err
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
cmd = exec.Command(name, flags...)
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()
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
// Make sure the command doesn't use any environmental variables.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
cmd.Env = []string{}
// Let some environment variables through. One important one is the
// temporary directory, especially on Windows it looks like Clang breaks if
// the temporary directory has not been set.
// See: https://github.com/tinygo-org/tinygo/issues/4557
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
for _, env := range os.Environ() {
// We could parse the key and look it up in a map, but since there are
// only a few keys iterating through them is easier and maybe even
// faster.
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
if strings.HasPrefix(env, prefix) {
cmd.Env = append(cmd.Env, env)
break
}
}
}
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 {
// We only support LLD.
if linker != "ld.lld" && linker != "wasm-ld" {
return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", linker)
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()
}
var cmd *exec.Cmd
if hasBuiltinTools {
cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...)
} else {
name, err := LookupCommand(linker)
if err != nil {
return err
}
cmd = exec.Command(name, flags...)
// Fall back to external command.
if _, ok := commands[linker]; ok {
return execCommand(linker, flags...)
}
var buf bytes.Buffer
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout
cmd.Stderr = &buf
err := cmd.Run()
if err != nil {
if buf.Len() == 0 {
// The linker failed but there was no output.
// Therefore, show some output anyway.
return fmt.Errorf("failed to run linker: %w", err)
}
return parseLLDErrors(buf.String())
}
return nil
}
// Split LLD errors into individual erros (including errors that continue on the
// next line, using a ">>>" prefix). If possible, replace the raw errors with a
// more user-friendly version (and one that's more in a Go style).
func parseLLDErrors(text string) error {
// Split linker output in separate error messages.
lines := strings.Split(text, "\n")
var errorLines []string // one or more line (belonging to a single error) per line
for _, line := range lines {
line = strings.TrimRight(line, "\r") // needed for Windows
if len(errorLines) != 0 && strings.HasPrefix(line, ">>> ") {
errorLines[len(errorLines)-1] += "\n" + line
continue
}
if line == "" {
continue
}
errorLines = append(errorLines, line)
}
// Parse error messages.
var linkErrors []error
var flashOverflow, ramOverflow uint64
for _, message := range errorLines {
parsedError := false
// Check for undefined symbols.
// This can happen in some cases like with CGo and //go:linkname tricker.
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[2]
for _, line := range strings.Split(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil {
parsedError = true
line, _ := strconv.Atoi(matches[3])
// TODO: detect common mistakes like -gc=none?
linkErrors = append(linkErrors, scanner.Error{
Pos: token.Position{
Filename: matches[2],
Line: line,
},
Msg: "linker could not find symbol " + symbolName,
})
}
}
}
// Check for flash/RAM overflow.
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
region := matches[3]
n, err := strconv.ParseUint(matches[4], 10, 64)
if err != nil {
// Should not happen at all (unless it overflows an uint64 for some reason).
continue
}
// Check which area overflowed.
// Some chips use differently named memory areas, but these are by
// far the most common.
switch region {
case "FLASH_TEXT":
if n > flashOverflow {
flashOverflow = n
}
parsedError = true
case "RAM":
if n > ramOverflow {
ramOverflow = n
}
parsedError = true
}
}
// If we couldn't parse the linker error: show the error as-is to
// the user.
if !parsedError {
linkErrors = append(linkErrors, LinkerError{message})
}
}
if flashOverflow > 0 {
linkErrors = append(linkErrors, LinkerError{
Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow),
})
}
if ramOverflow > 0 {
linkErrors = append(linkErrors, LinkerError{
Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow),
})
}
return newMultiError(linkErrors, "")
}
// LLD linker error that could not be parsed or doesn't refer to a source
// location.
type LinkerError struct {
Msg string
}
func (e LinkerError) Error() string {
return e.Msg
cmd.Stderr = os.Stderr
cmd.Dir = goenv.Get("TINYGOROOT")
return cmd.Run()
}
-284
View File
@@ -1,284 +0,0 @@
package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var libWasiLibc = Library{
name: "wasi-libc",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
err := os.Mkdir(bits, 0777)
if err != nil {
return err
}
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl")
err = buildMuslAllTypes("wasm32", muslDir, bits)
if err != nil {
return err
}
// See MUSL_OMIT_HEADERS in the Makefile.
omitHeaders := map[string]struct{}{
"syslog.h": {},
"wait.h": {},
"ucontext.h": {},
"paths.h": {},
"utmp.h": {},
"utmpx.h": {},
"lastlog.h": {},
"elf.h": {},
"link.h": {},
"pwd.h": {},
"shadow.h": {},
"grp.h": {},
"mntent.h": {},
"netdb.h": {},
"resolv.h": {},
"pty.h": {},
"dlfcn.h": {},
"setjmp.h": {},
"ulimit.h": {},
"wordexp.h": {},
"spawn.h": {},
"termios.h": {},
"libintl.h": {},
"aio.h": {},
"stdarg.h": {},
"stddef.h": {},
"pthread.h": {},
}
for _, glob := range [][2]string{
{"libc-bottom-half/headers/public/*.h", ""},
{"libc-bottom-half/headers/public/wasi/*.h", "wasi"},
{"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"},
{"libc-top-half/musl/include/*.h", ""},
{"libc-top-half/musl/include/netinet/*.h", "netinet"},
{"libc-top-half/musl/include/sys/*.h", "sys"},
} {
matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0]))
outDir := filepath.Join(includeDir, glob[1])
os.MkdirAll(outDir, 0o777)
for _, match := range matches {
name := filepath.Base(match)
if _, ok := omitHeaders[name]; ok {
continue
}
data, err := os.ReadFile(match)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(outDir, name), data, 0o666)
if err != nil {
return err
}
}
}
return nil
},
cflags: func(target, headerPath string) []string {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory",
"-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option",
"-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas",
"-DNDEBUG",
"-D__wasilibc_printscan_no_long_double",
"-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"",
"-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc
"-isystem", headerPath,
"-I" + libcDir + "/libc-top-half/musl/src/include",
"-I" + libcDir + "/libc-top-half/musl/src/internal",
"-I" + libcDir + "/libc-top-half/musl/arch/wasm32",
"-I" + libcDir + "/libc-top-half/musl/arch/generic",
"-I" + libcDir + "/libc-top-half/headers/private",
}
},
cflagsForFile: func(path string) []string {
if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-I" + libcDir + "/libc-bottom-half/headers/private",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src",
}
}
return nil
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) {
type filePattern struct {
glob string
exclude []string
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
globs := []filePattern{
// Top half: mostly musl sources.
{glob: "libc-top-half/sources/*.c"},
{glob: "libc-top-half/musl/src/conf/*.c"},
{glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{
"procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c",
}},
{glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{
"dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}},
{glob: "libc-top-half/musl/src/math/*.c", exclude: []string{
"__signbit.c", "__signbitf.c", "__signbitl.c",
"__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c",
"ceilf.c", "ceil.c",
"floorf.c", "floor.c",
"truncf.c", "trunc.c",
"rintf.c", "rint.c",
"nearbyintf.c", "nearbyint.c",
"sqrtf.c", "sqrt.c",
"fabsf.c", "fabs.c",
"copysignf.c", "copysign.c",
"fminf.c", "fmaxf.c",
"fmin.c", "fmax.c,",
}},
{glob: "libc-top-half/musl/src/multibyte/*.c"},
{glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{
"vfwscanf.c", "vfwprintf.c", // long double is unsupported
"__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c",
"rename.c",
"tmpnam.c", "tmpfile.c", "tempnam.c",
"popen.c", "pclose.c",
"remove.c",
"gets.c"}},
{glob: "libc-top-half/musl/src/stdlib/*.c"},
{glob: "libc-top-half/musl/src/string/*.c", exclude: []string{
"strsignal.c"}},
// Bottom half: connect top half to WASI equivalents.
{glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"},
{glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c"},
{glob: "libc-bottom-half/sources/*.c"},
}
// We're using the Boehm GC, so we need a heap implementation in the libc.
if libcNeedsMalloc {
globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"})
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
sources := []string{
"libc-top-half/musl/src/misc/a64l.c",
"libc-top-half/musl/src/misc/basename.c",
"libc-top-half/musl/src/misc/dirname.c",
"libc-top-half/musl/src/misc/ffs.c",
"libc-top-half/musl/src/misc/ffsl.c",
"libc-top-half/musl/src/misc/ffsll.c",
"libc-top-half/musl/src/misc/fmtmsg.c",
"libc-top-half/musl/src/misc/getdomainname.c",
"libc-top-half/musl/src/misc/gethostid.c",
"libc-top-half/musl/src/misc/getopt.c",
"libc-top-half/musl/src/misc/getopt_long.c",
"libc-top-half/musl/src/misc/getsubopt.c",
"libc-top-half/musl/src/misc/uname.c",
"libc-top-half/musl/src/misc/nftw.c",
"libc-top-half/musl/src/errno/strerror.c",
"libc-top-half/musl/src/network/htonl.c",
"libc-top-half/musl/src/network/htons.c",
"libc-top-half/musl/src/network/ntohl.c",
"libc-top-half/musl/src/network/ntohs.c",
"libc-top-half/musl/src/network/inet_ntop.c",
"libc-top-half/musl/src/network/inet_pton.c",
"libc-top-half/musl/src/network/inet_aton.c",
"libc-top-half/musl/src/network/in6addr_any.c",
"libc-top-half/musl/src/network/in6addr_loopback.c",
"libc-top-half/musl/src/fenv/fenv.c",
"libc-top-half/musl/src/fenv/fesetround.c",
"libc-top-half/musl/src/fenv/feupdateenv.c",
"libc-top-half/musl/src/fenv/fesetexceptflag.c",
"libc-top-half/musl/src/fenv/fegetexceptflag.c",
"libc-top-half/musl/src/fenv/feholdexcept.c",
"libc-top-half/musl/src/exit/exit.c",
"libc-top-half/musl/src/exit/atexit.c",
"libc-top-half/musl/src/exit/assert.c",
"libc-top-half/musl/src/exit/quick_exit.c",
"libc-top-half/musl/src/exit/at_quick_exit.c",
"libc-top-half/musl/src/time/strftime.c",
"libc-top-half/musl/src/time/asctime.c",
"libc-top-half/musl/src/time/asctime_r.c",
"libc-top-half/musl/src/time/ctime.c",
"libc-top-half/musl/src/time/ctime_r.c",
"libc-top-half/musl/src/time/wcsftime.c",
"libc-top-half/musl/src/time/strptime.c",
"libc-top-half/musl/src/time/difftime.c",
"libc-top-half/musl/src/time/timegm.c",
"libc-top-half/musl/src/time/ftime.c",
"libc-top-half/musl/src/time/gmtime.c",
"libc-top-half/musl/src/time/gmtime_r.c",
"libc-top-half/musl/src/time/timespec_get.c",
"libc-top-half/musl/src/time/getdate.c",
"libc-top-half/musl/src/time/localtime.c",
"libc-top-half/musl/src/time/localtime_r.c",
"libc-top-half/musl/src/time/mktime.c",
"libc-top-half/musl/src/time/__tm_to_secs.c",
"libc-top-half/musl/src/time/__month_to_secs.c",
"libc-top-half/musl/src/time/__secs_to_tm.c",
"libc-top-half/musl/src/time/__year_to_secs.c",
"libc-top-half/musl/src/time/__tz.c",
"libc-top-half/musl/src/fcntl/creat.c",
"libc-top-half/musl/src/dirent/alphasort.c",
"libc-top-half/musl/src/dirent/versionsort.c",
"libc-top-half/musl/src/env/__stack_chk_fail.c",
"libc-top-half/musl/src/env/clearenv.c",
"libc-top-half/musl/src/env/getenv.c",
"libc-top-half/musl/src/env/putenv.c",
"libc-top-half/musl/src/env/setenv.c",
"libc-top-half/musl/src/env/unsetenv.c",
"libc-top-half/musl/src/unistd/posix_close.c",
"libc-top-half/musl/src/stat/futimesat.c",
"libc-top-half/musl/src/legacy/getpagesize.c",
"libc-top-half/musl/src/thread/thrd_sleep.c",
}
basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/"
for _, pattern := range globs {
matches, err := filepath.Glob(basepath + pattern.glob)
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.
return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err)
}
if len(matches) == 0 {
return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern)
}
excludeSet := map[string]struct{}{}
for _, exclude := range pattern.exclude {
excludeSet[exclude] = struct{}{}
}
for _, match := range matches {
if _, ok := excludeSet[filepath.Base(match)]; ok {
continue
}
relpath, err := filepath.Rel(basepath, match)
if err != nil {
// Not sure if this is even possible.
return nil, err
}
sources = append(sources, relpath)
}
}
return sources, nil
},
}
-83
View File
@@ -1,83 +0,0 @@
package builder
import (
"os"
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
)
var libWasmBuiltins = Library{
name: "wasmbuiltins",
makeHeaders: func(target, includeDir string) error {
if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil {
return err
}
f, err := os.Create(includeDir + "/bits/alltypes.h")
if err != nil {
return err
}
if _, err := f.Write([]byte(wasmAllTypes)); err != nil {
return err
}
return f.Close()
},
cflags: func(target, headerPath string) []string {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", // match wasm-unknown (default on in LLVM 20)
"-mno-bulk-memory", // same here
"-isystem", libcDir + "/libc-top-half/musl/arch/wasm32",
"-isystem", libcDir + "/libc-top-half/musl/arch/generic",
"-isystem", libcDir + "/libc-top-half/musl/src/internal",
"-isystem", libcDir + "/libc-top-half/musl/src/include",
"-isystem", libcDir + "/libc-top-half/musl/include",
"-isystem", libcDir + "/libc-bottom-half/headers/public",
"-I" + headerPath,
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string, _ bool) ([]string, error) {
return []string{
// memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and
// llvm.memset.* LLVM intrinsics.
"libc-top-half/musl/src/string/memcpy.c",
"libc-top-half/musl/src/string/memmove.c",
"libc-top-half/musl/src/string/memset.c",
// exp, exp2, and log are needed for LLVM math builtin functions
// like llvm.exp.*.
"libc-top-half/musl/src/math/__math_divzero.c",
"libc-top-half/musl/src/math/__math_invalid.c",
"libc-top-half/musl/src/math/__math_oflow.c",
"libc-top-half/musl/src/math/__math_uflow.c",
"libc-top-half/musl/src/math/__math_xflow.c",
"libc-top-half/musl/src/math/exp.c",
"libc-top-half/musl/src/math/exp_data.c",
"libc-top-half/musl/src/math/exp2.c",
"libc-top-half/musl/src/math/log.c",
"libc-top-half/musl/src/math/log_data.c",
}, nil
},
}
// alltypes.h for wasm-libc, using the types as defined inside Clang.
const wasmAllTypes = `
typedef __SIZE_TYPE__ size_t;
typedef __INT8_TYPE__ int8_t;
typedef __INT16_TYPE__ int16_t;
typedef __INT32_TYPE__ int32_t;
typedef __INT64_TYPE__ int64_t;
typedef __UINT8_TYPE__ uint8_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT64_TYPE__ uint64_t;
typedef __UINTPTR_TYPE__ uintptr_t;
// This type is used internally in wasi-libc.
typedef double double_t;
`
+60 -221
View File
@@ -18,7 +18,6 @@ import (
"go/scanner"
"go/token"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -26,15 +25,9 @@ import (
"golang.org/x/tools/go/ast/astutil"
)
// Function that's only defined in Go 1.22.
var setASTFileFields = func(f *ast.File, start, end token.Pos) {
}
// cgoPackage holds all CGo-related information of a package.
type cgoPackage struct {
generated *ast.File
packageName string
cgoFiles []*ast.File
generatedPos token.Pos
errors []error
currentDir string // current working directory
@@ -43,7 +36,6 @@ type cgoPackage struct {
fset *token.FileSet
tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
anonDecls map[interface{}]string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
@@ -82,28 +74,20 @@ type bitfieldInfo struct {
endBit int64 // may be 0 meaning "until the end of the field"
}
// Information about a #cgo noescape line in the source code.
type noescapingFunc struct {
name string
pos token.Pos
used bool // true if used somewhere in the source (for proper error reporting)
}
// cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases.
var cgoAliases = map[string]string{
"_Cgo_int8_t": "int8",
"_Cgo_int16_t": "int16",
"_Cgo_int32_t": "int32",
"_Cgo_int64_t": "int64",
"_Cgo_uint8_t": "uint8",
"_Cgo_uint16_t": "uint16",
"_Cgo_uint32_t": "uint32",
"_Cgo_uint64_t": "uint64",
"_Cgo_uintptr_t": "uintptr",
"_Cgo_float": "float32",
"_Cgo_double": "float64",
"_Cgo__Bool": "bool",
"C.int8_t": "int8",
"C.int16_t": "int16",
"C.int32_t": "int32",
"C.int64_t": "int64",
"C.uint8_t": "uint8",
"C.uint16_t": "uint16",
"C.uint32_t": "uint32",
"C.uint64_t": "uint64",
"C.uintptr_t": "uintptr",
"C.float": "float32",
"C.double": "float64",
}
// builtinAliases are handled specially because they only exist on the Go side
@@ -145,105 +129,31 @@ typedef unsigned long long _Cgo_ulonglong;
// The string/bytes functions below implement C.CString etc. To make sure the
// runtime doesn't need to know the C int type, lengths are converted to uintptr
// first.
const generatedGoFilePrefixBase = `
import "syscall"
// These functions will be modified to get a "C." prefix, so the source below
// doesn't reflect the final AST.
const generatedGoFilePrefix = `
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func __GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func __GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
`
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
`
// Windows uses fake errno values in the syscall package.
// See for example: https://github.com/golang/go/issues/23468
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
// syscall package is the standard library one we can't change it, but we can
// map the errno values to match the values in the syscall package.
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
var _Cgo___errno_mapping = [...]syscall.Errno{
1: syscall.EPERM,
2: syscall.ENOENT,
3: syscall.ESRCH,
4: syscall.EINTR,
5: syscall.EIO,
6: syscall.ENXIO,
7: syscall.E2BIG,
8: syscall.ENOEXEC,
9: syscall.EBADF,
10: syscall.ECHILD,
11: syscall.EAGAIN,
12: syscall.ENOMEM,
13: syscall.EACCES,
14: syscall.EFAULT,
16: syscall.EBUSY,
17: syscall.EEXIST,
18: syscall.EXDEV,
19: syscall.ENODEV,
20: syscall.ENOTDIR,
21: syscall.EISDIR,
22: syscall.EINVAL,
23: syscall.ENFILE,
24: syscall.EMFILE,
25: syscall.ENOTTY,
27: syscall.EFBIG,
28: syscall.ENOSPC,
29: syscall.ESPIPE,
30: syscall.EROFS,
31: syscall.EMLINK,
32: syscall.EPIPE,
33: syscall.EDOM,
34: syscall.ERANGE,
36: syscall.EDEADLK,
38: syscall.ENAMETOOLONG,
39: syscall.ENOLCK,
40: syscall.ENOSYS,
41: syscall.ENOTEMPTY,
42: syscall.EILSEQ,
}
func _Cgo___get_errno() error {
num := _Cgo___get_errno_num()
if num < uintptr(len(_Cgo___errno_mapping)) {
if mapped := _Cgo___errno_mapping[num]; mapped != 0 {
return mapped
}
}
return syscall.Errno(num)
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
`
@@ -254,15 +164,13 @@ func _Cgo___get_errno() error {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
// hashes of the accessed C header files. If there is one or more error, it
// returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir,
importPath: importPath,
fset: fset,
tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{},
noescapingFuncs: map[string]*noescapingFunc{},
anonDecls: map[interface{}]string{},
visitedFiles: map[string][]byte{},
}
@@ -287,21 +195,32 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Construct a new in-memory AST for CGo declarations of this package.
// The first part is written as Go code that is then parsed, but more code
// is added later to the AST to declare functions, globals, etc.
goCode := "package " + files[0].Name.Name + "\n\n"
if goos == "windows" {
goCode += generatedGoFilePrefixWindows
} else {
goCode += generatedGoFilePrefixOther
}
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
if err != nil {
// This is always a bug in the cgo package.
panic("unexpected error: " + err.Error())
}
p.cgoFiles = append(p.cgoFiles, p.generated)
// If the Comments field is not set to nil, the go/format package will get
// confused about where comments should go.
p.generated.Comments = nil
// Adjust some of the functions in there.
for _, decl := range p.generated.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
}
}
}
// Patch some types, for example *C.char in C.CString.
cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
return cf.walker(cursor, nil)
}, nil)
// Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
@@ -372,6 +291,9 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// have better alternatives anyway.
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...)
if clangHeaders != "" {
cflagsForCGo = append(cflagsForCGo, "-isystem", clangHeaders)
}
// Retrieve types such as C.int, C.longlong, etc from C.
p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
@@ -380,7 +302,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
Tok: token.TYPE,
}
for _, name := range builtinAliases {
typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name])
typeSpec := p.getIntegerType("C."+name, names["_Cgo_"+name])
gen.Specs = append(gen.Specs, typeSpec)
}
p.generated.Decls = append(p.generated.Decls, gen)
@@ -389,11 +311,10 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Process CGo imports for each file.
for i, f := range files {
cf := p.newCGoFile(f, i)
// These types are aliased with the corresponding types in C. For
// example, float in C is always float32 in Go.
// Float and double are aliased, meaning that C.float is the same thing
// as float32 in Go.
cf.names["float"] = clangCursor{}
cf.names["double"] = clangCursor{}
cf.names["_Bool"] = clangCursor{}
// Now read all the names (identifies) that C defines in the header
// snippet.
cf.readNames(p.cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
@@ -409,26 +330,10 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
})
}
// Show an error when a #cgo noescape line isn't used in practice.
// This matches upstream Go. I think the goal is to avoid issues with
// misspelled function names, which seems very useful.
var unusedNoescapeLines []*noescapingFunc
for _, value := range p.noescapingFuncs {
if !value.used {
unusedNoescapeLines = append(unusedNoescapeLines, value)
}
}
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
})
for _, value := range unusedNoescapeLines {
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
}
// Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated)
return p.cgoFiles, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
return p.generated, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
}
func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile {
@@ -490,33 +395,6 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
}
text = text[:lineStart] + string(spaces) + text[lineEnd:]
allFields := strings.Fields(line[4:])
switch allFields[0] {
case "noescape":
// The code indicates that pointer parameters will not be captured
// by the called C function.
if len(allFields) < 2 {
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
continue
}
if len(allFields) > 2 {
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
continue
}
name := allFields[1]
p.noescapingFuncs[name] = &noescapingFunc{
name: name,
pos: pos,
used: false,
}
continue
case "nocallback":
// We don't do anything special when calling a C function, so there
// appears to be no optimization that we can do here.
// Accept, but ignore the parameter for compatibility.
continue
}
// Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':')
if colon < 0 {
@@ -1253,22 +1131,22 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string {
// Some types are defined in stdint.h and map directly to a particular Go
// type.
if alias := cgoAliases["_Cgo_"+name]; alias != "" {
if alias := cgoAliases["C."+name]; alias != "" {
return alias
}
node := f.getASTDeclNode(name, found)
node := f.getASTDeclNode(name, found, iscall)
if node, ok := node.(*ast.FuncDecl); ok {
if !iscall {
return node.Name.Name + "$funcaddr"
}
return node.Name.Name
}
return "_Cgo_" + name
return "C." + name
}
// getASTDeclNode will declare the given C AST node (if not already defined) and
// returns it.
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately.
return node
@@ -1363,8 +1241,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
case *elaboratedTypeInfo:
// Add struct bitfields.
for _, bitfield := range elaboratedType.bitfields {
f.createBitfieldGetter(bitfield, "_Cgo_"+name)
f.createBitfieldSetter(bitfield, "_Cgo_"+name)
f.createBitfieldGetter(bitfield, "C."+name)
f.createBitfieldSetter(bitfield, "C."+name)
}
if elaboratedType.unionSize != 0 {
// Create union getters/setters.
@@ -1373,7 +1251,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue
}
f.createUnionAccessor(field, "_Cgo_"+name)
f.createUnionAccessor(field, "C."+name)
}
}
}
@@ -1387,45 +1265,6 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// separate namespace (no _Cgo_ hacks like in gc).
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
switch node := cursor.Node().(type) {
case *ast.AssignStmt:
// An assign statement could be something like this:
//
// val, errno := C.some_func()
//
// Check whether it looks like that, and if so, read the errno value and
// return it as the second return value. The call will be transformed
// into something like this:
//
// val, errno := C.some_func(), C.__get_errno()
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
return true
}
rhs, ok := node.Rhs[0].(*ast.CallExpr)
if !ok {
return true
}
fun, ok := rhs.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
// Replace "C"."some_func" into "C.somefunc".
rhs.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: f.getASTDeclName(fun.Sel.Name, found, true),
}
// Add the errno value as the second value in the statement.
node.Rhs = append(node.Rhs, &ast.CallExpr{
Fun: &ast.Ident{
NamePos: node.Lhs[1].End(),
Name: "_Cgo___get_errno",
},
})
}
case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok {
@@ -1447,7 +1286,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b
return true
}
if x.Name == "C" {
name := "_Cgo_" + node.Sel.Name
name := "C." + node.Sel.Name
if found, ok := names[node.Sel.Name]; ok {
name = f.getASTDeclName(node.Sel.Name, found, false)
}
-17
View File
@@ -1,17 +0,0 @@
//go:build go1.22
package cgo
// Code specifically for Go 1.22.
import (
"go/ast"
"go/token"
)
func init() {
setASTFileFields = func(f *ast.File, start, end token.Pos) {
f.FileStart = start
f.FileEnd = end
}
}
+8 -34
View File
@@ -7,7 +7,6 @@ import (
"go/ast"
"go/format"
"go/parser"
"go/scanner"
"go/token"
"go/types"
"os"
@@ -56,7 +55,7 @@ func TestCGo(t *testing.T) {
}
// Process the AST with CGo.
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "")
// Check the AST for type errors.
var typecheckErrors []error
@@ -64,10 +63,10 @@ func TestCGo(t *testing.T) {
Error: func(err error) {
typecheckErrors = append(typecheckErrors, err)
},
Importer: newSimpleImporter(),
Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"),
}
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
_, err = config.Check("", fset, []*ast.File{f, cgoAST}, nil)
if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an
// unexpected condition).
@@ -92,7 +91,7 @@ func TestCGo(t *testing.T) {
}
buf.WriteString("\n")
}
err = format.Node(buf, fset, cgoFiles[0])
err = format.Node(buf, fset, cgoAST)
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
@@ -202,33 +201,14 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
}
// simpleImporter implements the types.Importer interface, but only allows
// importing the syscall and unsafe packages.
// importing the unsafe package.
type simpleImporter struct {
syscallPkg *types.Package
}
func newSimpleImporter() *simpleImporter {
i := &simpleImporter{}
// Implement a dummy syscall package with the Errno type.
i.syscallPkg = types.NewPackage("syscall", "syscall")
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
named := types.NewNamed(obj, nil, nil)
i.syscallPkg.Scope().Insert(obj)
named.SetUnderlying(types.Typ[types.Uintptr])
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
i.syscallPkg.MarkComplete()
return i
}
// Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package.
func (i *simpleImporter) Import(path string) (*types.Package, error) {
func (i simpleImporter) Import(path string) (*types.Package, error) {
switch path {
case "syscall":
return i.syscallPkg, nil
case "unsafe":
return types.Unsafe, nil
default:
@@ -236,16 +216,10 @@ func (i *simpleImporter) Import(path string) (*types.Package, error) {
}
}
// formatDiagnostic formats the error message to be an indented comment. It
// formatDiagnostics formats the error message to be an indented comment. It
// also fixes Windows path name issues (backward slashes).
func formatDiagnostic(err error) string {
var msg string
switch err := err.(type) {
case scanner.Error:
msg = err.Pos.String() + ": " + err.Msg
default:
msg = err.Error()
}
msg := err.Error()
if runtime.GOOS == "windows" {
// Fix Windows path slashes.
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
+8 -154
View File
@@ -17,8 +17,6 @@ var (
token.OR: precedenceOr,
token.XOR: precedenceXor,
token.AND: precedenceAnd,
token.SHL: precedenceShift,
token.SHR: precedenceShift,
token.ADD: precedenceAdd,
token.SUB: precedenceAdd,
token.MUL: precedenceMul,
@@ -27,13 +25,11 @@ var (
}
)
// See: https://en.cppreference.com/w/c/language/operator_precedence
const (
precedenceLowest = iota + 1
precedenceOr
precedenceXor
precedenceAnd
precedenceShift
precedenceAdd
precedenceMul
precedencePrefix
@@ -54,72 +50,8 @@ func init() {
}
// parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value, f)
// If params is non-nil (could be a zero length slice), this const is
// actually a function-call like expression from another macro.
// This means we have to parse a string like "(a, b) (a+b)".
// We do this by parsing the parameters at the start and then treating the
// following like a normal constant expression.
if params != nil {
// Parse opening paren.
if t.curToken != token.LPAREN {
return nil, unexpectedToken(t, token.LPAREN)
}
t.Next()
// Parse parameters (identifiers) and closing paren.
var paramIdents []string
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
// No parameters, break early.
t.Next()
break
}
// Read the parameter name.
if t.curToken != token.IDENT {
return nil, unexpectedToken(t, token.IDENT)
}
paramIdents = append(paramIdents, t.curValue)
t.Next()
// Read the next token: either a continuation (comma) or end of list
// (rparen).
if t.curToken == token.RPAREN {
// End of parameter list.
t.Next()
break
} else if t.curToken == token.COMMA {
// Comma, so there will be another parameter name.
t.Next()
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
}
}
}
// Report an error if there is a mismatch in parameter length.
// The error is reported at the location of the closing paren from the
// caller location.
if len(params) != len(paramIdents) {
return nil, &scanner.Error{
Pos: t.fset.Position(callerPos),
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
}
}
// Assign values to the parameters.
// These parameter names are closer in 'scope' than other identifiers so
// will be used first when parsing an identifier.
for i, name := range paramIdents {
t.params[name] = params[i]
}
}
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 {
@@ -150,7 +82,7 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch t.peekToken {
case token.OR, token.XOR, token.AND, token.SHL, token.SHR, token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
case token.OR, token.XOR, token.AND, token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr)
}
@@ -160,68 +92,6 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
}
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
// If the identifier is one of the parameters of this function-like macro,
// use the parameter value.
if val, ok := t.params[t.curValue]; ok {
return val, nil
}
if t.f != nil {
// Check whether this identifier is actually a macro "call" with
// parameters. In that case, we should parse the parameters and pass it
// on to a new invocation of parseConst.
if t.peekToken == token.LPAREN {
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
// We know the current and peek tokens (the peek one is the '('
// token). So skip ahead until the current token is the first
// unknown token.
t.Next()
t.Next()
// Parse the list of parameters until ')' (rparen) is found.
params := []ast.Expr{}
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
break
}
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
params = append(params, x)
t.Next()
if t.curToken == token.COMMA {
t.Next()
} else if t.curToken == token.RPAREN {
break
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
}
}
}
// Evaluate the macro value and use it as the identifier value.
rparen := t.curPos
pos, text := t.f.getMacro(cursor)
return parseConst(pos, t.fset, text, params, rparen, t.f)
}
}
// Normally the name is something defined in the file (like another
// macro) which we get the declaration from using getASTDeclName.
// This ensures that names that are only referenced inside a macro are
// still getting defined.
if cursor, ok := t.f.names[t.curValue]; ok {
return &ast.Ident{
NamePos: t.curPos,
Name: t.f.getASTDeclName(t.curValue, cursor, false),
}, nil
}
}
// t.f is nil during testing. This is a fallback.
return &ast.Ident{
NamePos: t.curPos,
Name: "C." + t.curValue,
@@ -290,25 +160,21 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
// tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct {
f *cgoFile
curPos, peekPos token.Pos
fset *token.FileSet
curToken, peekToken token.Token
curValue, peekValue string
buf string
params map[string]ast.Expr
}
// newTokenizer initializes a new tokenizer, positioned at the first token in
// the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer {
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{
f: f,
peekPos: start,
fset: fset,
buf: buf,
peekToken: token.ILLEGAL,
params: make(map[string]ast.Expr),
}
// Parse the first two tokens (cur and peek).
t.Next()
@@ -325,9 +191,7 @@ func (t *tokenizer) Next() {
t.curValue = t.peekValue
// Parse the next peek token.
if t.peekPos != token.NoPos {
t.peekPos += token.Pos(len(t.curValue))
}
t.peekPos += token.Pos(len(t.curValue))
for {
if len(t.buf) == 0 {
t.peekToken = token.EOF
@@ -339,28 +203,20 @@ 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
if t.peekPos != token.NoPos {
t.peekPos++
}
t.peekPos++
t.buf = t.buf[1:]
case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&" || string(t.buf[:2]) == "<<" || string(t.buf[:2]) == ">>"):
case len(t.buf) >= 2 && (string(t.buf[:2]) == "||" || string(t.buf[:2]) == "&&"):
// Two-character tokens.
switch c {
case '&':
t.peekToken = token.LAND
case '|':
t.peekToken = token.LOR
case '<':
t.peekToken = token.SHL
case '>':
t.peekToken = token.SHR
default:
panic("unreachable")
}
t.peekValue = t.buf[:2]
t.buf = t.buf[2:]
return
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
// Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators.
switch c {
@@ -368,8 +224,6 @@ func (t *tokenizer) Next() {
t.peekToken = token.LPAREN
case ')':
t.peekToken = token.RPAREN
case ',':
t.peekToken = token.COMMA
case '+':
t.peekToken = token.ADD
case '-':
+1 -5
View File
@@ -40,10 +40,6 @@ func TestParseConst(t *testing.T) {
{`5&5`, `5 & 5`},
{`5|5`, `5 | 5`},
{`5^5`, `5 ^ 5`},
{`5<<5`, `5 << 5`},
{`5>>5`, `5 >> 5`},
{`5>>5 + 3`, `5 >> (5 + 3)`},
{`5>>5 ^ 3`, `5>>5 ^ 3`},
{`5||5`, `error: 1:2: unexpected token ||, expected end of expression`}, // logical binops aren't supported yet
{`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`},
@@ -59,7 +55,7 @@ func TestParseConst(t *testing.T) {
} {
fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
expr, err := parseConst(startPos, fset, tc.C)
s := "<invalid>"
if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") {
+74 -133
View File
@@ -4,7 +4,6 @@ package cgo
// modification. It does not touch the AST itself.
import (
"bytes"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
@@ -61,26 +60,11 @@ CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c);
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
// In file included from _cgo_export.c:4:
// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
// libclang.go:68:5: note: previous declaration is here
// See: https://github.com/golang/go/issues/49721
#if defined(_WIN32)
#define CGO_DECL __declspec(dllexport)
#else
#define CGO_DECL
#endif
CGO_DECL
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
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"
@@ -219,7 +203,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
obj := &ast.Object{
Kind: ast.Fun,
Name: "_Cgo_" + name,
Name: "C." + name,
}
exportName := name
localName := name
@@ -257,7 +241,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
Name: &ast.Ident{
NamePos: pos,
Name: "_Cgo_" + localName,
Name: "C." + localName,
Obj: obj,
},
Type: &ast.FuncType{
@@ -269,18 +253,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
},
},
}
var doc []string
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
doc = append(doc, "//go:variadic")
}
if _, ok := f.noescapingFuncs[name]; ok {
doc = append(doc, "//go:noescape")
f.noescapingFuncs[name].used = true
}
if len(doc) != 0 {
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
Slash: pos - 1,
Text: strings.Join(doc, "\n"),
Text: "//go:variadic",
})
}
for i := 0; i < numArgs; i++ {
@@ -319,7 +295,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
return decl, stringSignature
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos)
typeName := "_Cgo_" + name
typeName := "C." + name
typeExpr := typ.typeExpr
if typ.unionSize != 0 {
// Convert to a single-field struct type.
@@ -340,7 +316,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
obj.Decl = typeSpec
return typeSpec, typ
case C.CXCursor_TypedefDecl:
typeName := "_Cgo_" + name
typeName := "C." + name
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
obj := &ast.Object{
Kind: ast.Typ,
@@ -378,12 +354,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
obj := &ast.Object{
Kind: ast.Var,
Name: "_Cgo_" + name,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Name: "C." + name,
Obj: obj,
}},
Type: typeExpr,
@@ -392,8 +368,42 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
gen.Specs = append(gen.Specs, valueSpec)
return gen, nil
case C.CXCursor_MacroDefinition:
tokenPos, value := f.getMacro(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
sourceRange := C.tinygo_clang_getCursorExtent(c)
start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
f.addError(pos, "internal error: could not find file where macro is defined")
return nil, nil
}
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
}
if startOffset > endOffset {
f.addError(pos, "internal error: start offset of macro is after end offset")
return nil, nil
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
f.addError(pos, "internal error: end offset of macro lies after end of file")
return nil, nil
}
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
}
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
@@ -407,12 +417,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
@@ -423,7 +433,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
case C.CXCursor_EnumDecl:
obj := &ast.Object{
Kind: ast.Typ,
Name: "_Cgo_" + name,
Name: "C." + name,
}
underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
// TODO: gc's CGo implementation uses types such as `uint32` for enums
@@ -431,7 +441,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
typeSpec := &ast.TypeSpec{
Name: &ast.Ident{
NamePos: pos,
Name: "_Cgo_" + name,
Name: "C." + name,
Obj: obj,
},
Assign: pos,
@@ -454,12 +464,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{
NamePos: pos,
Name: "_Cgo_" + name,
Name: "C." + name,
Obj: obj,
}},
Values: []ast.Expr{expr},
@@ -473,62 +483,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}
}
// Return whether this is a macro that's also function-like, like this:
//
// #define add(a, b) (a+b)
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
return false
}
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
}
// Get the macro value: the position in the source file and the string value of
// the macro.
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
value = sourceBuf.String()
// Obtain the position of this token. This is the position of the first
// character in the 'value' string and is used to report errors at the
// correct location in the source file.
pos = f.getCursorPosition(c)
return
}
func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString)
s = C.GoString(rawString)
@@ -634,13 +588,6 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines)
p.tokenFiles[filename] = f
// Add dummy file AST, to satisfy the type checker.
astFile := &ast.File{
Package: f.Pos(0),
Name: ast.NewIdent(p.packageName),
}
setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
p.cgoFiles = append(p.cgoFiles, astFile)
}
positionFile := p.tokenFiles[filename]
@@ -687,6 +634,13 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
// addErrorAt is a utility function to add an error to the list of errors.
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.currentDir, position.Filename)
if err == nil {
position.Filename = relpath
}
}
p.errors = append(p.errors, scanner.Error{
Pos: position,
Msg: msg,
@@ -699,19 +653,8 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
// Strip typedefs, if any.
underlyingType := typ
if underlyingType.kind == C.CXType_Elaborated {
// Starting with LLVM 16, the elaborated type is used for more types.
// According to the Clang documentation, the elaborated type has no
// semantic meaning so can be stripped (it is used to better convey type
// name information).
// Source:
// https://clang.llvm.org/doxygen/classclang_1_1ElaboratedType.html#details
// > The type itself is always "sugar", used to express what was written
// > in the source code but containing no additional semantic information.
underlyingType = C.clang_Type_getNamedType(underlyingType)
}
if underlyingType.kind == C.CXType_Typedef {
c := C.tinygo_clang_getTypeDeclaration(underlyingType)
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
@@ -745,27 +688,27 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string
switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U:
typeName = "_Cgo_char"
typeName = "C.char"
case C.CXType_SChar:
typeName = "_Cgo_schar"
typeName = "C.schar"
case C.CXType_UChar:
typeName = "_Cgo_uchar"
typeName = "C.uchar"
case C.CXType_Short:
typeName = "_Cgo_short"
typeName = "C.short"
case C.CXType_UShort:
typeName = "_Cgo_ushort"
typeName = "C.ushort"
case C.CXType_Int:
typeName = "_Cgo_int"
typeName = "C.int"
case C.CXType_UInt:
typeName = "_Cgo_uint"
typeName = "C.uint"
case C.CXType_Long:
typeName = "_Cgo_long"
typeName = "C.long"
case C.CXType_ULong:
typeName = "_Cgo_ulong"
typeName = "C.ulong"
case C.CXType_LongLong:
typeName = "_Cgo_longlong"
typeName = "C.longlong"
case C.CXType_ULongLong:
typeName = "_Cgo_ulonglong"
typeName = "C.ulonglong"
case C.CXType_Bool:
typeName = "bool"
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
@@ -845,8 +788,6 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
return f.makeASTType(underlying, pos)
case C.CXType_Enum:
return f.makeASTType(underlying, pos)
case C.CXType_Typedef:
return f.makeASTType(underlying, pos)
default:
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
@@ -865,7 +806,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
// makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_"
}
if name == "" || C.tinygo_clang_Cursor_isAnonymous(cursor) != 0 {
if name == "" {
// Anonymous record, probably inside a typedef.
location := f.getUniqueLocationID(pos, cursor)
name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location)
@@ -896,7 +837,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
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))
typeName = "_Cgo_<unknown>"
typeName = "C.<unknown>"
}
return &ast.Ident{
NamePos: pos,
@@ -913,7 +854,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
var goName string
typeSize := C.clang_Type_getSizeOf(underlyingType)
switch name {
case "_Cgo_char":
case "C.char":
if typeSize != 1 {
// This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted.
@@ -926,7 +867,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case C.CXType_Char_U:
goName = "uint8"
}
case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong":
case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typeSize {
case 1:
goName = "int8"
@@ -937,7 +878,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case 8:
goName = "int64"
}
case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong":
case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typeSize {
case 1:
goName = "uint8"
+15
View File
@@ -0,0 +1,15 @@
//go:build !byollvm && llvm14
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"
+3 -3
View File
@@ -1,4 +1,4 @@
//go:build !byollvm && llvm15
//go:build !byollvm && !llvm14
package cgo
@@ -8,8 +8,8 @@ package cgo
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@15/include
#cgo freebsd CFLAGS: -I/usr/local/llvm15/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-15/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@15/lib -lclang -lffi
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@15/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm15/lib -lclang
*/
import "C"
-21
View File
@@ -1,21 +0,0 @@
//go:build !byollvm && llvm16
package cgo
// As of 2023-05-05, there is a packaging issue with LLVM 16 on Debian:
// https://github.com/llvm/llvm-project/issues/62199
// A workaround is to fix this locally, using something like this:
//
// ln -sf ../../x86_64-linux-gnu/libclang-16.so.1 /usr/lib/llvm-16/lib/libclang.so
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-16/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@16/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@16/include
#cgo freebsd CFLAGS: -I/usr/local/llvm16/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-16/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@16/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@16/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm16/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm17
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-17 -I/usr/include/llvm-c-17 -I/usr/lib/llvm-17/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@17/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@17/include
#cgo freebsd CFLAGS: -I/usr/local/llvm17/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-17/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@17/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@17/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm17/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm18
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-18 -I/usr/include/llvm-c-18 -I/usr/lib/llvm-18/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@18/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@18/include
#cgo freebsd CFLAGS: -I/usr/local/llvm18/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-18/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@18/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@18/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm18/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm19
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include
#cgo freebsd CFLAGS: -I/usr/local/llvm19/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib/llvm-20/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@20/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@20/include
#cgo freebsd CFLAGS: -I/usr/local/llvm20/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@20/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@20/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm20/lib -lclang
*/
import "C"
+1 -9
View File
@@ -77,14 +77,6 @@ CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c);
}
unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
return clang_Cursor_isAnonymous(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c);
}
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
return clang_Cursor_isMacroFunctionLike(c);
}
}
-2
View File
@@ -78,7 +78,6 @@ var validCompilerFlags = []*regexp.Regexp{
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?plt`),
re(`-f(no-)?rtti`),
re(`-f(no-)?short-enums`),
re(`-f(no-)?split-stack`),
re(`-f(no-)?stack-(.+)`),
re(`-f(no-)?strict-aliasing`),
@@ -143,7 +142,6 @@ var validLinkerFlags = []*regexp.Regexp{
re(`-L([^@\-].*)`),
re(`-O`),
re(`-O([^@\-].*)`),
re(`--export=([^@\-].*)`),
re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`),
-1
View File
@@ -108,7 +108,6 @@ var goodLinkerFlags = [][]string{
{"-Fbar"},
{"-lbar"},
{"-Lbar"},
{"--export=my_symbol"},
{"-fpic"},
{"-fno-pic"},
{"-fPIC"},
+23 -38
View File
@@ -1,54 +1,39 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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
)
-16
View File
@@ -3,26 +3,10 @@ package main
/*
#define foo 3
#define bar foo
#define unreferenced 4
#define referenced unreferenced
#define fnlike() 5
#define fnlike_val fnlike()
#define square(n) (n*n)
#define square_val square(20)
#define add(a, b) (a + b)
#define add_val add(3, 5)
*/
import "C"
const (
Foo = C.foo
Bar = C.bar
Baz = C.referenced
fnlike = C.fnlike_val
square = C.square_val
add = C.add_val
)
+25 -45
View File
@@ -1,62 +1,42 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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 _Cgo_foo = 3
const _Cgo_bar = _Cgo_foo
const _Cgo_unreferenced = 4
const _Cgo_referenced = _Cgo_unreferenced
const _Cgo_fnlike_val = 5
const _Cgo_square_val = (20 * 20)
const _Cgo_add_val = (3 + 5)
const C.foo = 3
const C.bar = C.foo
-26
View File
@@ -10,35 +10,20 @@ typedef struct {
typedef someType noType; // undefined type
// Some invalid noescape lines
#cgo noescape
#cgo noescape foo bar
#cgo noescape unusedFunction
#define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte
#define SOME_CONST_b 3 ) // const with lots of weird whitespace (to test error locations)
# define SOME_CONST_startspace 3)
*/
//
//
// #define SOME_CONST_4 8) // after some empty lines
// #cgo CFLAGS: -DSOME_PARAM_CONST_invalid=3/+3
// #cgo CFLAGS: -DSOME_PARAM_CONST_valid=3+4
import "C"
// #warning another warning
import "C"
// #define add(a, b) (a+b)
// #define add_toomuch add(1, 2, 3)
// #define add_toolittle add(1)
import "C"
// Make sure that errors for the following lines won't change with future
// additions to the CGo preamble.
//
//line errors.go:100
var (
// constant too large
@@ -53,15 +38,4 @@ var (
_ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4
_ = C.SOME_CONST_b
_ = C.SOME_CONST_startspace
// constants passed by a command line parameter
_ = C.SOME_PARAM_CONST_invalid
_ = C.SOME_PARAM_CONST_valid
_ = C.add_toomuch
_ = C.add_toolittle
)
+35 -64
View File
@@ -1,89 +1,60 @@
// CGo errors:
// testdata/errors.go:14:1: missing function name in #cgo noescape line
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
// testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:31:5: warning: another warning
// testdata/errors.go:18:23: unexpected token ), expected end of expression
// testdata/errors.go:26:26: unexpected token ), expected end of expression
// testdata/errors.go:21:33: unexpected token ), expected end of expression
// testdata/errors.go:22:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
// testdata/errors.go:22:5: warning: another warning
// testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:19:26: unexpected token ), expected end of expression
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as _Cgo_char value in variable declaration (overflows)
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1
// testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4
// testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b
// testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace
// testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid
// testdata/errors.go:122: undefined: _Cgo_add_toomuch
// testdata/errors.go:123: undefined: _Cgo_add_toolittle
// testdata/errors.go:108: undefined: 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: undefined: C.SOME_CONST_4
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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 _Cgo_struct_point_t struct {
x _Cgo_int
y _Cgo_int
type C._Ctype_struct___0 struct {
x C.int
y C.int
}
type _Cgo_point_t = _Cgo_struct_point_t
type C.point_t = C._Ctype_struct___0
const _Cgo_SOME_CONST_3 = 1234
const _Cgo_SOME_PARAM_CONST_valid = 3 + 4
const C.SOME_CONST_3 = 1234
+25 -40
View File
@@ -5,58 +5,43 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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 _Cgo_BAR = 3
const _Cgo_FOO_H = 1
const C.BAR = 3
const C.FOO_H = 1
-5
View File
@@ -9,10 +9,6 @@ static void staticfunc(int x);
// Global variable signatures.
extern int someValue;
void notEscapingFunction(int *a);
#cgo noescape notEscapingFunction
*/
import "C"
@@ -22,7 +18,6 @@ func accessFunctions() {
C.variadic0()
C.variadic2(3, 5)
C.staticfunc(3)
C.notEscapingFunction(nil)
}
func accessGlobals() {
+32 -52
View File
@@ -1,84 +1,64 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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 _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int
func C.foo(a C.int, b C.int) C.int
var _Cgo_foo$funcaddr unsafe.Pointer
var C.foo$funcaddr unsafe.Pointer
//export variadic0
//go:variadic
func _Cgo_variadic0()
func C.variadic0()
var _Cgo_variadic0$funcaddr unsafe.Pointer
var C.variadic0$funcaddr unsafe.Pointer
//export variadic2
//go:variadic
func _Cgo_variadic2(x _Cgo_int, y _Cgo_int)
func C.variadic2(x C.int, y C.int)
var _Cgo_variadic2$funcaddr unsafe.Pointer
var C.variadic2$funcaddr unsafe.Pointer
//export _Cgo_static_173c95a79b6df1980521_staticfunc
func _Cgo_staticfunc!symbols.go(x _Cgo_int)
func C.staticfunc!symbols.go(x C.int)
var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
//export notEscapingFunction
//go:noescape
func _Cgo_notEscapingFunction(a *_Cgo_int)
var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer
//go:extern someValue
var _Cgo_someValue _Cgo_int
var C.someValue C.int
+92 -109
View File
@@ -1,166 +1,149 @@
package main
import "syscall"
import "unsafe"
var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char
//go:linkname C.CString runtime.cgo_CString
func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string
//go:linkname C.GoString runtime.cgo_GoString
func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
//go:linkname C.__GoStringN runtime.cgo_GoStringN
func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
return _Cgo___GoStringN(cstr, uintptr(length))
func C.GoStringN(cstr *C.char, length C.int) string {
return C.__GoStringN(cstr, uintptr(length))
}
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
//go:linkname C.__GoBytes runtime.cgo_GoBytes
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return C.__GoBytes(ptr, uintptr(length))
}
type (
_Cgo_char uint8
_Cgo_schar int8
_Cgo_uchar uint8
_Cgo_short int16
_Cgo_ushort uint16
_Cgo_int int32
_Cgo_uint uint32
_Cgo_long int32
_Cgo_ulong uint32
_Cgo_longlong int64
_Cgo_ulonglong uint64
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 _Cgo_myint = _Cgo_int
type _Cgo_struct_point2d_t struct {
x _Cgo_int
y _Cgo_int
type C.myint = C.int
type C._Ctype_struct___0 struct {
x C.int
y C.int
}
type _Cgo_point2d_t = _Cgo_struct_point2d_t
type _Cgo_struct_point3d struct {
x _Cgo_int
y _Cgo_int
z _Cgo_int
type C.point2d_t = C._Ctype_struct___0
type C.struct_point3d struct {
x C.int
y C.int
z C.int
}
type _Cgo_point3d_t = _Cgo_struct_point3d
type _Cgo_struct_type1 struct {
_type _Cgo_int
__type _Cgo_int
___type _Cgo_int
type C.point3d_t = C.struct_point3d
type C.struct_type1 struct {
_type C.int
__type C.int
___type C.int
}
type _Cgo_struct_type2 struct{ _type _Cgo_int }
type _Cgo_union_union1_t struct{ i _Cgo_int }
type _Cgo_union1_t = _Cgo_union_union1_t
type _Cgo_union_union3_t struct{ $union uint64 }
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 *_Cgo_union_union3_t) unionfield_i() *_Cgo_int {
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
func (union *C._Ctype_union___2) unionfield_i() *C.int {
return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union3_t) unionfield_d() *float64 {
func (union *C._Ctype_union___2) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short {
return (*_Cgo_short)(unsafe.Pointer(&union.$union))
func (union *C._Ctype_union___2) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union))
}
type _Cgo_union3_t = _Cgo_union_union3_t
type _Cgo_union_union2d struct{ $union [2]uint64 }
type C.union3_t = C._Ctype_union___2
type C.union_union2d struct{ $union [2]uint64 }
func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int {
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 {
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 _Cgo_union2d_t = _Cgo_union_union2d
type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar }
type _Cgo_unionarray_t = _Cgo_union_unionarray_t
type _Cgo__Ctype_union___0 struct{ $union [3]uint32 }
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 *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t {
return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union))
func (union *C._Ctype_union___5) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t {
return (*_Cgo_point3d_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 _Cgo_struct_struct_nested_t struct {
begin _Cgo_point2d_t
end _Cgo_point2d_t
tag _Cgo_int
type C._Ctype_struct___4 struct {
begin C.point2d_t
end C.point2d_t
tag C.int
coord _Cgo__Ctype_union___0
coord C._Ctype_union___5
}
type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t
type _Cgo_union_union_nested_t struct{ $union [2]uint64 }
type C.struct_nested_t = C._Ctype_struct___4
type C._Ctype_union___6 struct{ $union [2]uint64 }
func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t {
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union))
func (union *C._Ctype_union___6) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
}
func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t {
return (*_Cgo_unionarray_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 *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t {
return (*_Cgo_union3_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 _Cgo_union_nested_t = _Cgo_union_union_nested_t
type _Cgo_enum_option = _Cgo_int
type _Cgo_option_t = _Cgo_enum_option
type _Cgo_enum_option2_t = _Cgo_uint
type _Cgo_option2_t = _Cgo_enum_option2_t
type _Cgo_struct_types_t struct {
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 {
f float32
d float64
ptr *_Cgo_int
ptr *C.int
}
type _Cgo_types_t = _Cgo_struct_types_t
type _Cgo_myIntArray = [10]_Cgo_int
type _Cgo_struct_bitfield_t struct {
start _Cgo_uchar
__bitfield_1 _Cgo_uchar
type C.types_t = C._Ctype_struct___8
type C.myIntArray = [10]C.int
type C._Ctype_struct___9 struct {
start C.uchar
__bitfield_1 C.uchar
d _Cgo_uchar
e _Cgo_uchar
d C.uchar
e C.uchar
}
func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f }
func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) {
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 *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_uchar {
func (s *C._Ctype_struct___9) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1
}
func (s *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) {
func (s *C._Ctype_struct___9) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
}
func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar {
func (s *C._Ctype_struct___9) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6
}
func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar,
func (s *C._Ctype_struct___9) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type _Cgo_bitfield_t = _Cgo_struct_bitfield_t
type C.bitfield_t = C._Ctype_struct___9
+110 -203
View File
@@ -8,29 +8,18 @@ import (
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/google/shlex"
"github.com/tinygo-org/tinygo/goenv"
)
// Library versions. Whenever an existing library is changed, this number should
// be added/increased so that existing caches are invalidated.
//
// (This is a bit of a layering violation, this should really be part of the
// builder.Library struct but that's hard to do since we want to know the
// library path in advance in several places).
var libVersions = map[string]int{
"musl": 3,
"bdwgc": 2,
}
// Config keeps all configuration affecting the build in a single struct.
type Config struct {
Options *Options
Target *TargetSpec
GoMinorVersion int
ClangHeaders string // Clang built-in header include path
TestConfig TestConfig
}
@@ -45,17 +34,6 @@ func (c *Config) CPU() string {
return c.Target.CPU
}
// The current build mode (like the `-buildmode` command line flag).
func (c *Config) BuildMode() string {
if c.Options.BuildMode != "" {
return c.Options.BuildMode
}
if c.Target.BuildMode != "" {
return c.Target.BuildMode
}
return "default"
}
// 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.
@@ -83,7 +61,7 @@ func (c *Config) GOOS() string {
}
// GOARCH returns the GOARCH of the target. This might not always be the actual
// architecture: for example, the AVR target is not supported by the Go standard
// archtecture: for example, the AVR target is not supported by the Go standard
// library so such targets will usually pretend to be linux/arm.
func (c *Config) GOARCH() string {
return c.Target.GOARCH
@@ -95,27 +73,10 @@ func (c *Config) GOARM() string {
return c.Options.GOARM
}
// GOMIPS will return the GOMIPS environment variable given to the compiler when
// building a program.
func (c *Config) GOMIPS() string {
return c.Options.GOMIPS
}
// BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string {
tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race)
tags = append(tags, []string{
"tinygo", // that's the compiler
"purego", // to get various crypto packages to work
"osusergo", // to get os/user to work
"math_big_pure_go", // to get math/big to work
"gc." + c.GC(), "scheduler." + c.Scheduler(), // used inside the runtime package
"serial." + c.Serial()}...) // used inside the machine package
switch c.Scheduler() {
case "threads", "cores":
default:
tags = append(tags, "tinygo.unicore")
}
targetTags := filterTags(c.Target.BuildTags, c.Options.Tags)
tags := append(targetTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i))
}
@@ -123,6 +84,12 @@ func (c *Config) BuildTags() []string {
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", "conservative" and "precise".
func (c *Config) GC() string {
@@ -139,7 +106,7 @@ func (c *Config) GC() string {
// that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "custom", "precise", "boehm":
case "conservative", "custom", "precise":
for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
@@ -179,18 +146,18 @@ func (c *Config) Serial() string {
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline.
func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
switch c.Options.Opt {
case "none", "0":
return "O0", 0, 0
return 0, 0, 0 // -O0
case "1":
return "O1", 1, 0
return 1, 0, 0 // -O1
case "2":
return "O2", 2, 0
return 2, 0, 225 // -O2
case "s":
return "Os", 2, 1
return 2, 1, 225 // -Os
case "z":
return "Oz", 2, 2 // default
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.
@@ -224,15 +191,6 @@ func (c *Config) StackSize() uint64 {
return c.Target.DefaultStackSize
}
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() >= 16*1024 {
return 1024
}
return 256
}
// 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 {
@@ -242,40 +200,20 @@ func (c *Config) RP2040BootPatch() bool {
return false
}
// Return a canonicalized architecture name, so we don't have to deal with arm*
// vs thumb* vs arm64.
func CanonicalArchName(triple string) string {
// 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 arch == "arm64" {
return "aarch64"
}
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
return "arm"
}
if arch == "mipsel" {
return "mips"
arch = "arm"
}
return arch
}
// 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 {
return CanonicalArchName(triple)
}
// Returns true if the libc needs to include malloc, for the libcs where this
// matters.
func (c *Config) LibcNeedsMalloc() bool {
if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" {
return true
}
return false
}
// LibraryPath returns the path to the library build directory. The path will be
// a library path in the cache directory (which might not yet be built).
func (c *Config) LibraryPath(name string) string {
// 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()
@@ -283,27 +221,18 @@ func (c *Config) LibraryPath(name string) string {
if c.ABI() != "" {
archname += "-" + c.ABI()
}
if c.Target.SoftFloat {
archname += "-softfloat"
}
if name == "bdwgc" {
// Boehm GC is compiled against a particular libc.
archname += "-" + c.Target.Libc
}
// Append a version string, if this library has a version.
if v, ok := libVersions[name]; ok {
archname += "-v" + strconv.Itoa(v)
}
options := ""
if c.LibcNeedsMalloc() {
options += "+malloc"
// 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+options+"-"+archname)
return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
}
// DefaultBinaryExtension returns the default extension for binaries, such as
@@ -331,22 +260,56 @@ func (c *Config) DefaultBinaryExtension() string {
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing.
func (c *Config) CFlags(libclang bool) []string {
func (c *Config) CFlags() []string {
var cflags []string
for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
}
resourceDir := goenv.ClangResourceDir(libclang)
if resourceDir != "" {
// The resource directory contains the built-in clang headers like
// stdbool.h, stdint.h, float.h, etc.
// It is left empty if we're using an external compiler (that already
// knows these headers).
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags,
"-resource-dir="+resourceDir,
"--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)
}
cflags = append(cflags, c.LibcCFlags()...)
// Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-gdwarf-4")
// Use the same optimization level as TinyGo.
@@ -373,80 +336,6 @@ func (c *Config) CFlags(libclang bool) []string {
return cflags
}
// LibcCFlags returns the C compiler flags for the configured libc.
// It only uses flags that are part of the libc path (triple, cpu, abi, libc
// name) so it can safely be used to compile another C library.
func (c *Config) LibcCFlags() []string {
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
}
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path := c.LibraryPath("picolibc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
}
case "musl":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("musl")
arch := MuslArchitecture(c.Triple())
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
}
case "wasi-libc":
path := c.LibraryPath("wasi-libc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
}
case "wasmbuiltins":
// nothing to add (library is purely for builtins)
return nil
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("mingw-w64")
cflags := []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
}
if c.GOARCH() == "386" {
cflags = append(cflags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
)
} else {
cflags = append(cflags,
"-D_UCRT",
"-D_WIN32_WINNT=0x0a00", // target Windows 10
)
}
return cflags
case "":
// No libc specified, nothing to add.
return nil
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)
}
}
// LDFlags returns the flags to pass to the linker. A few more flags are needed
// (like the one for the compiler runtime), but this represents the majority of
// the flags.
@@ -461,8 +350,6 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
}
ldflags = append(ldflags, c.Options.ExtLDFlags...)
return ldflags
}
@@ -530,13 +417,13 @@ func (c *Config) BinaryFormat(ext string) string {
// 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 -programmer command-line option.
// file or be modified using the -programmmer command-line option.
func (c *Config) Programmer() (method, openocdInterface string) {
switch c.Options.Programmer {
case "":
// No configuration supplied.
return c.Target.FlashMethod, c.Target.OpenOCDInterface
case "openocd", "msd", "command", "adb":
case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp":
@@ -570,6 +457,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
}
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
if c.Target.OpenOCDTransport != "" {
transport := c.Target.OpenOCDTransport
if transport == "swd" {
@@ -581,9 +471,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
args = append(args, "-c", "transport select "+transport)
}
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
return args, nil
}
@@ -606,6 +493,11 @@ func (c *Config) RelocationModel() string {
return "static"
}
// WasmAbi returns the WASM ABI which is specified in the target JSON file.
func (c *Config) WasmAbi() string {
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 {
@@ -649,14 +541,29 @@ func (c *Config) Emulator(format, binary string) ([]string, error) {
type TestConfig struct {
CompileTestBinary bool
CompileOnly bool
Verbose bool
Short bool
RunRegexp string
SkipRegexp string
Count *int
BenchRegexp string
BenchTime string
BenchMem bool
Shuffle string
// TODO: Filter the test functions to run, include verbose flag, etc
}
// filterTags removes predefined build tags for a target if a conflicting option
// is provided by the user.
func filterTags(targetTags []string, userTags []string) []string {
var filtered []string
for _, t := range targetTags {
switch {
case strings.HasPrefix(t, "runtime_memhash_"):
overridden := false
for _, ut := range userTags {
if strings.HasPrefix(ut, "runtime_memhash_") {
overridden = true
break
}
}
if !overridden {
filtered = append(filtered, t)
}
default:
filtered = append(filtered, t)
}
}
return filtered
}
+132
View File
@@ -0,0 +1,132 @@
package compileopts
import (
"fmt"
"strings"
"testing"
)
func TestBuildTags(t *testing.T) {
tests := []struct {
targetTags []string
userTags []string
result []string
}{
{
targetTags: []string{},
userTags: []string{},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{"bear"},
userTags: []string{},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
},
},
{
targetTags: []string{},
userTags: []string{"cat"},
result: []string{
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear"},
userTags: []string{"cat"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat"},
result: []string{
"bear",
"runtime_memhash_leveldb",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_leveldb"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_leveldb",
},
},
{
targetTags: []string{"bear", "runtime_memhash_leveldb"},
userTags: []string{"cat", "runtime_memhash_sip"},
result: []string{
"bear",
"tinygo",
"math_big_pure_go",
"gc.conservative",
"scheduler.none",
"serial.none",
"cat",
"runtime_memhash_sip",
},
},
}
for _, tc := range tests {
tt := tc
t.Run(fmt.Sprintf("%s+%s", strings.Join(tt.targetTags, ","), strings.Join(tt.userTags, ",")), func(t *testing.T) {
c := &Config{
Target: &TargetSpec{
BuildTags: tt.targetTags,
},
Options: &Options{
Tags: tt.userTags,
},
}
res := c.BuildTags()
if len(res) != len(tt.result) {
t.Errorf("expected %d tags, got %d", len(tt.result), len(res))
}
for i, tag := range tt.result {
if tag != res[i] {
t.Errorf("tag %d: expected %s, got %s", i, tt.result[i], tag)
}
}
})
}
}
+6 -22
View File
@@ -8,11 +8,10 @@ import (
)
var (
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
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"}
)
@@ -24,10 +23,7 @@ type Options struct {
GOOS string // environment variable
GOARCH string // environment variable
GOARM string // environment variable (only used with GOARCH=arm)
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
Directory string // working dir, leave it unset to use the current working dir
Target string
BuildMode string // -buildmode flag
Opt string
GC string
PanicStrategy string
@@ -39,11 +35,9 @@ type Options struct {
PrintIR bool
DumpSSA bool
VerifyIR bool
SkipDWARF bool
PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap
Debug bool
Nobounds bool
PrintSizes string
PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool
@@ -53,25 +47,15 @@ type Options struct {
Programmer string
OpenOCDCommands []string
LLVMFeatures string
Directory string
PrintJSON bool
Monitor bool
BaudRate int
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags []string
GoCompatibility bool // enable to check for Go version compatibility
}
// Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error {
if o.BuildMode != "" {
valid := isInArray(validBuildModeOptions, o.BuildMode)
if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode,
strings.Join(validBuildModeOptions, ", "))
}
}
if o.GC != "" {
valid := isInArray(validGCOptions, o.GC)
if !valid {
+3 -3
View File
@@ -9,9 +9,9 @@ import (
func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise, boehm`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify, threads, cores`)
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, html`)
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
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 {
+132 -333
View File
@@ -23,54 +23,46 @@ import (
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html
// https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
type TargetSpec struct {
Inherits []string `json:"inherits,omitempty"`
InheritableOnly bool `json:"inheritable-only"` // this target is only meant to be inherited from, not used directly
Triple string `json:"llvm-target,omitempty"`
CPU string `json:"cpu,omitempty"`
ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag
Features string `json:"features,omitempty"`
GOOS string `json:"goos,omitempty"`
GOARCH string `json:"goarch,omitempty"`
SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc)
BuildTags []string `json:"build-tags,omitempty"`
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified)
GC string `json:"gc,omitempty"`
Scheduler string `json:"scheduler,omitempty"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
Linker string `json:"linker,omitempty"`
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc,omitempty"`
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time.
CFlags []string `json:"cflags,omitempty"`
LDFlags []string `json:"ldflags,omitempty"`
LinkerScript string `json:"linkerscript,omitempty"`
ExtraFiles []string `json:"extra-files,omitempty"`
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear.
Emulator string `json:"emulator,omitempty"`
FlashCommand string `json:"flash-command,omitempty"`
GDB []string `json:"gdb,omitempty"`
PortReset string `json:"flash-1200-bps-reset,omitempty"`
SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid"
FlashMethod string `json:"flash-method,omitempty"`
FlashVolume []string `json:"msd-volume-name,omitempty"`
FlashFilename string `json:"msd-firmware-name,omitempty"`
UF2FamilyID string `json:"uf2-family-id,omitempty"`
BinaryFormat string `json:"binary-format,omitempty"`
OpenOCDInterface string `json:"openocd-interface,omitempty"`
OpenOCDTarget string `json:"openocd-target,omitempty"`
OpenOCDTransport string `json:"openocd-transport,omitempty"`
OpenOCDCommands []string `json:"openocd-commands,omitempty"`
OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd
JLinkDevice string `json:"jlink-device,omitempty"`
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
ADBPushRemote string `json:"adb-push-remote,omitempty"`
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
CodeModel string `json:"code-model,omitempty"`
RelocationModel string `json:"relocation-model,omitempty"`
WITPackage string `json:"wit-package,omitempty"`
WITWorld string `json:"wit-world,omitempty"`
Inherits []string `json:"inherits"`
Triple string `json:"llvm-target"`
CPU string `json:"cpu"`
ABI string `json:"target-abi"` // rougly equivalent to -mabi= flag
Features string `json:"features"`
GOOS string `json:"goos"`
GOARCH string `json:"goarch"`
BuildTags []string `json:"build-tags"`
GC string `json:"gc"`
Scheduler string `json:"scheduler"`
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
Linker string `json:"linker"`
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc"`
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
CFlags []string `json:"cflags"`
LDFlags []string `json:"ldflags"`
LinkerScript string `json:"linkerscript"`
ExtraFiles []string `json:"extra-files"`
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
Emulator string `json:"emulator"`
FlashCommand string `json:"flash-command"`
GDB []string `json:"gdb"`
PortReset string `json:"flash-1200-bps-reset"`
SerialPort []string `json:"serial-port"` // serial port IDs in the form "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.
@@ -93,10 +85,6 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) error {
if src.Uint() != 0 {
dst.Set(src)
}
case reflect.Bool:
if src.Bool() {
dst.Set(src)
}
case reflect.Ptr: // for pointers, copy if not nil
if !src.IsNil() {
dst.Set(src)
@@ -152,11 +140,6 @@ func (spec *TargetSpec) loadFromGivenStr(str string) error {
// resolveInherits loads inherited targets, recursively.
func (spec *TargetSpec) resolveInherits() error {
// Save InheritableOnly before resolving, since it must not propagate
// from parent to child (a board target should not become inheritable-only
// just because its parent processor target is).
inheritableOnly := spec.InheritableOnly
// First create a new spec with all the inherited properties.
newSpec := &TargetSpec{}
for _, name := range spec.Inherits {
@@ -182,31 +165,60 @@ func (spec *TargetSpec) resolveInherits() error {
}
*spec = *newSpec
// Restore InheritableOnly from the original spec, not from parents.
spec.InheritableOnly = inheritableOnly
return nil
}
// Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) {
if options.Target == "" && options.GOARCH == "wasm" {
// Set a specific target if we're building from a known GOOS/GOARCH
// combination that is defined in a target JSON file.
switch options.GOOS {
case "js":
options.Target = "wasm"
case "wasip1":
options.Target = "wasip1"
case "wasip2":
options.Target = "wasip2"
default:
return nil, errors.New("GOARCH=wasm but GOOS is not set correctly. Please set GOOS to js, wasip1, or wasip2.")
}
}
if options.Target == "" {
return defaultTarget(options)
// 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
}
llvmvendor := "unknown"
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"
llvmos = "macosx11.0.0"
}
llvmvendor = "apple"
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
target := llvmarch + "-" + llvmvendor + "-" + llvmos
if options.GOOS == "windows" {
target += "-gnu"
} else if options.GOARCH == "arm" {
target += "-gnueabihf"
}
return defaultTarget(options.GOOS, options.GOARCH, target)
}
// See whether there is a target specification for this target (e.g.
@@ -230,245 +242,70 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
return spec, nil
}
// GetTargetSpecs retrieves target specifications from the TINYGOROOT targets
// directory. Only valid target JSON files are considered, and the function
// returns a map of target names to their respective TargetSpec.
func GetTargetSpecs() (map[string]*TargetSpec, error) {
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
entries, err := os.ReadDir(dir)
if err != nil {
return nil, fmt.Errorf("could not list targets: %w", err)
}
maps := map[string]*TargetSpec{}
for _, entry := range entries {
entryInfo, err := entry.Info()
if err != nil {
return nil, fmt.Errorf("could not get entry info: %w", err)
}
if !entryInfo.Mode().IsRegular() || !strings.HasSuffix(entry.Name(), ".json") {
// Only inspect JSON files.
continue
}
path := filepath.Join(dir, entry.Name())
spec, err := LoadTarget(&Options{Target: path})
if err != nil {
return nil, fmt.Errorf("could not list target: %w", err)
}
if spec.InheritableOnly {
// Skip targets that are only meant to be inherited from, not used directly.
continue
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
continue
}
name := entry.Name()
name = name[:len(name)-5]
maps[name] = spec
}
return maps, nil
}
// Load a target from environment variables (which default to
// runtime.GOOS/runtime.GOARCH).
func defaultTarget(options *Options) (*TargetSpec, error) {
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{
GOOS: options.GOOS,
GOARCH: options.GOARCH,
BuildTags: []string{options.GOOS, options.GOARCH},
Triple: triple,
GOOS: goos,
GOARCH: goarch,
BuildTags: []string{goos, goarch},
GC: "precise",
Scheduler: "tasks",
Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB
GDB: []string{"gdb"},
PortReset: "false",
}
// Configure target based on GOARCH.
var llvmarch string
switch options.GOARCH {
switch goarch {
case "386":
llvmarch = "i386"
spec.CPU = "pentium4"
spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
case "amd64":
llvmarch = "x86_64"
spec.CPU = "x86-64"
spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
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")
subarch := strings.Split(options.GOARM, ",")
if len(subarch) > 2 {
return nil, fmt.Errorf("invalid GOARM=%s, must be of form <num>,[hardfloat|softfloat]", options.GOARM)
}
archLevel := subarch[0]
var fpu string
if len(subarch) >= 2 {
fpu = subarch[1]
} else {
// Pick the default fpu value: softfloat for armv5 and hardfloat
// above that.
if archLevel == "5" {
fpu = "softfloat"
} else {
fpu = "hardfloat"
}
}
switch fpu {
case "softfloat":
spec.CFlags = append(spec.CFlags, "-msoft-float")
spec.SoftFloat = true
case "hardfloat":
// Hardware floating point support is the default everywhere except
// on ARMv5 where it needs to be enabled explicitly.
if archLevel == "5" {
spec.CFlags = append(spec.CFlags, "-mfpu=vfpv2")
}
default:
return nil, fmt.Errorf("invalid extension GOARM=%s, must be softfloat or hardfloat", options.GOARM)
}
switch archLevel {
case "5":
llvmarch = "armv5"
if spec.SoftFloat {
spec.Features = "+armv5t,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
} else {
spec.Features = "+armv5t,+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 "6":
llvmarch = "armv6"
if spec.SoftFloat {
spec.Features = "+armv6,+dsp,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
} else {
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 "7":
llvmarch = "armv7"
if spec.SoftFloat {
spec.Features = "+armv7-a,+dsp,+soft-float,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
} else {
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"
}
default:
return nil, fmt.Errorf("invalid GOARM=%s, must be of form <num>,[hardfloat|softfloat] where num is 5, 6, or 7", options.GOARM)
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"
llvmarch = "aarch64"
if options.GOOS == "darwin" {
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a"
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
} else if options.GOOS == "windows" {
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv"
} else { // linux
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics"
}
case "mips", "mipsle":
spec.CPU = "mips32"
spec.CFlags = append(spec.CFlags, "-fno-pic")
if options.GOARCH == "mips" {
llvmarch = "mips" // big endian
} else {
llvmarch = "mipsel" // little endian
}
switch options.GOMIPS {
case "hardfloat":
spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls"
case "softfloat":
spec.SoftFloat = true
spec.Features = "+mips32,+soft-float,-noabicalls"
spec.CFlags = append(spec.CFlags, "-msoft-float")
default:
return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS)
}
case "wasm":
return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to js, wasip1, or wasip2.")
default:
return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH)
spec.Features = "+neon"
}
// Configure target based on GOOS.
llvmos := options.GOOS
llvmvendor := "unknown"
switch options.GOOS {
case "darwin":
spec.GC = "boehm"
platformVersion := "10.12.0"
if options.GOARCH == "arm64" {
platformVersion = "11.0.0" // first macosx platform with arm64 support
}
llvmvendor = "apple"
spec.Scheduler = "threads"
if goos == "darwin" {
spec.Linker = "ld.lld"
spec.Libc = "darwin-libSystem"
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
// iOS!
llvmos = "macosx" + platformVersion
arch := strings.Split(triple, "-")[0]
platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx")
spec.LDFlags = append(spec.LDFlags,
"-flavor", "darwin",
"-dead_strip",
"-arch", llvmarch,
"-arch", arch,
"-platform_version", "macos", platformVersion, platformVersion,
)
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_darwin.c",
"src/internal/task/task_threads.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "linux":
spec.GC = "boehm"
spec.Scheduler = "threads"
} else if goos == "linux" {
spec.Linker = "ld.lld"
spec.RTLib = "compiler-rt"
spec.Libc = "musl"
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
if options.GOARCH == "arm64" {
// Disable outline atomics. For details, see:
// https://cpufun.substack.com/p/atomics-in-aarch64
// A better way would be to fully support outline atomics, which
// makes atomics slightly more efficient on systems with many cores.
// But the instructions are only supported on newer aarch64 CPUs, so
// this feature is normally put in a system library which does
// feature detection for you.
// We take the lazy way out and simply disable this feature, instead
// of enabling it in compiler-rt (which is a bit more complicated).
// We don't really need this feature anyway as we don't even support
// proper threading.
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
}
spec.ExtraFiles = append(spec.ExtraFiles,
"src/internal/futex/futex_linux.c",
"src/internal/task/task_threads.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
spec.GC = "boehm"
spec.Scheduler = "tasks"
} else if goos == "windows" {
spec.Linker = "ld.lld"
spec.Libc = "mingw-w64"
switch options.GOARCH {
case "386":
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pe",
"--major-os-version", "4",
"--major-subsystem-version", "4",
)
// __udivdi3 is not present in ucrt it seems.
spec.RTLib = "compiler-rt"
// 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
switch goarch {
case "amd64":
// 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",
"--image-base", "0x400000",
@@ -484,57 +321,24 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"--no-insert-timestamp",
"--no-dynamicbase",
)
case "wasm", "wasip1", "wasip2":
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
default:
return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS)
} else {
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
}
if spec.GC == "boehm" {
// Add this file only when needed. This fixes a build failure on
// Windows.
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_boehm.c")
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos
if options.GOOS == "windows" {
spec.Triple += "-gnu"
} else if options.GOOS == "linux" {
// We use musl on Linux (not glibc) so we should use -musleabi* instead
// of -gnueabi*.
// The *hf suffix selects between soft/hard floating point ABI.
if spec.SoftFloat {
spec.Triple += "-musleabi"
} else {
spec.Triple += "-musleabihf"
}
}
// Add extra assembly files (needed for the scheduler etc).
if options.GOARCH != "wasm" {
if goarch != "wasm" {
suffix := ""
if options.GOOS == "windows" && options.GOARCH == "amd64" {
if goos == "windows" && goarch == "amd64" {
// Windows uses a different calling convention on amd64 from other
// operating systems so we need separate assembly files.
suffix = "_windows"
}
asmGoarch := options.GOARCH
if options.GOARCH == "mips" || options.GOARCH == "mipsle" {
asmGoarch = "mipsx"
}
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
}
// Configure the emulator.
if options.GOARCH != runtime.GOARCH {
if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"}
if options.GOOS == "linux" {
switch options.GOARCH {
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" {
@@ -546,19 +350,14 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
spec.Emulator = "qemu-arm {}"
case "arm64":
spec.Emulator = "qemu-aarch64 {}"
case "mips":
spec.Emulator = "qemu-mips {}"
case "mipsle":
spec.Emulator = "qemu-mipsel {}"
}
}
}
if options.GOOS != runtime.GOOS {
if options.GOOS == "windows" {
if goos != runtime.GOOS {
if goos == "windows" {
spec.Emulator = "wine {}"
}
}
return &spec, nil
}
-31
View File
@@ -23,37 +23,6 @@ func TestLoadTarget(t *testing.T) {
}
}
func TestGetTargetSpecs_InheritableOnlyTargetsExcluded(t *testing.T) {
specs, err := GetTargetSpecs()
if err != nil {
t.Fatal("GetTargetSpecs failed:", err)
}
// Inheritable-only processor-level targets should not appear in the listing.
inheritableOnlyTargets := []string{"esp32", "esp32c3", "esp32s3", "esp8266", "rp2040", "rp2350", "rp2350b"}
for _, name := range inheritableOnlyTargets {
if _, ok := specs[name]; ok {
t.Errorf("inheritable-only target %q should not appear in GetTargetSpecs", name)
}
}
// Board targets that inherit from inheritable-only targets should still appear.
boardTargets := []string{"esp32-coreboard-v2", "pico"}
for _, name := range boardTargets {
if _, ok := specs[name]; !ok {
t.Errorf("board target %q should appear in GetTargetSpecs", name)
}
}
}
func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) {
// Inheritable-only targets should still be loadable directly (for building).
_, err := LoadTarget(&Options{Target: "esp32"})
if err != nil {
t.Errorf("LoadTarget should still load inheritable-only target esp32: %v", err)
}
}
func TestOverrideProperties(t *testing.T) {
baseAutoStackSize := true
base := &TargetSpec{
+7 -12
View File
@@ -16,18 +16,13 @@ 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/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",
"internal/chacha8rand.block": "internal/chacha8rand.block_generic",
// AES
"crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock",
"crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock",
"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",
+4 -4
View File
@@ -99,7 +99,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value,
// 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 unsigned greater than.
// 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)
@@ -135,7 +135,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
// uintptr if uintptrs were signed.
maxBufSize := b.CreateLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false), "")
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
@@ -150,7 +150,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
// Make sure maxBufSize has the same type as bufSize.
if maxBufSize.Type() != bufSize.Type() {
maxBufSize = b.CreateZExt(maxBufSize, bufSize.Type(), "")
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
}
// Do the check for a too large (or negative) buffer size.
@@ -245,7 +245,7 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock)
+55 -20
View File
@@ -1,6 +1,9 @@
package compiler
import (
"fmt"
"strings"
"tinygo.org/x/go-llvm"
)
@@ -10,42 +13,74 @@ import (
func (b *builder) createAtomicOp(name string) llvm.Value {
switch name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn))
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.GlobalValueType(), 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 "AndInt32", "AndInt64", "AndUint32", "AndUint64", "AndUintptr":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn))
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAnd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
return oldVal
case "OrInt32", "OrInt64", "OrUint32", "OrUint64", "OrUintptr":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn))
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpOr, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
return oldVal
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn))
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], getPos(b.fn))
old := b.getValue(b.fn.Params[1], getPos(b.fn))
newVal := b.getValue(b.fn.Params[2], getPos(b.fn))
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], getPos(b.fn))
ptr := b.getValue(b.fn.Params[0])
val := b.CreateLoad(b.getLLVMType(b.fn.Signature.Results().At(0).Type()), 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], getPos(b.fn))
val := b.getValue(b.fn.Params[1], getPos(b.fn))
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.GlobalValueType(), 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
+9 -31
View File
@@ -19,9 +19,8 @@ const maxFieldsPerParam = 3
// useful while declaring or defining a function.
type paramInfo struct {
llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
flags paramFlags // extra flags for this parameter
name string // name, possibly with suffixes for e.g. struct fields
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
}
// paramFlags identifies parameter attributes for flags. Most importantly, it
@@ -29,27 +28,20 @@ type paramInfo struct {
type paramFlags uint8
const (
// Whether this is a full or partial Go parameter (int, slice, etc).
// The extra context parameter is not a Go parameter.
paramIsGoParam = 1 << iota
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
// 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 {
member := b.program.ImportedPackage("runtime").Members[fnName]
if member == nil {
panic("unknown runtime call: " + fnName)
}
fn := member.(*ssa.Function)
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
fnType, llvmFn := b.getFunction(fn)
if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil))
}
args = append(args, llvm.Undef(b.dataPtrType)) // unused context parameter
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
if isInvoke {
return b.createInvoke(fnType, llvmFn, args, name)
}
@@ -79,15 +71,7 @@ func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value,
fragments := b.expandFormalParam(arg)
expanded = append(expanded, fragments...)
}
call := b.CreateCall(fnType, fn, expanded, name)
if !fn.IsAFunction().IsNil() {
if cc := fn.FunctionCallConv(); cc != llvm.CCallConv {
// Set a different calling convention if needed.
// This is needed for GetModuleHandleExA on Windows, for example.
call.SetInstructionCallConv(cc)
}
}
return call
return b.CreateCall(fnType, fn, expanded, name)
}
// createInvoke is like createCall but continues execution at the landing pad if
@@ -170,7 +154,6 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
continue
}
suffix := strconv.Itoa(i)
isString := false
if goType != nil {
// Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on.
@@ -187,16 +170,12 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"r", "i"}[i]
case types.String:
suffix = []string{"data", "len"}[i]
isString = true
}
case *types.Signature:
suffix = []string{"context", "funcptr"}[i]
}
}
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
if isString {
subInfos[0].flags |= paramIsReadonly
}
paramInfos = append(paramInfos, subInfos...)
}
return paramInfos
@@ -212,7 +191,6 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty
info := paramInfo{
llvmType: t,
name: name,
flags: paramIsGoParam,
}
if goType != nil {
switch underlying := goType.Underlying().(type) {
@@ -251,7 +229,7 @@ func extractSubfield(t types.Type, field int) types.Type {
}
}
// flattenAggregateTypeOffsets returns the offsets from the start of an object of
// 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.
+43 -58
View File
@@ -4,9 +4,7 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering.
import (
"fmt"
"go/types"
"math"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
@@ -16,7 +14,7 @@ import (
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, getPos(expr))
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, "")
@@ -29,33 +27,34 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
// 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, getPos(instr))
chanValue := b.getValue(instr.X, getPos(instr))
ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X)
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
// End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
}
@@ -63,31 +62,32 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// 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, getPos(unop))
ch := b.getValue(unop.X)
// Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -126,20 +126,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
}
const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
// (so the select index can be stored in a uint32 with two bits reserved
// for other purposes). It seems unlikely that a real program would have
// that many states, but we check for this case anyway to be sure.
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
// operations which aren't available everywhere.
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
// Continue as usual (we'll generate broken code but the error will
// prevent the compilation to complete).
}
// This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select
// statement.
@@ -154,7 +140,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States {
ch := b.getValue(state.Chan, state.Pos)
ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType)
selectState = b.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir {
@@ -170,10 +156,11 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
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, state.Pos)
sendValue := b.getValue(state.Send)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default:
panic("unreachable")
}
@@ -181,10 +168,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}
// Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.dataPtrType)
recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
recvbufAlloca, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -194,7 +181,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
statesAlloca, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
for i, state := range selectStates {
// Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{
@@ -214,10 +201,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if expr.Blocking {
// Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate.
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
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(chBlockAllocaType, chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block")
@@ -225,23 +212,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result")
// Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(opsAlloca, opsSize)
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else {
opsPtr := llvm.ConstNull(b.dataPtrType)
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
results = b.createRuntimeCall("chanSelect", []llvm.Value{
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
}, "select.result")
}
// Terminate the lifetime of the states alloca.
b.emitLifetimeEnd(statesAlloca, statesSize)
b.emitLifetimeEnd(statesI8, statesSize)
// The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract
@@ -263,7 +247,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
if expr.Index == 0 {
// index
value := b.getValue(expr.Tuple, getPos(expr))
value := b.getValue(expr.Tuple)
index := b.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
index = b.CreateSExt(index, b.intType, "")
@@ -271,7 +255,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
return index
} else if expr.Index == 1 {
// comma-ok
value := b.getValue(expr.Tuple, getPos(expr))
value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "")
} else {
// Select statements are (index, ok, ...) where ... is a number of
@@ -281,6 +265,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type())
return b.CreateLoad(typ, recvbuf, "")
ptr := b.CreateBitCast(recvbuf, llvm.PointerType(typ, 0), "")
return b.CreateLoad(typ, ptr, "")
}
}
+187 -460
View File
File diff suppressed because it is too large Load Diff
+53 -96
View File
@@ -29,7 +29,7 @@ func TestCompiler(t *testing.T) {
t.Parallel()
// Determine Go minor version (e.g. 16 in go1.16.3).
_, goMinor, err := goenv.GetGorootVersion()
_, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
if err != nil {
t.Fatal("could not read Go version:", err)
}
@@ -54,9 +54,6 @@ func TestCompiler(t *testing.T) {
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
}
if goMinor >= 21 {
tests = append(tests, testCase{"go1.21.go", "", ""})
}
for _, tc := range tests {
name := tc.file
@@ -73,11 +70,52 @@ func TestCompiler(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(),
ABI: config.ABI(),
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()
mod, errs := testCompilePackage(t, options, tc.file)
// 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)
@@ -85,18 +123,20 @@ func TestCompiler(t *testing.T) {
return
}
err := llvm.VerifyModule(mod, llvm.PrintMessageAction)
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil {
t.Error(err)
}
// Optimize IR a little.
passOptions := llvm.NewPassBuilderOptions()
defer passOptions.Dispose()
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions)
if err != nil {
t.Error(err)
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 != "" {
@@ -167,95 +207,12 @@ func filterIrrelevantIRLines(lines []string) []string {
if strings.HasPrefix(line, "source_filename = ") {
continue
}
if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") {
if llvmVersion < 14 && strings.HasPrefix(line, "target datalayout = ") {
// The datalayout string may vary betewen LLVM versions.
// Right now test outputs are for LLVM 15 and higher.
// Right now test outputs are for LLVM 14 and higher.
continue
}
out = append(out, line)
}
return out
}
func TestCompilerErrors(t *testing.T) {
t.Parallel()
// Read expected errors from the test file.
var expectedErrors []string
errorsFile, err := os.ReadFile("testdata/errors.go")
if err != nil {
t.Error(err)
}
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for _, line := range strings.Split(errorsFileString, "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
}
}
// Compile the Go file with errors.
options := &compileopts.Options{
Target: "wasm",
}
_, errs := testCompilePackage(t, options, "errors.go")
// Check whether the actual errors match the expected errors.
expectedErrorsIdx := 0
for _, err := range errs {
err := err.(types.Error)
position := err.Fset.Position(err.Pos)
position.Filename = "errors.go" // don't use a full path
if expectedErrorsIdx >= len(expectedErrors) || expectedErrors[expectedErrorsIdx] != err.Msg {
t.Errorf("unexpected compiler error: %s: %s", position.String(), err.Msg)
continue
}
expectedErrorsIdx++
}
}
// Build a package given a number of compiler options and a file.
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
Features: config.Features(),
ABI: config.ABI(),
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/"+file, 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", file, err)
}
// Compile AST to IR.
program := lprogram.LoadSSA()
pkg := lprogram.MainPkg()
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
}
+78 -167
View File
@@ -16,7 +16,6 @@ package compiler
import (
"go/types"
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
@@ -61,8 +60,9 @@ func (b *builder) deferInitFunc() {
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
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.
@@ -100,14 +100,13 @@ func (b *builder) createLandingPad() {
// Continue at the 'recover' block, which returns to the parent in an
// appropriate way.
b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry)
b.CreateBr(b.blockEntries[b.fn.Recover])
}
// Create a checkpoint (similar to setjmp). This emits inline assembly that
// stores the current program counter inside the ptr address (actually
// ptr+sizeof(ptr)) and then returns a boolean indicating whether this is the
// normal flow (false) or we jumped here from somewhere else (true).
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
// 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
@@ -162,7 +161,7 @@ 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},~{memory}"
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" && b.GOOS != "windows" {
// These registers cause the following warning when compiling for
// MacOS and Windows:
@@ -189,24 +188,6 @@ 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 "mips":
// $4 flag (zero or non-zero)
// $5 defer frame
asmString = `
.set noat
move $$4, $$zero
jal 1f
1:
addiu $$ra, 8
sw $$ra, 4($$5)
.set at`
constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{memory}"
if !strings.Contains(b.Features, "+soft-float") {
// Using floating point registers together with GOMIPS=softfloat
// results in a crash: "This value type is not natively supported!"
// So only add them when using hardfloat.
constraints += ",~{$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}"
}
case "riscv32":
asmString = `
la a2, 1f
@@ -218,124 +199,49 @@ li a0, 0
// 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.dataPtrType}, false)
asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false)
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
result := b.CreateCall(asmType, asm, []llvm.Value{ptr}, "setjmp")
result := b.CreateCall(asmType, 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")
return isZero
}
// 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() {
isZero := b.createCheckpoint(b.deferFrame)
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
b.currentBlockInfo.exit = continueBB
b.blockExits[b.currentBlock] = continueBB
}
// isInLoop checks if there is a path from the current block to itself.
// Use Tarjan's strongly connected components algorithm to search for cycles.
// A one-node SCC is a cycle iff there is an edge from the node to itself.
// A multi-node SCC is always a cycle.
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
func (b *builder) isInLoop() bool {
if b.currentBlockInfo.tarjan.lowLink == 0 {
b.strongConnect(b.currentBlock)
}
return b.currentBlockInfo.tarjan.cyclic
}
// isInLoop checks if there is a path from a basic block to itself.
func isInLoop(start *ssa.BasicBlock) bool {
// Use a breadth-first search to scan backwards through the block graph.
queue := []*ssa.BasicBlock{start}
checked := map[*ssa.BasicBlock]struct{}{}
func (b *builder) strongConnect(block *ssa.BasicBlock) {
// Assign a new index.
// Indices start from 1 so that 0 can be used as a sentinel.
assignedIndex := b.tarjanIndex + 1
b.tarjanIndex = assignedIndex
for len(queue) > 0 {
// pop a block off of the queue
block := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Apply the new index.
blockIndex := block.Index
node := &b.blockInfo[blockIndex].tarjan
node.lowLink = assignedIndex
// Push the node onto the stack.
node.onStack = true
b.tarjanStack = append(b.tarjanStack, uint(blockIndex))
// Process the successors.
for _, successor := range block.Succs {
// Look up the successor's state.
successorIndex := successor.Index
if successorIndex == blockIndex {
// Handle a self-cycle specially.
node.cyclic = true
continue
}
successorNode := &b.blockInfo[successorIndex].tarjan
switch {
case successorNode.lowLink == 0:
// This node has not yet been visisted.
b.strongConnect(successor)
case !successorNode.onStack:
// This node has been visited, but is in a different SCC.
// Ignore it, and do not update lowLink.
continue
}
// Update the lowLink index.
// This always uses the min-of-lowlink instead of using index in the on-stack case.
// This is done for two reasons:
// 1. The lowLink update can be shared between the new-node and on-stack cases.
// 2. The assigned index does not need to be saved - it is only needed for root node detection.
if successorNode.lowLink < node.lowLink {
node.lowLink = successorNode.lowLink
}
}
if node.lowLink == assignedIndex {
// This is a root node.
// Pop the SCC off the stack.
stack := b.tarjanStack
top := stack[len(stack)-1]
stack = stack[:len(stack)-1]
blocks := b.blockInfo
topNode := &blocks[top].tarjan
topNode.onStack = false
if top != uint(blockIndex) {
// The root node is not the only node in the SCC.
// Mark all nodes in this SCC as cyclic.
topNode.cyclic = true
for top != uint(blockIndex) {
top = stack[len(stack)-1]
stack = stack[:len(stack)-1]
topNode = &blocks[top].tarjan
topNode.onStack = false
topNode.cyclic = true
// Search through predecessors.
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
// Defers are often placed near the start of the function.
for _, pred := range block.Preds {
if pred == start {
// cycle found
return true
}
if _, ok := checked[pred]; ok {
// block already checked
continue
}
// add to queue and checked map
queue = append(queue, pred)
checked[pred] = struct{}{}
}
b.tarjanStack = stack
}
}
// tarjanNode holds per-block state for isInLoop and strongConnect.
type tarjanNode struct {
// lowLink is the index of the first visited node that is reachable from this block.
// The lowLink indices are assigned by the SCC search, and do not correspond to b.Index.
// A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited.
lowLink uint
// onStack tracks whether this node is currently on the SCC search stack.
onStack bool
// cyclic indicates whether this block is in a loop.
// If lowLink is 0, strongConnect must be called before reading this field.
cyclic bool
return false
}
// createDefer emits a single defer instruction, to be run when this function
@@ -343,7 +249,8 @@ type tarjanNode struct {
func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to
// make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
next := b.CreateLoad(deferType, b.deferPtr, "defer.next")
var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
@@ -360,13 +267,13 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// 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, getPos(instr)) // interface
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.dataPtrType, b.dataPtrType)
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr))
val := b.getValue(arg)
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
@@ -383,7 +290,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// runtime._defer fields).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -395,7 +302,7 @@ 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, getPos(instr))
closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "")
// Get the callback number.
@@ -411,7 +318,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// context pointer).
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -423,7 +330,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg, getPos(instr)))
argValues = append(argValues, b.getValue(arg))
}
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
@@ -446,7 +353,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
}
} else {
funcValue := b.getValue(instr.Call.Value, getPos(instr))
funcValue := b.getValue(instr.Call.Value)
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
@@ -461,7 +368,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
llvmParam := b.getValue(param)
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
@@ -477,18 +384,16 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Put this struct in an allocation.
var alloca llvm.Value
if instr.Block() != b.currentBlock {
panic("block mismatch")
}
if !b.isInLoop() {
if !isInLoop(instr.Block()) {
// This can safely use a stack allocation.
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "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.dataPtrType)
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
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")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
@@ -496,12 +401,14 @@ func (b *builder) createDefer(instr *ssa.Defer) {
b.CreateStore(deferredCall, alloca)
// Push it on top of the linked list by replacing deferPtr.
b.CreateStore(alloca, b.deferPtr)
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
b.CreateStore(allocaCast, b.deferPtr)
}
// createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() {
deferType := b.getLLVMRuntimeType("_defer")
deferPtrType := llvm.PointerType(deferType, 0)
// Add a loop like the following:
// for stack != nil {
@@ -528,7 +435,7 @@ func (b *builder) createRunDefers() {
// Create loop head:
// for stack != nil {
b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "")
deferData := b.CreateLoad(deferPtrType, b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop)
@@ -541,7 +448,7 @@ func (b *builder) createRunDefers() {
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep")
nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next")
nextStack := b.CreateLoad(deferPtrType, nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -562,26 +469,28 @@ func (b *builder) createRunDefers() {
// Call on an value or interface value.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
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.dataPtrType, b.dataPtrType)
valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType)
}
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -596,8 +505,7 @@ func (b *builder) createRunDefers() {
//Get function pointer and context
var context llvm.Value
fnPtr, context = b.decodeFuncValue(funcValue)
fnType = b.getLLVMFunctionType(callback.Signature())
fnType, fnPtr, context = b.decodeFuncValue(funcValue, callback.Signature())
//Pass context
forwardParams = append(forwardParams, context)
@@ -611,7 +519,7 @@ func (b *builder) createRunDefers() {
// 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.dataPtrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
b.createCall(fnType, fnPtr, forwardParams, "")
@@ -620,27 +528,28 @@ func (b *builder) createRunDefers() {
// Direct call.
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// 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(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
// 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.dataPtrType))
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
}
// Call real function.
@@ -650,19 +559,20 @@ func (b *builder) createRunDefers() {
case *ssa.MakeClosure:
// Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.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, b.dataPtrType) // closure
valueTypes = append(valueTypes, b.i8ptrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct.
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
@@ -674,7 +584,7 @@ func (b *builder) createRunDefers() {
db := b.deferBuiltinFuncs[callback]
//Get parameter types
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
@@ -683,12 +593,13 @@ func (b *builder) createRunDefers() {
}
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(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
gep := b.CreateInBoundsGEP(deferredCallType, deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
argValues = append(argValues, forwardParam)
}
+39 -11
View File
@@ -13,14 +13,34 @@ import (
// 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)
}
// 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}
funcValueType := b.getFuncType(sig)
funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType)
funcValue = b.CreateInsertValue(funcValue, context, 0, "")
funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "")
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = 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 {
@@ -34,20 +54,28 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
}
// decodeFuncValue extracts the context and the function pointer from this func
// value.
func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) {
// value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcType llvm.Type, funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "")
funcPtr = b.CreateExtractValue(funcValue, 1, "")
if !funcPtr.IsAConstantExpr().IsNil() && funcPtr.Opcode() == llvm.BitCast {
funcPtr = funcPtr.Operand(0) // needed for LLVM 14 (no opaque pointers)
}
if sig != nil {
funcType = b.getRawFuncType(sig)
llvmSig := llvm.PointerType(funcType, b.funcPtrAddrSpace)
funcPtr = b.CreateBitCast(funcPtr, 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.dataPtrType, c.funcPtrType}, false)
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
}
// getLLVMFunctionType returns a LLVM function type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
// getRawFuncType returns a LLVM function type for a given signature.
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
@@ -75,7 +103,7 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type.
recv = c.dataPtrType
recv = c.i8ptrType
}
for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
@@ -88,7 +116,7 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
}
}
// All functions take these parameters at the end.
paramTypes = append(paramTypes, c.dataPtrType) // context
paramTypes = append(paramTypes, c.i8ptrType) // context
// Make a func type out of the signature.
return llvm.FunctionType(returnType, paramTypes, false)
@@ -106,7 +134,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings {
// The context stores the bound variables.
llvmBoundVar := b.getValue(binding, getPos(expr))
llvmBoundVar := b.getValue(binding)
boundVars[i] = llvmBoundVar
}
+4 -4
View File
@@ -78,9 +78,12 @@ func (b *builder) trackValue(value llvm.Value) {
}
}
// trackPointer creates a call to runtime.trackPointer, bitcasting the pointer
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
// first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) {
if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "")
}
@@ -99,9 +102,6 @@ func typeHasPointers(t llvm.Type) bool {
}
return false
case llvm.ArrayTypeKind:
if t.ArrayLength() == 0 {
return false
}
if typeHasPointers(t.ElementType()) {
return true
}
+60 -296
View File
@@ -7,14 +7,43 @@ import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
)
// createGo emits code to start a new goroutine.
func (b *builder) createGo(instr *ssa.Go) {
if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.getValue(param))
}
var prefix string
var funcPtr llvm.Value
var funcPtrType llvm.Type
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
}
funcPtrType, 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.
@@ -41,49 +70,17 @@ func (b *builder) createGo(instr *ssa.Go) {
var argValues []llvm.Value
for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type())
argValues = append(argValues, b.getValue(arg, getPos(instr)))
argValues = append(argValues, b.getValue(arg))
}
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return
}
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
}
var prefix string
var funcPtr llvm.Value
var funcType llvm.Type
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, getPos(instr))
context = b.extractFuncContext(funcValue)
default:
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcType, funcPtr = b.getFunction(callee)
} else if instr.Call.IsInvoke() {
// This is a method call on an interface value.
itf := b.getValue(instr.Call.Value, getPos(instr))
itf := b.getValue(instr.Call.Value)
itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType()
funcPtrType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode
} else {
@@ -93,8 +90,7 @@ func (b *builder) createGo(instr *ssa.Go) {
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
funcPtrType, 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)
@@ -102,14 +98,14 @@ func (b *builder) createGo(instr *ssa.Go) {
paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
callee := b.createGoroutineStartWrapper(funcPtrType, 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).
stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
stackSize = b.createCall(stackSizeFnType, stackSizeFn, []llvm.Value{callee, llvm.Undef(b.dataPtrType)}, "stacksize")
stackSize = b.createCall(stackSizeFnType, 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.
@@ -119,148 +115,7 @@ func (b *builder) createGo(instr *ssa.Go) {
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
}
fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
}
// Create an exported wrapper function for functions with the //go:wasmexport
// pragma. This wrapper function is quite complex when the scheduler is enabled:
// it needs to start a new goroutine each time the exported function is called.
func (b *builder) createWasmExport() {
pos := b.info.wasmExportPos
if b.info.exported {
// //export really shouldn't be used anymore when //go:wasmexport is
// available, because //go:wasmexport is much better defined.
b.addError(pos, "cannot use //export and //go:wasmexport at the same time")
return
}
const suffix = "#wasmexport"
// Declare the exported function.
paramTypes := b.llvmFnType.ParamTypes()
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
b.addStandardAttributes(exportedFn)
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
// Create a builder for this wrapper function.
builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn)
defer builder.Dispose()
// Define this function as a separate function in DWARF
if b.Debug {
if b.fn.Syntax() != nil {
// Create debug info file if needed.
pos := b.program.Fset.Position(pos)
builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line)
}
builder.setDebugLocation(pos)
}
// Create a single basic block inside of it.
bb := llvm.AddBasicBlock(exportedFn, "entry")
builder.SetInsertPointAtEnd(bb)
// Insert an assertion to make sure this //go:wasmexport function is not
// called at a time when it is not allowed (for example, before the runtime
// is initialized).
builder.createRuntimeCall("wasmExportCheckRun", nil, "")
if b.Scheduler == "none" {
// When the scheduler has been disabled, this is really trivial: just
// call the function.
params := exportedFn.Params()
params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter
retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "")
if b.fn.Signature.Results() == nil {
builder.CreateRetVoid()
} else {
builder.CreateRet(retval)
}
} else {
// The scheduler is enabled, so we need to start a new goroutine, wait
// for it to complete, and read the result value.
// Build a function that looks like this:
//
// func foo#wasmexport(param0, param1, ..., paramN) {
// var state *stateStruct
//
// // 'done' must be explicitly initialized ('state' is not zeroed)
// state.done = false
//
// // store the parameters in the state object
// state.param0 = param0
// state.param1 = param1
// ...
// state.paramN = paramN
//
// // create a goroutine and push it to the runqueue
// task.start(uintptr(gowrapper), &state)
//
// // run the scheduler
// runtime.wasmExportRun(&state.done)
//
// // if there is a return value, load it and return
// return state.result
// }
hasReturn := b.fn.Signature.Results() != nil
// Build the state struct type.
// It stores the function parameters, the 'done' flag, and reserves
// space for a return value if needed.
stateFields := exportedFnType.ParamTypes()
numParams := len(stateFields)
stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field
if hasReturn {
stateFields = append(stateFields, b.llvmFnType.ReturnType())
}
stateStruct := b.ctx.StructType(stateFields, false)
// Allocate the state struct on the stack.
statePtr := builder.CreateAlloca(stateStruct, "status")
// Initialize the 'done' field.
doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false),
}, "done.gep")
builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP)
// Store all parameters in the state object.
for i, param := range exportedFn.Params() {
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "")
builder.CreateStore(param, gep)
}
// Create a new goroutine and add it to the runqueue.
wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos)
stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "")
// Run the scheduler.
builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "")
// Read the return value (if any) and return to the caller of the
// //go:wasmexport function.
if hasReturn {
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false),
}, "")
retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval")
builder.CreateRet(retval)
} else {
builder.CreateRetVoid()
}
}
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "")
}
// createGoroutineStartWrapper creates a wrapper for the task-based
@@ -286,7 +141,7 @@ func (b *builder) createWasmExport() {
// 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(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value {
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value
b := &builder{
@@ -304,18 +159,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if !fn.IsAFunction().IsNil() {
// See whether this wrapper has already been created. If so, return it.
name := fn.Name()
wrapperName := name + "$gowrapper"
if isWasmExport {
wrapperName += "-wasmexport"
}
wrapper = c.mod.NamedFunction(wrapperName)
wrapper = c.mod.NamedFunction(name + "$gowrapper")
if !wrapper.IsNil() {
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
}
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType)
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true)
@@ -345,110 +196,23 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
}
if !isWasmExport {
// Regular 'go' instruction.
// Create the list of params for the call.
paramTypes := fnType.ParamTypes()
if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
}
// Create the list of params for the call.
paramTypes := fnType.ParamTypes()
if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
}
// Create the call.
b.CreateCall(fnType, fn, params, "")
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
if !hasContext {
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
}
// Create the call.
b.CreateCall(fnType, fn, params, "")
if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType),
}, "")
}
} else {
// Goroutine started from a //go:wasmexport pragma.
// The function looks like this:
//
// func foo$gowrapper-wasmexport(state *stateStruct) {
// // load values
// param0 := state.params[0]
// param1 := state.params[1]
//
// // call wrapped functions
// result := foo(param0, param1, ...)
//
// // store result value (if there is any)
// state.result = result
//
// // finish exported function
// state.done = true
// runtime.wasmExportExit()
// }
//
// The state object here looks like:
//
// struct state {
// param0
// param1
// param* // etc
// done bool
// result returnType
// }
returnType := fnType.ReturnType()
hasReturn := returnType != b.ctx.VoidType()
statePtr := wrapper.Param(0)
// Create the state struct (it must match the type in createWasmExport).
stateFields := fnType.ParamTypes()
numParams := len(stateFields) - 1
stateFields = stateFields[:numParams:numParams] // strip 'context' parameter
stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool
if hasReturn {
stateFields = append(stateFields, returnType)
}
stateStruct := b.ctx.StructType(stateFields, false)
// Extract parameters from the state object, and call the function
// that's being wrapped.
var callParams []llvm.Value
for i := 0; i < numParams; i++ {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "")
param := b.CreateLoad(stateFields[i], gep, "")
callParams = append(callParams, param)
}
callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter
result := b.CreateCall(fnType, fn, callParams, "")
// Store the return value back into the shared state.
// Unlike regular goroutines, these special //go:wasmexport
// goroutines can return a value.
if hasReturn {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false),
}, "result.ptr")
b.CreateStore(result, gep)
}
// Mark this function as having finished executing.
// This is important so the runtime knows the exported function
// didn't block.
doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false),
}, "done.gep")
b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP)
// Call back into the runtime. This will exit the goroutine, switch
// back to the scheduler, which will in turn return from the
// //go:wasmexport function.
b.createRuntimeCall("wasmExportExit", nil, "")
if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.i8ptrType),
}, "")
}
} else {
@@ -470,7 +234,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// merged into one.
// Create the wrapper.
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
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)
@@ -515,7 +279,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType),
llvm.Undef(c.i8ptrType),
}, "")
}
}
@@ -530,5 +294,5 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
}
// Return a ptrtoint of the wrapper, not the function itself.
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
return b.CreatePtrToInt(wrapper, c.uintptrType, "")
}
+13 -26
View File
@@ -5,7 +5,6 @@ package compiler
import (
"fmt"
"go/constant"
"go/token"
"regexp"
"strconv"
"strings"
@@ -38,10 +37,10 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
// provided immediately. For example:
//
// arm.AsmFull(
// "str {value}, [{result}]",
// "str {value}, {result}",
// map[string]interface{}{
// "value": 1,
// "result": uintptr(unsafe.Pointer(&dest)),
// "value": 1
// "result": &dest,
// })
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
@@ -56,7 +55,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
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, getPos(instr))
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
case *ssa.Call:
if r.Common() == instr {
break
@@ -99,8 +98,8 @@ 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 starting with LLVM 14,
// probably as a preparation for opaque pointers.
// 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
default:
@@ -141,7 +140,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
//
// The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly.
func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error) {
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -154,7 +153,7 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
} else {
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg, pos)
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -179,7 +178,7 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
// 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, pos token.Pos) (llvm.Value, error) {
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{}
@@ -192,7 +191,7 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
} else {
constraints += ",{x" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg, pos)
llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -232,32 +231,20 @@ func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
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(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil
return b.CreateCall(fnType, 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(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil
return b.CreateCall(fnType, 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(fnType, target, []llvm.Value{b.getValue(call.Args[1], getPos(call))}, ""), nil
return b.CreateCall(fnType, target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
}
}
// Implement runtime/interrupt.Checkpoint.Save. It needs to be implemented
// directly at the call site. If it isn't implemented directly at the call site
// (but instead through a function call), it might result in an overwritten
// stack in the non-jump return case.
func (b *builder) createInterruptCheckpoint(ptr ssa.Value) llvm.Value {
addr := b.getValue(ptr, ptr.Pos())
b.createNilCheck(ptr, addr, "deref")
stackPointer := b.readStackPointer()
b.CreateStore(stackPointer, addr)
return b.createCheckpoint(addr)
}
+77 -411
View File
@@ -6,11 +6,9 @@ package compiler
// interface-lowering.go for more details.
import (
"encoding/binary"
"fmt"
"go/token"
"go/types"
"sort"
"strconv"
"strings"
@@ -18,12 +16,6 @@ import (
"tinygo.org/x/go-llvm"
)
// numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in
// Named, Pointer, and Struct type descriptors. When set, an inline method set
// is present in the type descriptor. Must match the constant in
// src/internal/reflectlite/type.go.
const numMethodHasMethodSet = 0x8000
// Type kinds for basic types.
// They must match the constants for the Kind type in src/reflect/type.go.
var basicTypes = [...]uint8{
@@ -65,15 +57,6 @@ const (
structFieldFlagAnonymous = 1 << iota
structFieldFlagHasTag
structFieldFlagIsExported
structFieldFlagIsEmbedded
)
type reflectChanDir int
const (
refRecvDir reflectChanDir = 1 << iota // <-chan
refSendDir // chan<-
refBothDir = refRecvDir | refSendDir // chan
)
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
@@ -93,95 +76,25 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.
// 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 undefined if the interface is nil or llvmType
// 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]
}
func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
pkgpathName := "reflect/types.type.pkgpath.empty"
if pkgpath != "" {
pkgpathName = "reflect/types.type.pkgpath:" + pkgpath
}
pkgpathGlobal := c.mod.NamedGlobal(pkgpathName)
if pkgpathGlobal.IsNil() {
pkgpathInitializer := c.ctx.ConstString(pkgpath+"\x00", false)
pkgpathGlobal = llvm.AddGlobal(c.mod, pkgpathInitializer.Type(), pkgpathName)
pkgpathGlobal.SetInitializer(pkgpathInitializer)
pkgpathGlobal.SetAlignment(1)
pkgpathGlobal.SetUnnamedAddr(true)
pkgpathGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
pkgpathGlobal.SetGlobalConstant(true)
}
pkgPathPtr := llvm.ConstGEP(pkgpathGlobal.GlobalValueType(), pkgpathGlobal, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
return pkgPathPtr
}
// getTypeCode returns a reference to a type code.
// A type code is a pointer to a constant global that describes the type.
// This function returns a pointer to the 'kind' field (which might not be the
// first field in the struct).
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// Resolve alias types: alias types are resolved at compile time.
typ = types.Unalias(typ)
ms := c.program.MethodSets.MethodSet(typ)
hasMethodSet := ms.Len() != 0
_, isInterface := typ.Underlying().(*types.Interface)
if isInterface {
if _, ok := typ.Underlying().(*types.Interface); ok {
hasMethodSet = false
}
// As defined in https://pkg.go.dev/reflect#Type:
// NumMethod returns the number of methods accessible using Method.
// For a non-interface type, it returns the number of exported methods.
// For an interface type, it returns the number of exported and unexported methods.
var numMethods int
for i := 0; i < ms.Len(); i++ {
if isInterface || ms.At(i).Obj().Exported() {
numMethods++
}
}
// Short-circuit all the global pointer logic here for pointers to pointers.
if typ, ok := typ.(*types.Pointer); ok {
if _, ok := typ.Elem().(*types.Pointer); ok {
// For a pointer to a pointer, we just increase the pointer by 1
ptr := c.getTypeCode(typ.Elem())
// if the type is already *****T or higher, we can't make it.
if typstr := typ.String(); strings.HasPrefix(typstr, "*****") {
c.addError(token.NoPos, fmt.Sprintf("too many levels of pointers for typecode: %s", typstr))
}
return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 1, false),
})
}
}
typeCodeName, isLocal := getTypeCodeName(typ)
globalName := "reflect/types.type:" + typeCodeName
var global llvm.Value
if isLocal {
// This type is a named type inside a function, like this:
//
// func foo() any {
// type named int
// return named(0)
// }
if obj := c.interfaceTypes.At(typ); obj != nil {
global = obj.(llvm.Value)
}
} else {
// Regular type (named or otherwise).
global = c.mod.NamedGlobal(globalName)
}
globalName := "reflect/types.type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
var typeFields []llvm.Value
// Define the type fields. These must match the structs in
@@ -190,97 +103,48 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typeFieldTypes := []*types.Var{
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]),
}
// Compute the method set value for types that support methods.
var methods []*types.Func
for i := 0; i < ms.Len(); i++ {
methods = append(methods, ms.At(i).Obj().(*types.Func))
}
methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))),
}, nil)
methodSetValue := c.getMethodSetValue(methods)
switch typ := typ.(type) {
case *types.Basic:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
case *types.Named:
name := typ.Obj().Name()
var pkgname string
if pkg := typ.Obj().Pkg(); pkg != nil {
pkgname = pkg.Name()
}
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
)
if len(methods) > 0 {
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
}
case *types.Chan, *types.Slice:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))),
)
case *types.Chan:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), // reuse for select chan direction
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
case *types.Slice:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
case *types.Pointer:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
if len(methods) > 0 {
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
}
case *types.Array:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
types.NewVar(token.NoPos, nil, "sliceOf", types.Typ[types.UnsafePointer]),
)
case *types.Map:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "keyType", types.Typ[types.UnsafePointer]),
)
case *types.Struct:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]),
types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
)
if len(methods) > 0 {
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
}
case *types.Interface:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
types.NewVar(token.NoPos, nil, "methods", methodSetType),
)
// TODO: methods
case *types.Signature:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
@@ -300,133 +164,49 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}
globalType := types.NewStruct(typeFieldTypes, nil)
global = llvm.AddGlobal(c.mod, c.getLLVMType(globalType), globalName)
if isLocal {
c.interfaceTypes.Set(typ, global)
}
metabyte := getTypeKind(typ)
// Precompute these so we don't have to calculate them at runtime.
if types.Comparable(typ) {
metabyte |= 1 << 6
}
if hashmapIsBinaryKey(typ) {
metabyte |= 1 << 7
}
switch typ := typ.(type) {
case *types.Basic:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
case *types.Named:
name := typ.Obj().Name()
var pkgpath string
var pkgname string
if pkg := typ.Obj().Pkg(); pkg != nil {
pkgpath = pkg.Path()
pkgname = pkg.Name()
}
pkgPathPtr := c.pkgPathPtr(pkgpath)
namedNumMethods := uint64(numMethods)
if namedNumMethods&numMethodHasMethodSet != 0 {
panic("numMethods overflow: too many exported methods on named type " + name)
}
if len(methods) > 0 {
namedNumMethods |= numMethodHasMethodSet
}
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), namedNumMethods, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Underlying()), // underlying
pkgPathPtr, // pkgpath pointer
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Underlying()), // underlying
}
if len(methods) > 0 {
typeFields = append(typeFields, methodSetValue) // methods
}
typeFields = append(typeFields, c.ctx.ConstString(pkgname+"."+name+"\x00", false)) // name
metabyte |= 1 << 5 // "named" flag
metabyte |= 1 << 5 // "named" flag
case *types.Chan:
var dir reflectChanDir
switch typ.Dir() {
case types.SendRecv:
dir = refBothDir
case types.RecvOnly:
dir = refRecvDir
case types.SendOnly:
dir = refSendDir
}
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(dir), false), // actually channel direction
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
}
case *types.Slice:
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
}
case *types.Pointer:
ptrNumMethods := uint64(numMethods)
if ptrNumMethods&numMethodHasMethodSet != 0 {
panic("numMethods overflow: too many exported methods on pointer type")
}
if len(methods) > 0 {
ptrNumMethods |= numMethodHasMethodSet
}
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), ptrNumMethods, false), // numMethods
c.getTypeCode(typ.Elem()),
}
if len(methods) > 0 {
typeFields = append(typeFields, methodSetValue)
}
typeFields = []llvm.Value{c.getTypeCode(typ.Elem())}
case *types.Array:
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length
c.getTypeCode(types.NewSlice(typ.Elem())), // slicePtr
}
case *types.Map:
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elem
c.getTypeCode(typ.Key()), // key
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elem
c.getTypeCode(typ.Key()), // key
}
case *types.Struct:
var pkgpath string
if typ.NumFields() > 0 {
if pkg := typ.Field(0).Pkg(); pkg != nil {
pkgpath = pkg.Path()
}
}
pkgPathPtr := c.pkgPathPtr(pkgpath)
llvmStructType := c.getLLVMType(typ)
size := c.targetData.TypeStoreSize(llvmStructType)
structNumMethods := uint64(numMethods)
if structNumMethods&numMethodHasMethodSet != 0 {
panic("numMethods overflow: too many exported methods on struct type")
}
if len(methods) > 0 {
structNumMethods |= numMethodHasMethodSet
}
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), structNumMethods, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
pkgPathPtr,
llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size
llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields
c.getTypeCode(types.NewPointer(typ)), // ptrTo
}
structFieldType := c.getLLVMRuntimeType("structField")
var fields []llvm.Value
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
offset := c.targetData.ElementOffset(llvmStructType, i)
var flags uint8
if field.Anonymous() {
flags |= structFieldFlagAnonymous
@@ -437,14 +217,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
if token.IsExported(field.Name()) {
flags |= structFieldFlagIsExported
}
if field.Embedded() {
flags |= structFieldFlagIsEmbedded
}
var offsBytes [binary.MaxVarintLen32]byte
offLen := binary.PutUvarint(offsBytes[:], offset)
data := string(flags) + string(offsBytes[:offLen]) + field.Name() + "\x00"
data := string(flags) + field.Name() + "\x00"
if typ.Tag(i) != "" {
if len(typ.Tag(i)) > 0xff {
c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i))))
@@ -468,14 +241,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}))
}
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
if len(methods) > 0 {
typeFields = append(typeFields, methodSetValue)
}
case *types.Interface:
typeFields = []llvm.Value{
c.getTypeCode(types.NewPointer(typ)),
methodSetValue,
}
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: methods
case *types.Signature:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: params, return values, etc
@@ -486,20 +254,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
}, typeFields...)
if hasMethodSet {
typeFields = append([]llvm.Value{
c.getTypeMethodSet(typ),
llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType),
}, typeFields...)
}
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 {
alignment = 4
}
alignment := c.targetData.TypeAllocSize(c.i8ptrType)
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
if isLocal {
global.SetLinkage(llvm.InternalLinkage)
} else {
global.SetLinkage(llvm.LinkOnceODRLinkage)
}
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true)
global.SetAlignment(int(alignment))
if c.Debug {
@@ -524,8 +285,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
offset = 1
}
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), offset, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), offset, false),
})
}
@@ -580,93 +341,57 @@ var basicTypeNames = [...]string{
// 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.
func getTypeCodeName(t types.Type) (string, bool) {
switch t := types.Unalias(t).(type) {
func getTypeCodeName(t types.Type) string {
switch t := t.(type) {
case *types.Named:
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
return "named:" + t.String() + "$local", true
}
return "named:" + t.String(), false
return "named:" + t.String()
case *types.Array:
s, isLocal := getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
case *types.Basic:
return "basic:" + basicTypeNames[t.Kind()], false
return "basic:" + basicTypeNames[t.Kind()]
case *types.Chan:
s, isLocal := getTypeCodeName(t.Elem())
var dir string
switch t.Dir() {
case types.SendOnly:
dir = "s:"
case types.RecvOnly:
dir = "r:"
case types.SendRecv:
dir = "sr:"
}
return "chan:" + dir + s, isLocal
return "chan:" + getTypeCodeName(t.Elem())
case *types.Interface:
isLocal := false
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
}
s, local := getTypeCodeName(t.Method(i).Type())
if local {
isLocal = true
}
methods[i] = name + ":" + s
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
}
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map:
keyType, keyLocal := getTypeCodeName(t.Key())
elemType, elemLocal := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
keyType := getTypeCodeName(t.Key())
elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer:
s, isLocal := getTypeCodeName(t.Elem())
return "pointer:" + s, isLocal
return "pointer:" + getTypeCodeName(t.Elem())
case *types.Signature:
isLocal := false
params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ {
s, local := getTypeCodeName(t.Params().At(i).Type())
if local {
isLocal = true
}
params[i] = s
params[i] = getTypeCodeName(t.Params().At(i).Type())
}
results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ {
s, local := getTypeCodeName(t.Results().At(i).Type())
if local {
isLocal = true
}
results[i] = s
results[i] = getTypeCodeName(t.Results().At(i).Type())
}
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice:
s, isLocal := getTypeCodeName(t.Elem())
return "slice:" + s, isLocal
return "slice:" + getTypeCodeName(t.Elem())
case *types.Struct:
elems := make([]string, t.NumFields())
isLocal := false
for i := 0; i < t.NumFields(); i++ {
embedded := ""
if t.Field(i).Embedded() {
embedded = "#"
}
s, local := getTypeCodeName(t.Field(i).Type())
if local {
isLocal = true
}
elems[i] = embedded + t.Field(i).Name() + ":" + s
elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`"
}
}
return "struct:" + "{" + strings.Join(elems, ",") + "}", isLocal
return "struct:" + "{" + strings.Join(elems, ",") + "}"
default:
panic("unknown type: " + t.String())
}
@@ -699,7 +424,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.dataPtrType, signatures),
llvm.ConstArray(c.i8ptrType, signatures),
c.ctx.ConstStruct(wrappers, false),
}, false)
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
@@ -750,27 +475,26 @@ func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
// 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, getPos(expr))
itf := b.getValue(expr.X)
assertedType := b.getLLVMType(expr.AssertedType)
actualTypeNum := b.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 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.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
if intf, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
if intf.Empty() {
// intf is the empty interface => no methods
// This type assertion always succeeds, so we can just set commaOk to true.
commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true)
} else {
// Type assert on an interface type with methods.
// Create a call to a declared-but-not-defined function that will
// be lowered by the interface lowering pass into a type-ID
// comparison chain.
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
}
} else {
name, _ := getTypeCodeName(expr.AssertedType)
globalName := "reflect/types.typeid:" + name
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
@@ -797,7 +521,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok")
nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
@@ -843,66 +567,26 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
return strings.Join(methods, "; ")
}
// getMethodSetValue creates the method set struct value for a list of methods.
// The struct contains a length and a sorted array of method signature pointers.
func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// Create a sorted list of method signature global names.
type methodRef struct {
name string
value llvm.Value
// 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.i8ptrType}, 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))
}
var refs []methodRef
for _, method := range methods {
name := method.Name()
if !token.IsExported(name) {
name = method.Pkg().Path() + "." + name
}
s, _ := getTypeCodeName(method.Type())
globalName := "reflect/types.signature:" + name + ":" + s
value := c.mod.NamedGlobal(globalName)
if value.IsNil() {
value = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
value.SetInitializer(llvm.ConstNull(c.ctx.Int8Type()))
value.SetGlobalConstant(true)
value.SetLinkage(llvm.LinkOnceODRLinkage)
value.SetAlignment(1)
if c.Debug {
file := c.getDIFile("<Go type>")
diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{
Name: globalName,
File: file,
Line: 1,
Type: c.getDIType(types.Typ[types.Uint8]),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: 8,
})
value.AddMetadata(0, diglobal)
}
}
refs = append(refs, methodRef{globalName, value})
}
sort.Slice(refs, func(i, j int) bool {
return refs[i].name < refs[j].name
})
var values []llvm.Value
for _, ref := range refs {
values = append(values, ref.value)
}
return c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(len(values)), false),
llvm.ConstArray(c.dataPtrType, values),
}, false)
return llvmFn
}
// 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 {
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
fnName := s + "." + instr.Method.Name() + "$invoke"
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature)
@@ -911,7 +595,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
paramTuple = append(paramTuple, sig.Params().At(i))
}
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
@@ -921,24 +605,6 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
return llvmFn
}
// createInterfaceTypeAssert creates a call to a declared-but-not-defined
// $typeassert function for the given interface. This function will be defined
// by the interface lowering pass as a type-ID comparison chain, avoiding the
// need for runtime.typeImplementsMethodSet at compile time.
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
s, _ := getTypeCodeName(intf)
fnName := s + ".$typeassert"
llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{b.dataPtrType}, false)
llvmFn = llvm.AddFunction(b.mod, fnName, llvmFnType)
b.addStandardDeclaredAttributes(llvmFn)
methods := b.getMethodsString(intf)
llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("tinygo-methods", methods))
}
return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{actualType}, "")
}
// 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.
@@ -969,7 +635,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
}
// create wrapper function
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
paramTypes := append([]llvm.Type{c.i8ptrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper)
@@ -1061,7 +727,7 @@ func signature(sig *types.Signature) string {
// 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 := types.Unalias(t).(type) {
switch t := t.(type) {
case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic:
+2 -5
View File
@@ -24,7 +24,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// 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], getPos(instr))
funcValue := b.getValue(instr.Args[1])
if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error
// message.
@@ -36,13 +36,11 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
}
funcRawPtr, funcContext := b.decodeFuncValue(funcValue)
_, 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.
// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
// global has the lower bits unset.
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)
@@ -50,7 +48,6 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetVisibility(llvm.HiddenVisibility)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetAlignment(1)
initializer := llvm.ConstNull(globalLLVMType)
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
+27 -212
View File
@@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -23,12 +24,8 @@ func (b *builder) defineIntrinsicFunction() {
b.createMemoryCopyImpl()
case name == "runtime.memzero":
b.createMemoryZeroImpl()
case name == "runtime.stacksave":
b.createStackSaveImpl()
case name == "runtime.KeepAlive":
b.createKeepAliveImpl()
case name == "machine.keepAliveNoEscape":
b.createMachineKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -50,24 +47,22 @@ func (b *builder) defineIntrinsicFunction() {
// and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyImpl() {
b.createFunctionStart(true)
params := b.fn.Params[0:3]
b.createMemCopy(
b.fn.Name(),
b.getValue(params[0], getPos(b.fn)),
b.getValue(params[1], getPos(b.fn)),
b.getValue(params[2], getPos(b.fn)),
)
b.CreateRetVoid()
}
func (b *builder) createMemCopy(kind string, dst, src, len llvm.Value) llvm.Value {
fnName := "llvm." + kind + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
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.dataPtrType, b.dataPtrType, b.uintptrType, b.ctx.Int1Type()}, false)
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)
}
return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{dst, src, len, llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
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.GlobalValueType(), llvmFn, params, "")
b.CreateRetVoid()
}
// createMemoryZeroImpl creates calls to llvm.memset.* to zero a block of
@@ -75,43 +70,32 @@ func (b *builder) createMemCopy(kind string, dst, src, len llvm.Value) llvm.Valu
// regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroImpl() {
b.createFunctionStart(true)
llvmFn := b.getMemsetFunc()
fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
if llvmutil.Major() < 15 { // compatibility with LLVM 14
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], getPos(b.fn)),
b.getValue(b.fn.Params[0]),
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
b.getValue(b.fn.Params[1], getPos(b.fn)),
b.getValue(b.fn.Params[1]),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}
b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "")
b.CreateRetVoid()
}
// createStackSaveImpl creates a call to llvm.stacksave.p0 to read the current
// stack pointer.
func (b *builder) createStackSaveImpl() {
b.createFunctionStart(true)
sp := b.readStackPointer()
b.CreateRet(sp)
}
// Return the llvm.memset.p0.i8 function declaration.
func (c *compilerContext) getMemsetFunc() llvm.Value {
fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, fnType)
}
return llvmFn
}
// createKeepAlive creates the runtime.KeepAlive function. It is implemented
// using inline assembly.
func (b *builder) createKeepAliveImpl() {
b.createFunctionStart(true)
// Get the underlying value of the interface value.
interfaceValue := b.getValue(b.fn.Params[0], getPos(b.fn))
interfaceValue := b.getValue(b.fn.Params[0])
pointerValue := b.CreateExtractValue(interfaceValue, 1, "")
// Create an equivalent of the following C code, which is basically just a
@@ -121,44 +105,7 @@ func (b *builder) createKeepAliveImpl() {
//
// It should be portable to basically everything as the "r" register type
// exists basically everywhere.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRetVoid()
}
// createAbiEscapeImpl implements the generic internal/abi.Escape function. It
// currently only supports pointer types.
func (b *builder) createAbiEscapeImpl() {
b.createFunctionStart(true)
// The first parameter is assumed to be a pointer. This is checked at the
// call site of createAbiEscapeImpl.
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))
// Create an equivalent of the following C code, which is basically just a
// nop but ensures the pointerValue is kept alive:
//
// __asm__ __volatile__("" : : "r"(pointerValue))
//
// It should be portable to basically everything as the "r" register type
// exists basically everywhere.
asmType := llvm.FunctionType(b.dataPtrType, []llvm.Type{b.dataPtrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "=r,0", true, false, 0, false)
result := b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
b.CreateRet(result)
}
// Implement machine.keepAliveNoEscape, which makes sure the compiler keeps the
// pointer parameter alive until this point (for GC).
func (b *builder) createMachineKeepAliveImpl() {
b.createFunctionStart(true)
pointerValue := b.getValue(b.fn.Params[0], getPos(b.fn))
// See createKeepAliveImpl for details.
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
asmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType}, false)
asmFn := llvm.InlineAsm(asmType, "", "r", true, false, 0, false)
b.createCall(asmType, asmFn, []llvm.Value{pointerValue}, "")
@@ -202,140 +149,8 @@ func (b *builder) defineMathOp() {
// 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, getPos(b.fn))
args[i] = b.getValue(param)
}
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
b.CreateRet(result)
}
func (b *builder) defineCryptoIntrinsic() bool {
if b.fn.Pkg.Pkg.Path() != "crypto/internal/constanttime" {
return false
}
switch b.fn.Name() {
case "boolToUint8":
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
result := b.CreateZExt(param, b.ctx.Int8Type(), "")
b.CreateRet(result)
return true
}
return false
}
// Implement most math/bits functions.
//
// This implements all the functions that operate on bits. It does not yet
// implement the arithmetic functions (like bits.Add), which also have LLVM
// intrinsics.
func (b *builder) defineMathBitsIntrinsic() bool {
if b.fn.Pkg.Pkg.Path() != "math/bits" {
return false
}
name := b.fn.Name()
switch name {
case "LeadingZeros", "LeadingZeros8", "LeadingZeros16", "LeadingZeros32", "LeadingZeros64",
"TrailingZeros", "TrailingZeros8", "TrailingZeros16", "TrailingZeros32", "TrailingZeros64":
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
valueType := param.Type()
var intrinsicName string
if strings.HasPrefix(name, "Leading") { // LeadingZeros
intrinsicName = "llvm.ctlz.i" + strconv.Itoa(valueType.IntTypeWidth())
} else { // TrailingZeros
intrinsicName = "llvm.cttz.i" + strconv.Itoa(valueType.IntTypeWidth())
}
llvmFn := b.mod.NamedFunction(intrinsicName)
llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false)
if llvmFn.IsNil() {
llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType)
}
result := b.createCall(llvmFnType, llvmFn, []llvm.Value{
param,
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}, "")
result = b.createZExtOrTrunc(result, b.intType)
b.CreateRet(result)
return true
case "Len", "Len8", "Len16", "Len32", "Len64":
// bits.Len can be implemented as:
// (unsafe.Sizeof(v) * 8) - bits.LeadingZeros(n)
// Not sure why this isn't already done in the standard library, as it
// is much simpler than a lookup table.
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
valueType := param.Type()
valueBits := valueType.IntTypeWidth()
intrinsicName := "llvm.ctlz.i" + strconv.Itoa(valueBits)
llvmFn := b.mod.NamedFunction(intrinsicName)
llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, b.ctx.Int1Type()}, false)
if llvmFn.IsNil() {
llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType)
}
result := b.createCall(llvmFnType, llvmFn, []llvm.Value{
param,
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}, "")
result = b.createZExtOrTrunc(result, b.intType)
maxLen := llvm.ConstInt(b.intType, uint64(valueBits), false) // number of bits in the value
result = b.CreateSub(maxLen, result, "")
b.CreateRet(result)
return true
case "OnesCount", "OnesCount8", "OnesCount16", "OnesCount32", "OnesCount64":
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
valueType := param.Type()
intrinsicName := "llvm.ctpop.i" + strconv.Itoa(valueType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(intrinsicName)
llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false)
if llvmFn.IsNil() {
llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType)
}
result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "")
result = b.createZExtOrTrunc(result, b.intType)
b.CreateRet(result)
return true
case "Reverse", "Reverse8", "Reverse16", "Reverse32", "Reverse64",
"ReverseBytes", "ReverseBytes16", "ReverseBytes32", "ReverseBytes64":
b.createFunctionStart(true)
param := b.getValue(b.fn.Params[0], b.fn.Pos())
valueType := param.Type()
var intrinsicName string
if strings.HasPrefix(name, "ReverseBytes") {
intrinsicName = "llvm.bswap.i" + strconv.Itoa(valueType.IntTypeWidth())
} else { // Reverse
intrinsicName = "llvm.bitreverse.i" + strconv.Itoa(valueType.IntTypeWidth())
}
llvmFn := b.mod.NamedFunction(intrinsicName)
llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType}, false)
if llvmFn.IsNil() {
llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType)
}
result := b.createCall(llvmFnType, llvmFn, []llvm.Value{param}, "")
b.CreateRet(result)
return true
case "RotateLeft", "RotateLeft8", "RotateLeft16", "RotateLeft32", "RotateLeft64":
// Warning: the documentation says these functions must be constant time.
// I do not think LLVM guarantees this, but there's a good chance LLVM
// already recognized the rotate instruction so it probably won't get
// any _worse_ by implementing these rotate functions.
b.createFunctionStart(true)
x := b.getValue(b.fn.Params[0], b.fn.Pos())
k := b.getValue(b.fn.Params[1], b.fn.Pos())
valueType := x.Type()
intrinsicName := "llvm.fshl.i" + strconv.Itoa(valueType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(intrinsicName)
llvmFnType := llvm.FunctionType(valueType, []llvm.Type{valueType, valueType, valueType}, false)
if llvmFn.IsNil() {
llvmFn = llvm.AddFunction(b.mod, intrinsicName, llvmFnType)
}
k = b.createZExtOrTrunc(k, valueType)
result := b.createCall(llvmFnType, llvmFn, []llvm.Value{x, x, k}, "")
b.CreateRet(result)
return true
default:
return false
}
}
+146 -162
View File
@@ -1,13 +1,12 @@
package compiler
import (
"encoding/binary"
"fmt"
"go/token"
"go/types"
"math/big"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm"
)
@@ -21,7 +20,7 @@ import (
//
// 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, size llvm.Value) {
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
}
@@ -64,45 +63,47 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Allocate memory for the packed data.
size := b.targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(b.dataPtrType)
return llvm.ConstPointerNull(b.i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return values[0]
} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
return b.CreateBitCast(values[0], b.i8ptrType, "pack.ptr")
} else if size <= b.targetData.TypeAllocSize(b.i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return b.CreateIntToPtr(values[0], b.dataPtrType, "pack.int")
return b.CreateIntToPtr(values[0], b.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, _ := b.createTemporaryAlloca(b.dataPtrType, "")
packedAlloc, _, _ := b.createTemporaryAlloca(b.i8ptrType, "")
if size < b.targetData.TypeAllocSize(b.dataPtrType) {
if size < b.targetData.TypeAllocSize(b.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.
b.CreateStore(llvm.ConstNull(b.dataPtrType), packedAlloc)
b.CreateStore(llvm.ConstNull(b.i8ptrType), packedAlloc)
}
// Store all values in the alloca.
packedAllocCast := b.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
gep := b.CreateInBoundsGEP(packedType, packedAllocCast, indices, "")
b.CreateStore(value, gep)
}
// Load value (the *i8) from the alloca.
result := b.CreateLoad(b.dataPtrType, packedAlloc, "")
result := b.CreateLoad(b.i8ptrType, packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer.
packedPtr := b.CreateBitCast(packedAlloc, b.i8ptrType, "")
packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false)
b.emitLifetimeEnd(packedAlloc, packedSize)
b.emitLifetimeEnd(packedPtr, packedSize)
return result
} else {
@@ -123,22 +124,21 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage)
return global
return llvm.ConstBitCast(global, b.i8ptrType)
}
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
align := b.targetData.ABITypeAlignment(packedType)
alloc := b.mod.NamedFunction("runtime.alloc")
packedAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
packedHeapAlloc := b.CreateCall(alloc.GlobalValueType(), alloc, []llvm.Value{
sizeValue,
llvm.ConstNull(b.dataPtrType),
llvm.Undef(b.dataPtrType), // unused context parameter
llvm.ConstNull(b.i8ptrType),
llvm.Undef(b.i8ptrType), // unused context parameter
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
b.trackPointer(packedHeapAlloc)
}
packedAlloc := b.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
// Store all values in the heap pointer.
for i, value := range values {
@@ -151,7 +151,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
}
// Return the original heap allocation pointer, which already is an *i8.
return packedAlloc
return packedHeapAlloc
}
}
@@ -160,27 +160,28 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
packedType := b.ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc llvm.Value
needsLifetimeEnd := false
var packedAlloc, packedRawAlloc llvm.Value
size := b.targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{ptr}
} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
return []llvm.Value{b.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= b.targetData.TypeAllocSize(b.i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{b.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedAlloc, _ = b.createTemporaryAlloca(b.dataPtrType, "unpack.raw.alloc")
b.CreateStore(ptr, packedAlloc)
needsLifetimeEnd = true
packedRawAlloc, _, _ = b.createTemporaryAlloca(llvm.PointerType(b.i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := b.CreateBitCast(ptr, llvm.PointerType(b.i8ptrType, 0), "unpack.raw.value")
b.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = b.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap.
packedAlloc = ptr
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = b.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
@@ -197,9 +198,10 @@ func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []ll
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
values[i] = b.CreateLoad(valueType, gep, "")
}
if needsLifetimeEnd {
if !packedRawAlloc.IsNil() {
allocPtr := b.CreateBitCast(packedRawAlloc, b.i8ptrType, "")
allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false)
b.emitLifetimeEnd(packedAlloc, allocSize)
b.emitLifetimeEnd(allocPtr, allocSize)
}
return values
}
@@ -231,12 +233,6 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
//
// For details on what's in this value, see src/runtime/gc_precise.go.
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
if !typeHasPointers(t) {
// There are no pointers in this type, so we can simplify the layout.
layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
// Use the element type for arrays. This works even for nested arrays.
for {
kind := t.TypeKind()
@@ -254,29 +250,54 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
break
}
// Create the pointer bitmap.
// Do a few checks to see whether we need to generate any object layout
// information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerAlignment := uint64(c.targetData.PrefTypeAlignment(c.dataPtrType))
bitmapLen := objectSizeBytes / pointerAlignment
bitmapBytes := (bitmapLen + 7) / 8
bitmap := make([]byte, bitmapBytes, max(bitmapBytes, 8))
c.buildPointerBitmap(bitmap, pointerAlignment, pos, t, 0)
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)
// Try to encode the layout inline.
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType)
pointerBits := pointerSize * 8
if bitmapLen < pointerBits {
rawMask := binary.LittleEndian.Uint64(bitmap[0:8])
layout := rawMask*pointerBits + bitmapLen
layout <<= 1
layout |= 1
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
// Check if the layout fits.
layout &= 1<<pointerBits - 1
if (layout>>1)/pointerBits == rawMask {
// No set bits were shifted off.
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType)
}
// 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
@@ -284,24 +305,25 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// 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", bitmapLen, (bitmapLen+15)/16, bitmap)
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName)
if !global.IsNil() {
return global
return llvm.ConstBitCast(global, c.i8ptrType)
}
// Create the global initializer.
bitmapByteValues := make([]llvm.Value, bitmapBytes)
i8 := c.ctx.Int8Type()
for i, b := range bitmap {
bitmapByteValues[i] = llvm.ConstInt(i8, uint64(b), false)
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
bitmap.FillBytes(bitmapBytes)
reverseBytes(bitmapBytes) // big-endian to little-endian
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, bitmapLen, false),
llvm.ConstArray(i8, bitmapByteValues),
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
}, false)
// Create the actual global.
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
global.SetInitializer(initializer)
global.SetUnnamedAddr(true)
@@ -309,7 +331,6 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.SetLinkage(llvm.LinkOnceODRLinkage)
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
// AVR doesn't have alignment by default.
// The lowest bit must be unset to distinguish this from an inline layout.
global.SetAlignment(2)
}
if c.Debug && pos != token.NoPos {
@@ -338,85 +359,80 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.AddMetadata(0, diglobal)
}
return global
return llvm.ConstBitCast(global, c.i8ptrType)
}
// buildPointerBitmap scans the given LLVM type for pointers and sets bits in a
// bitmap at the word offset that contains a pointer. This scan is recursive.
func (c *compilerContext) buildPointerBitmap(
dst []byte,
ptrAlign uint64,
pos token.Pos,
t llvm.Type,
offset uint64,
) {
switch t.TypeKind() {
// 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:
// These types do not contain pointers.
return big.NewInt(0)
case llvm.PointerTypeKind:
// Set the corresponding position in the bitmap.
dst[offset/8] |= 1 << (offset % 8)
return big.NewInt(1)
case llvm.StructTypeKind:
// Recurse over struct elements.
for i, et := range t.StructElementTypes() {
eo := c.targetData.ElementOffset(t, i)
if eo%uint64(ptrAlign) != 0 {
if typeHasPointers(et) {
// 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")
}
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
}
c.buildPointerBitmap(
dst,
ptrAlign,
pos,
et,
offset+(eo/ptrAlign),
)
}
case llvm.ArrayTypeKind:
// Recurse over array elements.
len := t.ArrayLength()
if len <= 0 {
return
}
et := t.ElementType()
elementSize := c.targetData.TypeAllocSize(et)
if elementSize%ptrAlign != 0 {
if typeHasPointers(et) {
// 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")
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
}
return
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
ptrs.Or(ptrs, subptrs)
}
elementSize /= ptrAlign
for i := 0; i < len; i++ {
c.buildPointerBitmap(
dst,
ptrAlign,
pos,
et,
offset+uint64(i)*elementSize,
)
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 architecture from the LLVM triple but with some
// 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 {
return compileopts.CanonicalArchName(c.Triple)
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
@@ -440,46 +456,14 @@ func (c *compilerContext) isThumb() bool {
// readStackPointer emits a LLVM intrinsic call that returns the current stack
// pointer as an *i8.
func (b *builder) readStackPointer() llvm.Value {
name := "llvm.stacksave.p0"
if llvmutil.Version() < 18 {
name = "llvm.stacksave" // backwards compatibility with LLVM 17 and below
}
stacksave := b.mod.NamedFunction(name)
stacksave := b.mod.NamedFunction("llvm.stacksave")
if stacksave.IsNil() {
fnType := llvm.FunctionType(b.dataPtrType, nil, false)
stacksave = llvm.AddFunction(b.mod, name, fnType)
fnType := llvm.FunctionType(b.i8ptrType, nil, false)
stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
}
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
}
// writeStackPointer emits a LLVM intrinsic call that updates the current stack
// pointer.
func (b *builder) writeStackPointer(sp llvm.Value) {
name := "llvm.stackrestore.p0"
if llvmutil.Version() < 18 {
name = "llvm.stackrestore" // backwards compatibility with LLVM 17 and below
}
stackrestore := b.mod.NamedFunction(name)
if stackrestore.IsNil() {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.dataPtrType}, false)
stackrestore = llvm.AddFunction(b.mod, name, fnType)
}
b.CreateCall(stackrestore.GlobalValueType(), stackrestore, []llvm.Value{sp}, "")
}
// createZExtOrTrunc lets the input value fit in the output type bits, by zero
// extending or truncating the integer.
func (b *builder) createZExtOrTrunc(value llvm.Value, t llvm.Type) llvm.Value {
valueBits := value.Type().IntTypeWidth()
resultBits := t.IntTypeWidth()
if valueBits > resultBits {
value = b.CreateTrunc(value, t, "")
} else if valueBits < resultBits {
value = b.CreateZExt(value, t, "")
}
return value
}
// Reverse a slice of bytes. From the wiki:
// https://github.com/golang/go/wiki/SliceTricks#reversing
func reverseBytes(buf []byte) {
+34 -37
View File
@@ -1,5 +1,5 @@
// Package llvmutil contains utility functions used across multiple compiler
// packages. For example, they may be used by both the compiler package and
// packages. For example, they may be used by both the compiler pacakge and
// transformation packages.
//
// Normally, utility packages are avoided. However, in this case, the utility
@@ -8,13 +8,22 @@
package llvmutil
import (
"encoding/binary"
"strconv"
"strings"
"tinygo.org/x/go-llvm"
)
// Major returns the LLVM major version.
func Major() int {
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
if err != nil {
// sanity check, should be unreachable
panic("could not parse LLVM version: " + err.Error())
}
return llvmMajor
}
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is
// at the end of the current block.
@@ -32,19 +41,21 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm
}
// 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 CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, name string) (alloca, size llvm.Value) {
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")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
return
}
@@ -53,19 +64,21 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next)
} else {
builder.SetInsertPointAtEnd(inst.InstructionParent())
}
fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, bitcast}, "")
return alloca
}
@@ -81,10 +94,13 @@ func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value
// first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.start.p0"
if Major() < 15 { // compatibility with LLVM 14
fnName = "llvm.lifetime.start.p0i8"
}
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -95,10 +111,13 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
// first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fnName := "llvm.lifetime.end.p0"
if Major() < 15 {
fnName = "llvm.lifetime.end.p0i8"
}
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -177,7 +196,7 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv
return newBlock
}
// AppendToGlobal appends the given values to a global array like llvm.used. The global might
// Append the given values to a global array like llvm.used. The global might
// not exist yet. The values can be any pointer type, they will be cast to i8*.
func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
// Read the existing values in the llvm.used array (if it exists).
@@ -194,36 +213,14 @@ func AppendToGlobal(mod llvm.Module, globalName string, values ...llvm.Value) {
}
// Add the new values.
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, value := range values {
// Note: the bitcast is necessary to cast AVR function pointers to
// address space 0 pointer types.
usedValues = append(usedValues, llvm.ConstPointerCast(value, ptrType))
usedValues = append(usedValues, llvm.ConstPointerCast(value, i8ptrType))
}
// Create a new array (with the old and new values).
usedInitializer := llvm.ConstArray(ptrType, usedValues)
usedInitializer := llvm.ConstArray(i8ptrType, usedValues)
used := llvm.AddGlobal(mod, usedInitializer.Type(), globalName)
used.SetInitializer(usedInitializer)
used.SetLinkage(llvm.AppendingLinkage)
}
// Version returns the LLVM major version.
func Version() int {
majorStr := strings.Split(llvm.Version, ".")[0]
major, err := strconv.Atoi(majorStr)
if err != nil {
panic("unexpected error while parsing LLVM version: " + err.Error()) // should not happen
}
return major
}
// ByteOrder returns the byte order for the given target triple. Most targets are little
// endian, but for example MIPS can be big-endian.
func ByteOrder(target string) binary.ByteOrder {
if strings.HasPrefix(target, "mips-") {
return binary.BigEndian
} else {
return binary.LittleEndian
}
}
+49 -45
View File
@@ -6,11 +6,17 @@ import (
"go/token"
"go/types"
"github.com/tinygo-org/tinygo/src/tinygo"
"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) {
@@ -18,20 +24,20 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64
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 = uint64(tinygo.HashmapAlgorithmString)
alg = hashmapAlgorithmString
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = uint64(tinygo.HashmapAlgorithmBinary)
alg = hashmapAlgorithmBinary
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = uint64(tinygo.HashmapAlgorithmInterface)
alg = hashmapAlgorithmInterface
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
@@ -40,7 +46,7 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve, getPos(expr))
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 {
@@ -59,7 +65,7 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// 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, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
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
@@ -72,38 +78,37 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value
origKeyType := keyType
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.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, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap.
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
b.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, origKeyType, pos)
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, mapValueAlloca, mapValueSize}
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
}
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
@@ -118,39 +123,37 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// 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, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
origKeyType := keyType
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, key, valueAlloca}
params := []llvm.Value{m, key, valuePtr}
b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca, valueAlloca}
params := []llvm.Value{m, keyPtr, valuePtr}
b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
b.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, origKeyType, pos)
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey, valueAlloca}
params := []llvm.Value{m, itfKey, valuePtr}
b.createRuntimeCall("hashmapInterfaceSet", params, "")
}
b.emitLifetimeEnd(valueAlloca, valueSize)
b.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 {
origKeyType := keyType
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
@@ -158,12 +161,12 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
b.createRuntimeCall("hashmapStringDelete", params, "")
return nil
} else if hashmapIsBinaryKey(keyType) {
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca)
params := []llvm.Value{m, keyAlloca}
params := []llvm.Value{m, keyPtr}
b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyAlloca, keySize)
b.emitLifetimeEnd(keyPtr, keySize)
return nil
} else {
// Key is not trivially comparable, so compare it as an interface
@@ -171,7 +174,7 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
itfKey := key
if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first.
itfKey = b.createMakeInterface(key, origKeyType, pos)
itfKey = b.createMakeInterface(key, keyType, pos)
}
params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
@@ -179,11 +182,6 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
}
}
// Clear the given map.
func (b *builder) createMapClear(m llvm.Value) {
b.createRuntimeCall("hashmapClear", []llvm.Value{m}, "")
}
// 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.
@@ -219,9 +217,9 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
}
// Extract the key and value from the map.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next")
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(llvmStoredKeyType, mapKeyAlloca, "")
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
@@ -232,8 +230,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
}
// End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
b.emitLifetimeEnd(mapValueAlloca, mapValueSize)
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))
@@ -248,11 +246,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
// can be compared with runtime.memequal. Note that padding bytes are undef
// and can alter two "equal" structs being equal when compared with memequal.
func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.Underlying().(type) {
switch keyType := keyType.(type) {
case *types.Basic:
// TODO: unsafe.Pointer is also a binary key, but to support that we
// need to fix an issue with interp first (see
// https://github.com/tinygo-org/tinygo/pull/4898).
return keyType.Info()&(types.IsBoolean|types.IsInteger) != 0
case *types.Pointer:
return true
@@ -266,6 +261,8 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
return true
case *types.Array:
return hashmapIsBinaryKey(keyType.Elem())
case *types.Named:
return hashmapIsBinaryKey(keyType.Underlying())
default:
return false
}
@@ -321,14 +318,21 @@ func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
if i < numFields-1 {
nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
} else {
// Last field? Next offset is the total size of the allocate struct.
// Last field? Next offset is the total size of the allcoate struct.
nextOffset = b.targetData.TypeAllocSize(llvmStructType)
}
if fieldEndOffset != nextOffset {
n := llvm.ConstInt(b.uintptrType, nextOffset-fieldEndOffset, false)
llvmStoreSize := llvm.ConstInt(b.uintptrType, storeSize, false)
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), elemPtr, []llvm.Value{llvmStoreSize}, "")
gepPtr := elemPtr
if gepPtr.Type() != b.i8ptrType {
gepPtr = b.CreateBitCast(gepPtr, b.i8ptrType, "") // LLVM 14
}
paddingStart := b.CreateInBoundsGEP(b.ctx.Int8Type(), gepPtr, []llvm.Value{llvmStoreSize}, "")
if paddingStart.Type() != b.i8ptrType {
paddingStart = b.CreateBitCast(paddingStart, b.i8ptrType, "") // LLVM 14
}
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
}
}
+126 -377
View File
@@ -4,7 +4,6 @@ package compiler
// pragmas, determines the link name, etc.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
@@ -12,7 +11,6 @@ import (
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -24,18 +22,15 @@ import (
// The linkName value contains a valid link name, even if //go:linkname is not
// present.
type functionInfo struct {
wasmModule string // go:wasm-module
wasmName string // wasm-export-name or wasm-import-name in the IR
wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper)
wasmExportPos token.Pos // position of //go:wasmexport comment
linkName string // go:linkname, go:export - the IR function name
section string // go:section - object file section name
exported bool // go:export, CGo
interrupt bool // go:interrupt
nobounds bool // go:nobounds
noescape bool // go:noescape
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
module string // go:wasm-module
importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name
exported bool // go:export, CGo
interrupt bool // go:interrupt
nobounds bool // go:nobounds
variadic bool // go:variadic (CGo only)
inline inlineType // go:inline
}
type inlineType int
@@ -57,17 +52,6 @@ const (
inlineNone
)
// Values for the allockind attribute. Source:
// https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/IR/Attributes.h#L49
const (
allocKindAlloc = 1 << iota
allocKindRealloc
allocKindFree
allocKindUninitialized
allocKindZeroed
allocKindAligned
)
// getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) {
@@ -100,7 +84,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// Add an extra parameter as the function context. This context is used in
// closures and bound methods, but should be optimized away when not used.
if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0})
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", elemSize: 0})
}
var paramTypes []llvm.Type
@@ -128,25 +112,11 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
c.addStandardDeclaredAttributes(llvmFn)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, paramInfo := range paramInfos {
if paramInfo.elemSize != 0 {
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
for i, info := range paramInfos {
if info.elemSize != 0 {
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
}
if info.noescape && paramInfo.flags&paramIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
// Parameters to functions with a //go:noescape parameter should get
// the nocapture attribute. However, the context parameter should
// not.
// (It may be safe to add the nocapture parameter to the context
// parameter, but I'd like to stay on the safe side here).
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
// Readonly pointer parameters (like strings) benefit from being marked as readonly.
readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)
llvmFn.AddAttributeAtIndex(i+1, readonly)
}
}
// Set a number of function or parameter attributes, depending on the
@@ -157,39 +127,23 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
// Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.alloc":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
}
// Add attributes to signal to LLVM that this is an allocator function.
// This enables a number of optimizations.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc"))
// Use a special value to indicate the first parameter:
// > allocsize has two integer arguments, but because they're both 32 bits, we can
// > pack them into one 64-bit value, at the cost of making said value
// > nonsensical.
// >
// > In order to do this, we need to reserve one value of the second (optional)
// > allocsize argument to signify "not present."
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff))
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromBytes":
case "runtime.sliceCopy":
// Copying a slice won't capture any of the parameters.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromRunes":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer
@@ -200,7 +154,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
if strings.Split(c.Triple, "-")[0] == "avr" {
// These functions are compiler-rt/libgcc functions that are
// currently implemented in Go. Assembly versions should appear in
// LLVM 17 hopefully. Until then, they need to be made available to
// LLVM 16 hopefully. Until then, they need to be made available to
// the linker and the best way to do that is llvm.compiler.used.
// I considered adding a pragma for this, but the LLVM language
// reference explicitly says that this feature should not be exposed
@@ -209,25 +163,25 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// > circumstances, and should not be exposed to source languages.
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
}
case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc":
// On Windows we need to use a special calling convention for some
// external calls.
if c.GOOS == "windows" && c.GOARCH == "386" {
llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv)
}
}
// External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported {
if c.archFamily() == "wasm32" && len(fn.Blocks) == 0 {
if c.archFamily() == "wasm32" {
// We need to add the wasm-import-module and the wasm-import-name
// attributes.
if info.wasmModule != "" {
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule))
module := info.module
if module == "" {
module = "env"
}
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", module))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName))
name := info.importName
if name == "" {
name = info.linkName
}
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", name))
}
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
@@ -238,43 +192,13 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
}
}
// Build the function if needed.
c.maybeCreateSyntheticFunction(fn, llvmFn)
return fnType, llvmFn
}
// If this is a synthetic function (such as a generic function or a wrapper),
// create it now.
func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) {
// Synthetic functions are functions that do not appear in the source code,
// they are artificially constructed. Usually they are wrapper functions
// that are not referenced anywhere except in a SSA call instruction so
// should be created right away.
// The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here.
if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" {
if origin := fn.Origin(); origin != nil && origin.RelString(nil) == "internal/abi.Escape" {
// This is a special implementation or internal/abi.Escape, which
// can only really be implemented in the compiler.
// For simplicity we'll only implement pointer parameters for now.
if _, ok := fn.Params[0].Type().Underlying().(*types.Pointer); ok {
irbuilder := c.ctx.NewBuilder()
defer irbuilder.Dispose()
b := newBuilder(c, irbuilder, fn)
b.createAbiEscapeImpl()
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
// If the parameter is not of a pointer type, it will be left
// unimplemented. This will result in a linker error if the function
// is really called, making it clear it needs to be implemented.
return
}
if len(fn.Blocks) == 0 {
c.addError(fn.Pos(), "missing function body")
return
}
if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" {
irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn)
b.createFunction()
@@ -282,287 +206,116 @@ func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
llvmFn.SetUnnamedAddr(true)
}
return fnType, llvmFn
}
// getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be
// exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
if info, ok := c.functionInfos[f]; ok {
return info
}
info := functionInfo{
// Pick the default linkName.
linkName: f.RelString(nil),
}
// Check for a few runtime functions that are treated specially.
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
info.linkName = "_initialize"
info.wasmName = "_initialize"
info.exported = true
}
if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" {
info.linkName = "_start"
info.wasmName = "_start"
info.exported = true
}
if info.linkName == "runtime.wasmEntryLegacy" && c.BuildMode == "wasi-legacy" {
info.linkName = "_start"
info.wasmName = "_start"
info.exported = true
}
// Check for //go: pragmas, which may change the link name (among others).
c.parsePragmas(&info, f)
c.functionInfos[f] = info
info.parsePragmas(f)
return info
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline.
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
syntax := f.Syntax()
if f.Origin() != nil {
syntax = f.Origin().Syntax()
}
if syntax == nil {
func (info *functionInfo) parsePragmas(f *ssa.Function) {
if f.Syntax() == nil {
return
}
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
var importName string
// Read all pragmas of this function.
var pragmas []*ast.Comment
hasWasmExport := false
if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil {
for _, comment := range decl.Doc.List {
text := comment.Text
if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") {
pragmas = append(pragmas, comment)
if strings.HasPrefix(comment.Text, "//go:wasmexport ") {
hasWasmExport = true
if strings.HasPrefix(text, "//export ") {
// Rewrite '//export' to '//go:export' for compatibility with
// gc.
text = "//go:" + text[2:]
}
if !strings.HasPrefix(text, "//go:") {
continue
}
parts := strings.Fields(text)
switch parts[0] {
case "//go:export":
if len(parts) != 2 {
continue
}
importName = parts[1]
info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true
}
case "//go:wasm-module":
// Alternative comment for setting the import module.
if len(parts) != 2 {
continue
}
info.module = parts[1]
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
case "//go:variadic":
// The //go:variadic pragma is emitted by the CGo preprocessing
// pass for C variadic functions. This includes both explicit
// (with ...) and implicit (no parameters in signature)
// functions.
if strings.HasPrefix(f.Name(), "C.") {
// This prefix cannot naturally be created, it must have
// been created as a result of CGo preprocessing.
info.variadic = true
}
}
}
}
// Parse each pragma.
for _, comment := range pragmas {
parts := strings.Fields(comment.Text)
switch parts[0] {
case "//export", "//go:export":
if len(parts) != 2 {
continue
}
if hasWasmExport {
// //go:wasmexport overrides //export.
continue
}
info.linkName = parts[1]
info.wasmName = info.linkName
info.exported = true
case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true
}
case "//go:wasm-module":
// Alternative comment for setting the import module.
// This is deprecated, use //go:wasmimport instead.
if len(parts) != 2 {
continue
}
info.wasmModule = parts[1]
case "//go:wasmimport":
// Import a WebAssembly function, for example a WASI function.
// Original proposal: https://github.com/golang/go/issues/38248
// Allow globally: https://github.com/golang/go/issues/59149
if len(parts) != 3 {
continue
}
if f.Blocks != nil {
// Defined functions cannot be exported.
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
continue
}
c.checkWasmImportExport(f, comment.Text)
info.exported = true
info.wasmModule = parts[1]
info.wasmName = parts[2]
case "//go:wasmexport":
if f.Blocks == nil {
c.addError(f.Pos(), "can only use //go:wasmexport on definitions")
continue
}
if len(parts) != 2 {
c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1))
continue
}
name := parts[1]
if name == "_start" || name == "_initialize" {
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name))
continue
}
if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" {
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode))
continue
}
if c.archFamily() != "wasm32" {
c.addError(f.Pos(), "//go:wasmexport is only supported on wasm")
}
c.checkWasmImportExport(f, comment.Text)
info.wasmExport = name
info.wasmExportPos = comment.Slash
case "//go:inline":
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
// Only enable go:section when the package imports "unsafe".
// go:section also implies go:noinline since inlining could
// move the code to a different section than that requested.
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
info.inline = inlineNone
}
case "//go:nobounds":
// Skip bounds checking in this function. Useful for some
// runtime functions.
// This is somewhat dangerous and thus only imported in packages
// that import unsafe.
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
case "//go:noescape":
// Don't let pointer parameters escape.
// Following the upstream Go implementation, we only do this for
// declarations, not definitions.
if len(f.Blocks) == 0 {
info.noescape = true
}
case "//go:variadic":
// The //go:variadic pragma is emitted by the CGo preprocessing
// pass for C variadic functions. This includes both explicit
// (with ...) and implicit (no parameters in signature)
// functions.
if strings.HasPrefix(f.Name(), "_Cgo_") {
// This prefix was created as a result of CGo preprocessing.
info.variadic = true
// Set the importName for our exported function if we have one
if importName != "" {
if info.module == "" {
info.linkName = importName
} else {
// WebAssembly import
info.importName = importName
}
}
}
if c.Nobounds {
info.nobounds = true
}
}
// Check whether this function can be used in //go:wasmimport or
// //go:wasmexport. It will add an error if this is not the case.
//
// The list of allowed types is based on this proposal:
// https://github.com/golang/go/issues/59149
func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) {
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" {
// The runtime is a special case. Allow all kinds of parameters
// (importantly, including pointers).
return
}
if f.Signature.Results().Len() > 1 {
c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
} else if f.Signature.Results().Len() == 1 {
result := f.Signature.Results().At(0)
if !c.isValidWasmType(result.Type(), siteResult) {
c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String()))
}
}
for _, param := range f.Params {
// Check whether the type is allowed.
// Only a very limited number of types can be mapped to WebAssembly.
if !c.isValidWasmType(param.Type(), siteParam) {
c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String()))
}
}
}
// Check whether the type maps directly to a WebAssembly type.
//
// This reflects the relaxed type restrictions proposed here (except for structs.HostLayout):
// https://github.com/golang/go/issues/66984
//
// This previously reflected the additional restrictions documented here:
// https://github.com/golang/go/issues/59149
func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
switch typ := typ.Underlying().(type) {
case *types.Basic:
switch typ.Kind() {
case types.Bool:
return true
case types.Int8, types.Uint8, types.Int16, types.Uint16:
return site == siteIndirect
case types.Int32, types.Uint32, types.Int64, types.Uint64:
return true
case types.Float32, types.Float64:
return true
case types.Uintptr, types.UnsafePointer:
return true
case types.String:
// string flattens to two values, so disallowed as a result
return site == siteParam || site == siteIndirect
}
case *types.Array:
return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect)
case *types.Struct:
if site != siteIndirect {
return false
}
// Structs with no fields do not need structs.HostLayout
if typ.NumFields() == 0 {
return true
}
hasHostLayout := true // default to true before detecting Go version
// (*types.Package).GoVersion added in go1.21
if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok {
if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 {
hasHostLayout = false // package structs added in go1.23
}
}
for i := 0; i < typ.NumFields(); i++ {
ftyp := typ.Field(i).Type()
if types.Unalias(ftyp).String() == "structs.HostLayout" {
hasHostLayout = true
continue
}
if !c.isValidWasmType(ftyp, siteIndirect) {
return false
}
}
return hasHostLayout
case *types.Pointer:
return c.isValidWasmType(typ.Elem(), siteIndirect)
}
return false
}
type wasmSite int
const (
siteParam wasmSite = iota
siteResult
siteIndirect // pointer or field
)
// getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is
// not yet populated when the package has not yet been built.
@@ -614,14 +367,19 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI.
// The uwtable has two possible values: sync (1) or async (2). We use
// sync because we currently don't use async unwind tables.
// For details, see: https://llvm.org/docs/LangRef.html#function-attributes
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
if llvmutil.Major() < 15 {
// Needed for LLVM 14 support.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
} else {
// The uwtable has two possible values: sync (1) or async (2). We
// use sync because we currently don't use async unwind tables.
// For details, see: https://llvm.org/docs/LangRef.html#function-attributes
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
}
}
}
// addStandardAttributes adds all attributes added to defined functions.
// addStandardAttribute adds all attributes added to defined functions.
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
c.addStandardDeclaredAttributes(llvmFn)
c.addStandardDefinedAttributes(llvmFn)
@@ -631,7 +389,7 @@ func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
// linkName is equal to .RelString(nil) on a global and extern is false, but for
// some symbols this is different (due to //go:extern for example).
type globalInfo struct {
linkName string // go:extern, go:linkname
linkName string // go:extern
extern bool // go:extern
align int // go:align
section string // go:section
@@ -675,6 +433,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
// Set alignment from the //go:align comment.
var alignInBits uint32
alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
@@ -685,6 +444,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else {
// Set the alignment only when it is a power of two.
alignInBits = uint32(alignment) ^ uint32(alignment-1)
llvmGlobal.SetAlignment(alignment)
}
@@ -699,7 +459,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
Type: c.getDIType(typ),
LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: uint32(alignment) * 8,
AlignInBits: alignInBits,
})
llvmGlobal.AddMetadata(0, diglobal)
}
@@ -716,14 +476,14 @@ func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
// Check for //go: pragmas, which may change the link name (among others).
doc := c.astComments[info.linkName]
if doc != nil {
info.parsePragmas(doc, c, g)
info.parsePragmas(doc)
}
return info
}
// Parse //go: pragma comments from the source. In particular, it parses the
// //go:extern and //go:linkname pragmas on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext, g *ssa.Global) {
// //go:extern pragma on globals.
func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
for _, comment := range doc.List {
if !strings.HasPrefix(comment.Text, "//go:") {
continue
@@ -744,17 +504,6 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext,
if len(parts) == 2 {
info.section = parts[1]
}
case "//go:linkname":
if len(parts) != 3 || parts[1] != g.Name() {
continue
}
// Only enable go:linkname when the package imports "unsafe".
// This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a
// whole.
if hasUnsafeImport(g.Pkg.Pkg) {
info.linkName = parts[2]
}
}
}
}
+13 -325
View File
@@ -5,7 +5,6 @@ package compiler
import (
"strconv"
"strings"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -13,10 +12,9 @@ import (
// createRawSyscall creates a system call with the provided system call number
// and returns the result as a single integer (the system call result). The
// result is not further interpreted (with the exception of MIPS to use the same
// return value everywhere).
// result is not further interpreted.
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
num := b.getValue(call.Args[0], getPos(call))
num := b.getValue(call.Args[0])
switch {
case b.GOARCH == "amd64" && b.GOOS == "linux":
// Sources:
@@ -35,17 +33,18 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r10}",
"{r8}",
"{r9}",
"{r11}",
"{r12}",
"{r13}",
}[i]
llvmValue := b.getValue(arg, getPos(call))
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// rcx and r11 are clobbered by the syscall, so make sure they are not used
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
// Sources:
// syscall(2) man page
@@ -65,23 +64,14 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{edi}",
"{ebp}",
}[i]
llvmValue := b.getValue(arg, getPos(call))
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
if arch := b.archFamily(); arch != "arm" {
// Some targets pretend to be linux/arm for compatibility but aren't
// actually such a system. Make sure we emit an error instead of
// creating inline assembly that will fail to compile.
// See: https://github.com/tinygo-org/tinygo/issues/4959
return llvm.Value{}, b.makeError(call.Pos(), "system calls are not supported: target emulates a linux/arm system on "+arch)
}
// Implement the EABI system call convention for Linux.
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -99,7 +89,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r5}",
"{r6}",
}[i]
llvmValue := b.getValue(arg, getPos(call))
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -113,7 +103,6 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page.
args := []llvm.Value{}
@@ -130,7 +119,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{x4}",
"{x5}",
}[i]
llvmValue := b.getValue(arg, getPos(call))
llvmValue := b.getValue(arg)
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
@@ -146,98 +135,6 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
// Implement the system call convention for Linux.
// Source: syscall(2) man page and musl:
// https://git.musl-libc.org/cgit/musl/tree/arch/mips/syscall_arch.h
// Also useful:
// https://web.archive.org/web/20220529105937/https://www.linux-mips.org/wiki/Syscall
// The syscall number goes in r2, the result also in r2.
// Register r7 is both an input parameter and an output parameter: if it
// is non-zero, the system call failed and r2 is the error code.
// The code below implements the O32 syscall ABI, not the N32 ABI. It
// could implement both at the same time if needed (like what appears to
// be done in musl) by forcing arg5-arg7 into the right registers but
// letting the compiler decide the registers should result in _slightly_
// faster and smaller code.
args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType}
constraints := "={$2},={$7},0"
syscallParams := call.Args[1:]
if len(syscallParams) > 7 {
// There is one syscall that uses 7 parameters: sync_file_range.
// But only 7, not more. Go however only has Syscall6 and Syscall9.
// Therefore, we can ignore the remaining parameters.
syscallParams = syscallParams[:7]
}
for i, arg := range syscallParams {
constraints += "," + [...]string{
"{$4}", // arg1
"{$5}", // arg2
"{$6}", // arg3
"1", // arg4, error return
"r", // arg5 on the stack
"r", // arg6 on the stack
"r", // arg7 on the stack
}[i]
llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type())
}
// Create assembly code.
// Parameters beyond the first 4 are passed on the stack instead of in
// registers in the O32 syscall ABI.
// We need ".set noat" because LLVM might pick register $1 ($at) as the
// register for a parameter and apparently this is not allowed on MIPS
// unless you use this specific pragma.
asm := "syscall"
switch len(syscallParams) {
case 5:
asm = "" +
".set noat\n" +
"subu $$sp, $$sp, 32\n" +
"sw $7, 16($$sp)\n" + // arg5
"syscall\n" +
"addu $$sp, $$sp, 32\n" +
".set at\n"
case 6:
asm = "" +
".set noat\n" +
"subu $$sp, $$sp, 32\n" +
"sw $7, 16($$sp)\n" + // arg5
"sw $8, 20($$sp)\n" + // arg6
"syscall\n" +
"addu $$sp, $$sp, 32\n" +
".set at\n"
case 7:
asm = "" +
".set noat\n" +
"subu $$sp, $$sp, 32\n" +
"sw $7, 16($$sp)\n" + // arg5
"sw $8, 20($$sp)\n" + // arg6
"sw $9, 24($$sp)\n" + // arg7
"syscall\n" +
"addu $$sp, $$sp, 32\n" +
".set at\n"
}
constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
fnType := llvm.FunctionType(returnType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
call := b.CreateCall(fnType, target, args, "")
resultCode := b.CreateExtractValue(call, 0, "") // r2
errorFlag := b.CreateExtractValue(call, 1, "") // r7
// Pseudocode to return the result with the same convention as other
// archs:
// return (errorFlag != 0) ? -resultCode : resultCode;
// At least on QEMU with the O32 ABI, the error code is always positive.
zero := llvm.ConstInt(b.uintptrType, 0, false)
isError := b.CreateICmp(llvm.IntNE, errorFlag, zero, "")
negativeResult := b.CreateSub(zero, resultCode, "")
result := b.CreateSelect(isError, negativeResult, resultCode, "")
return result, nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
@@ -276,36 +173,28 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// The signature looks like this:
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
isI386 := strings.HasPrefix(b.Triple, "i386-")
// Prepare input values.
var paramTypes []llvm.Type
var params []llvm.Value
for _, val := range call.Args[2:] {
param := b.getValue(val, getPos(call))
param := b.getValue(val)
params = append(params, param)
paramTypes = append(paramTypes, param.Type())
}
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
fn := b.getValue(call.Args[0], getPos(call))
fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "")
fn := b.getValue(call.Args[0])
fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
// Prepare some functions that will be called later.
setLastError := b.mod.NamedFunction("SetLastError")
if setLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
if isI386 {
setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
}
}
getLastError := b.mod.NamedFunction("GetLastError")
if getLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
if isI386 {
getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
}
}
// Now do the actual call. Pseudocode:
@@ -316,24 +205,9 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// Note that SetLastError/GetLastError could be replaced with direct
// access to the thread control block, which is probably smaller and
// faster. The Go runtime does this in assembly.
// On windows/386, we also need to save/restore the stack pointer. I'm
// not entirely sure why this is needed, but without it these calls
// change the stack pointer leading to a crash soon after.
setLastErrorCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
var sp llvm.Value
if isI386 {
setLastErrorCall.SetInstructionCallConv(llvm.X86StdcallCallConv)
sp = b.readStackPointer()
}
b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
syscallResult := b.CreateCall(llvmType, fnPtr, params, "")
if isI386 {
syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
b.writeStackPointer(sp)
}
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
if isI386 {
errResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
}
if b.uintptrType != b.ctx.Int32Type() {
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
}
@@ -343,136 +217,11 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil
default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
}
}
// createSyscalln emits instructions for the syscall.syscalln function on
// Windows. This handles the variadic calling convention used in Go 1.26+:
//
// func syscalln(fn, n uintptr, args ...uintptr) (r1, r2 uintptr, err Errno)
//
// The function generates a switch on n to dispatch to the correct fixed-argument
// function pointer call with SetLastError(0)/GetLastError() wrapping.
func (b *builder) createSyscalln(call *ssa.CallCommon) (llvm.Value, error) {
const maxArgs = 18 // Windows syscalls support up to 18 args
isI386 := strings.HasPrefix(b.Triple, "i386-")
// Get the function pointer (call.Args[0]) and n (call.Args[1]).
fn := b.getValue(call.Args[0], getPos(call))
fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "")
n := b.getValue(call.Args[1], getPos(call))
// Get the variadic args slice (call.Args[2]).
// In SSA, the variadic slice is the third argument.
var argsPtr llvm.Value
if len(call.Args) > 2 {
argsSlice := b.getValue(call.Args[2], getPos(call))
argsPtr = b.CreateExtractValue(argsSlice, 0, "args.data")
} else {
argsPtr = llvm.ConstNull(b.dataPtrType)
}
// Prepare SetLastError and GetLastError.
setLastError := b.mod.NamedFunction("SetLastError")
if setLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
if isI386 {
setLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
}
}
getLastError := b.mod.NamedFunction("GetLastError")
if getLastError.IsNil() {
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
if isI386 {
getLastError.SetFunctionCallConv(llvm.X86StdcallCallConv)
}
}
retType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false)
// Create the merge block where all cases converge.
mergeBB := b.insertBasicBlock("syscalln.merge")
// Create the default (panic) block.
panicBB := b.insertBasicBlock("syscalln.panic")
// Create the switch on n.
sw := b.CreateSwitch(n, panicBB, maxArgs+1)
// We'll collect blocks and values for the PHI node.
var incomingVals []llvm.Value
var incomingBlocks []llvm.BasicBlock
// Generate a case for each arg count 0..maxArgs.
for i := 0; i <= maxArgs; i++ {
caseBB := b.insertBasicBlock("syscalln.case" + strconv.Itoa(i))
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), caseBB)
b.SetInsertPointAtEnd(caseBB)
// Load args[0] through args[i-1] from the slice data pointer.
var params []llvm.Value
var paramTypes []llvm.Type
for j := 0; j < i; j++ {
gep := b.CreateInBoundsGEP(b.uintptrType, argsPtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), uint64(j), false),
}, "")
arg := b.CreateLoad(b.uintptrType, gep, "")
params = append(params, arg)
paramTypes = append(paramTypes, b.uintptrType)
}
// SetLastError(0)
setCall := b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
var sp llvm.Value
if isI386 {
setCall.SetInstructionCallConv(llvm.X86StdcallCallConv)
sp = b.readStackPointer()
}
// Call fn(args...)
fnType := llvm.FunctionType(b.uintptrType, paramTypes, false)
syscallResult := b.CreateCall(fnType, fnPtr, params, "")
if isI386 {
syscallResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
b.writeStackPointer(sp)
}
// err = GetLastError()
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err")
if isI386 {
errResult.SetInstructionCallConv(llvm.X86StdcallCallConv)
}
if b.uintptrType != b.ctx.Int32Type() {
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
}
// Build {r1, 0, err}
result := llvm.ConstNull(retType)
result = b.CreateInsertValue(result, syscallResult, 0, "")
result = b.CreateInsertValue(result, errResult, 2, "")
incomingVals = append(incomingVals, result)
incomingBlocks = append(incomingBlocks, b.Builder.GetInsertBlock())
b.CreateBr(mergeBB)
}
// Panic block for n > maxArgs.
b.SetInsertPointAtEnd(panicBB)
b.CreateUnreachable()
// Merge block: PHI node to select the result.
b.SetInsertPointAtEnd(mergeBB)
phi := b.CreatePHI(retType, "syscalln.result")
phi.AddIncoming(incomingVals, incomingBlocks)
return phi, nil
}
// createRawSyscallNoError emits instructions for the Linux-specific
// syscall.rawSyscallNoError function.
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
@@ -485,64 +234,3 @@ func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, err
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
return retval, nil
}
// Lower a call to internal/abi.FuncPCABI0 on MacOS.
// This function is called like this:
//
// syscall(abi.FuncPCABI0(libc_mkdir_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)
//
// So we'll want to return a function pointer (as uintptr) that points to the
// libc function. Specifically, we _don't_ want to point to the trampoline
// function (which is implemented in Go assembly which we can't read), but
// rather to the actually intended function. For this we're going to assume that
// all the functions follow a specific pattern: libc_<functionname>_trampoline.
//
// The return value is the function pointer as an uintptr, or a nil value if
// this isn't possible (and a regular call should be made as fallback).
func (b *builder) createDarwinFuncPCABI0Call(instr *ssa.CallCommon) llvm.Value {
if b.GOOS != "darwin" {
// This has only been tested on MacOS (and only seems to be used there).
return llvm.Value{}
}
// Check that it uses a function call like syscall.libc_*_trampoline
itf := instr.Args[0].(*ssa.MakeInterface)
calledFn := itf.X.(*ssa.Function)
if pkgName := calledFn.Pkg.Pkg.Path(); pkgName != "syscall" && pkgName != "internal/syscall/unix" {
return llvm.Value{}
}
if !strings.HasPrefix(calledFn.Name(), "libc_") || !strings.HasSuffix(calledFn.Name(), "_trampoline") {
return llvm.Value{}
}
// Extract the libc function name.
name := strings.TrimPrefix(strings.TrimSuffix(calledFn.Name(), "_trampoline"), "libc_")
if name == "open" {
// Special case: open() is a variadic function and can't be called like
// a regular function. Therefore, we need to use a wrapper implemented
// in C.
name = "syscall_libc_open"
}
if b.GOARCH == "amd64" {
if name == "fdopendir" || name == "readdir_r" {
// Hack to support amd64, which needs the $INODE64 suffix.
// This is also done in upstream Go:
// https://github.com/golang/go/commit/096ab3c21b88ccc7d411379d09fe6274e3159467
name += "$INODE64"
}
}
// Obtain the C function.
// Use a simple function (no parameters or return value) because all we need
// is the address of the function.
llvmFn := b.mod.NamedFunction(name)
if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(b.ctx.VoidType(), nil, false)
llvmFn = llvm.AddFunction(b.mod, name, llvmFnType)
}
// Cast the function pointer to a uintptr (because that's what
// abi.FuncPCABI0 returns).
return b.CreatePtrToInt(llvmFn, b.uintptrType, "")
}

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