Compare commits

..

2 Commits

Author SHA1 Message Date
Ayke van Laethem 28a083633c cgo: allow --export= in LDFLAGS
This allows people to export some functions, such as malloc. Example:

    // #cgo LDFLAGS: --export=malloc
    import "C"

This exports the function malloc.

Note that this is somewhat unsafe right now, but it is used regardless.
By using this workaround, people have some time to transition away from
using malloc/free directly or until malloc is made safe to be used in
this way.
2022-09-15 11:36:29 +02:00
Ayke van Laethem 7e7814a087 wasm: do not export malloc, calloc, realloc, free
These functions were exported by accident, because the compiler had no
way of saying these functions shouldn't be exported.

This can be a big code size reduction for small programs. Before:

    $ tinygo build -o test.wasm -target=wasi -no-debug -scheduler=none ./testdata/alias.go && ls -l test.wasm
    -rwxrwxr-x 1 ayke ayke 2947  8 sep 13:47 test.wasm

After:

    $ tinygo build -o test.wasm -target=wasi -no-debug -scheduler=none ./testdata/alias.go && ls -l test.wasm
    -rwxrwxr-x 1 ayke ayke 968  8 sep 13:47 test.wasm

This is all because the GC isn't needed anymore.

This commit also adds support for using //go:wasm-module to set the
module name of an exported function (the default remains env).
2022-09-15 11:36:26 +02:00
1148 changed files with 14643 additions and 54450 deletions
+38 -26
View File
@@ -6,16 +6,28 @@ commands:
- run: - run:
name: "Pull submodules" name: "Pull submodules"
command: git submodule update --init command: git submodule update --init
install-xtensa-toolchain:
parameters:
variant:
type: string
steps:
- run:
name: "Install Xtensa toolchain"
command: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
llvm-source-linux: llvm-source-linux:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- llvm-source-18-v1 - llvm-source-14-v3
- run: - run:
name: "Fetch LLVM source" name: "Fetch LLVM source"
command: make llvm-source command: make llvm-source
- save_cache: - save_cache:
key: llvm-source-18-v1 key: llvm-source-14-v3
paths: paths:
- llvm-project/clang/lib/Headers - llvm-project/clang/lib/Headers
- llvm-project/clang/include - llvm-project/clang/include
@@ -33,13 +45,13 @@ commands:
steps: steps:
- restore_cache: - restore_cache:
keys: keys:
- binaryen-linux-v3 - binaryen-linux-v2
- run: - run:
name: "Build Binaryen" name: "Build Binaryen"
command: | command: |
make binaryen make binaryen
- save_cache: - save_cache:
key: binaryen-linux-v3 key: binaryen-linux-v2
paths: paths:
- build/wasm-opt - build/wasm-opt
test-linux: test-linux:
@@ -55,7 +67,7 @@ commands:
- run: - run:
name: "Install apt dependencies" name: "Install apt dependencies"
command: | 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 - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update apt-get update
apt-get install --no-install-recommends -y \ apt-get install --no-install-recommends -y \
@@ -63,22 +75,24 @@ commands:
clang-<<parameters.llvm>> \ clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \ libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \ lld-<<parameters.llvm>> \
gcc-avr \
avr-libc \
cmake \ cmake \
ninja-build ninja-build
- hack-ninja-jobs - hack-ninja-jobs
- build-binaryen-linux - build-binaryen-linux
- restore_cache: - restore_cache:
keys: keys:
- go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }} - go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v4-{{ checksum "go.mod" }} - go-cache-v3-{{ checksum "go.mod" }}
- llvm-source-linux - llvm-source-linux
- run: go install -tags=llvm<<parameters.llvm>> . - run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache: - restore_cache:
keys: keys:
- wasi-libc-sysroot-systemclang-v7 - wasi-libc-sysroot-systemclang-v6
- run: make wasi-libc - run: make wasi-libc
- save_cache: - save_cache:
key: wasi-libc-sysroot-systemclang-v7 key: wasi-libc-sysroot-systemclang-v6
paths: paths:
- lib/wasi-libc/sysroot - lib/wasi-libc/sysroot
- when: - when:
@@ -88,38 +102,36 @@ commands:
# Do this before gen-device so that it doesn't check the # Do this before gen-device so that it doesn't check the
# formatting of generated files. # formatting of generated files.
name: Check Go code formatting name: Check Go code formatting
command: make fmt-check lint command: make fmt-check
- run: make gen-device -j4 - run: make gen-device -j4
- run: make smoketest XTENSA=0 - run: make smoketest XTENSA=0
- save_cache: - 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: paths:
- ~/.cache/go-build - ~/.cache/go-build
- /go/pkg/mod - /go/pkg/mod
jobs: jobs:
test-llvm15-go119: test-llvm14-go118:
docker: docker:
- image: golang:1.19-bullseye - image: golang:1.18-buster
steps: steps:
- test-linux: - test-linux:
llvm: "15" llvm: "14"
# "make lint" fails before go 1.21 because internal/tools/go.mod specifies packages that require go 1.21 test-llvm14-go119:
docker:
- image: golang:1.19beta1-buster
steps:
- test-linux:
llvm: "14"
fmt-check: false fmt-check: false
resource_class: large
test-llvm18-go123:
docker:
- image: golang:1.23-bullseye
steps:
- test-linux:
llvm: "18"
resource_class: large
workflows: workflows:
test-all: test-all:
jobs: jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at # This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass. # least the smoke tests still pass.
- test-llvm15-go119 - test-llvm14-go118
# This tests LLVM 18 support when linking against system libraries. # This tests a beta version of Go. It should be removed once regular
- test-llvm18-go123 # release builds are built using this version.
- test-llvm14-go119
-2
View File
@@ -1,5 +1,3 @@
build/ build/
llvm-*/ llvm-*/
.github
.circleci
+31 -68
View File
@@ -14,36 +14,33 @@ concurrency:
jobs: jobs:
build-macos: build-macos:
name: build-macos name: build-macos
strategy: runs-on: macos-11
matrix:
# macos-12: amd64 (oldest supported version as of 05-02-2024)
# macos-14: arm64 (oldest arm64 version)
os: [macos-12, macos-14]
include:
- os: macos-12
goarch: amd64
- os: macos-14
goarch: arm64
runs-on: ${{ matrix.os }}
steps: steps:
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
run: | run: |
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
- name: Install Xtensa toolchain
shell: bash
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18.1'
cache: true cache: true
- name: Restore LLVM source cache - name: Cache LLVM source
uses: actions/cache/restore@v4 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-18-${{ matrix.os }}-v2 key: llvm-source-14-macos-v1
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -53,22 +50,11 @@ jobs:
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source run: make llvm-source
- name: Save LLVM source cache - name: Cache LLVM build
uses: actions/cache/save@v4 uses: actions/cache@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@v4
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-18-${{ matrix.os }}-v3 key: llvm-build-14-macos-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -82,33 +68,25 @@ jobs:
# build! # build!
make llvm-build make llvm-build
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache wasi-libc sysroot - name: Cache wasi-libc sysroot
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-wasi-libc id: cache-wasi-libc
with: with:
key: wasi-libc-sysroot-${{ matrix.os }}-v1 key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- name: Build wasi-libc - name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc run: make wasi-libc
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball - name: Build TinyGo release tarball
run: make release -j3 run: make release -j3
- name: Test stdlib packages - name: Test stdlib packages
run: make tinygo-test run: make tinygo-test
- name: Make release artifact - name: Make release artifact
shell: bash shell: bash
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz run: cp -p build/release.tar.gz build/tinygo.darwin-amd64.tar.gz
- name: Publish release artifact - name: Publish release artifact
# Note: this release artifact is double-zipped, see: # Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39 # https://github.com/actions/upload-artifact/issues/39
@@ -116,44 +94,29 @@ jobs:
# - have a double-zipped artifact when downloaded from the UI # - have a double-zipped artifact when downloaded from the UI
# - have a very slow artifact upload # - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast. # We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: darwin-${{ matrix.goarch }}-double-zipped name: release-double-zipped
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz path: build/tinygo.darwin-amd64.tar.gz
- name: Smoke tests - name: Smoke tests
shell: bash shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo run: make smoketest TINYGO=$(PWD)/build/tinygo AVR=0
test-macos-homebrew: test-macos-homebrew:
name: homebrew-install name: homebrew-install
runs-on: macos-latest runs-on: macos-latest
strategy:
matrix:
version: [16, 17, 18]
steps: steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@master
- 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 - name: Install LLVM
shell: bash
run: | run: |
brew install llvm@${{ matrix.version }} HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@14
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18'
cache: true cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }}) - name: Build TinyGo
run: go install -tags=llvm${{ matrix.version }}
- name: Check binary
run: tinygo version
- name: Build TinyGo (default LLVM)
if: matrix.version == 18
run: go install run: go install
- name: Check binary - name: Check binary
if: matrix.version == 18
run: tinygo version run: tinygo version
+22 -43
View File
@@ -19,46 +19,35 @@ jobs:
packages: write packages: write
contents: read contents: read
steps: 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 - name: Check out the repo
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: recursive submodules: recursive
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v1
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v3
with: with:
images: | images: |
tinygo/tinygo-dev tinygo/tinygo-dev
ghcr.io/${{ github.repository_owner }}/tinygo-dev ghcr.io/${{ github.repository }}/tinygo-dev
tags: | tags: |
type=sha,format=long type=sha,format=long
type=raw,value=latest type=raw,value=latest
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry - name: Log in to Github Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push - name: Build and push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v2
with: with:
context: . context: .
push: true push: true
@@ -80,31 +69,21 @@ jobs:
-H "Accept: application/vnd.github.v3+json" \ -H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \ https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}' -d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on Github Actions - name: Trigger TinyFS repo build on CircleCI
run: | run: |
curl -X POST \ curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfs/pipeline' \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \ --header 'Content-Type: application/json' \
-H "Accept: application/vnd.github.v3+json" \ -d '{"branch": "dev"}' \
https://api.github.com/repos/tinygo-org/tinyfs/actions/workflows/build.yml/dispatches \ -u "${{ secrets.CIRCLECI_API_TOKEN }}"
-d '{"ref": "dev"}' - name: Trigger TinyFont repo build on CircleCI
- name: Trigger TinyFont repo build on Github Actions
run: | run: |
curl -X POST \ curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfont/pipeline' \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \ --header 'Content-Type: application/json' \
-H "Accept: application/vnd.github.v3+json" \ -d '{"branch": "dev"}' \
https://api.github.com/repos/tinygo-org/tinyfont/actions/workflows/build.yml/dispatches \ -u "${{ secrets.CIRCLECI_API_TOKEN }}"
-d '{"ref": "dev"}' - name: Trigger TinyDraw repo build on CircleCI
- name: Trigger TinyDraw repo build on Github Actions
run: | run: |
curl -X POST \ curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinydraw/pipeline' \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \ --header 'Content-Type: application/json' \
-H "Accept: application/vnd.github.v3+json" \ -d '{"branch": "dev"}' \
https://api.github.com/repos/tinygo-org/tinydraw/actions/workflows/build.yml/dispatches \ -u "${{ secrets.CIRCLECI_API_TOKEN }}"
-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"}'
+183 -139
View File
@@ -18,32 +18,32 @@ jobs:
# statically linked binary. # statically linked binary.
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: golang:1.23-alpine image: golang:1.18-alpine
steps: steps:
- name: Install apk dependencies - name: Install apk dependencies
# tar: needed for actions/cache@v4 # tar: needed for actions/cache@v3
# git+openssh: needed for checkout (I think?) # git+openssh: needed for checkout (I think?)
# ruby: needed to install fpm # ruby: needed to install fpm
run: apk add tar git openssh make g++ ruby-dev run: apk add tar git openssh make g++ ruby
- name: Work around CVE-2022-24765 - name: Work around CVE-2022-24765
# We're not on a multi-user machine, so this is safe. # We're not on a multi-user machine, so this is safe.
run: git config --global --add safe.directory "$GITHUB_WORKSPACE" run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Cache Go - name: Cache Go
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }} key: go-cache-linux-alpine-v1-${{ hashFiles('go.mod') }}
path: | path: |
~/.cache/go-build ~/.cache/go-build
~/go/pkg/mod ~/go/pkg/mod
- name: Restore LLVM source cache - name: Cache LLVM source
uses: actions/cache/restore@v4 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-18-linux-alpine-v1 key: llvm-source-14-linux-alpine-v1
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -53,22 +53,11 @@ jobs:
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source run: make llvm-source
- name: Save LLVM source cache - name: Cache LLVM build
uses: actions/cache/save@v4 uses: actions/cache@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@v4
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-18-linux-alpine-v2 key: llvm-build-14-linux-alpine-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -82,14 +71,8 @@ jobs:
make llvm-build make llvm-build
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
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 - name: Cache Binaryen
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-alpine-v1 key: binaryen-linux-alpine-v1
@@ -100,10 +83,10 @@ jobs:
apk add cmake samurai python3 apk add cmake samurai python3
make binaryen STATIC=1 make binaryen STATIC=1
- name: Cache wasi-libc - name: Cache wasi-libc
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-wasi-libc id: cache-wasi-libc
with: with:
key: wasi-libc-sysroot-linux-alpine-v2 key: wasi-libc-sysroot-linux-alpine-v1
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- name: Build wasi-libc - name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
@@ -113,17 +96,13 @@ jobs:
gem install --version 4.0.7 public_suffix gem install --version 4.0.7 public_suffix
gem install --version 2.7.6 dotenv gem install --version 2.7.6 dotenv
gem install --no-document fpm gem install --no-document fpm
- name: Run linter
run: make lint
- name: Run spellcheck
run: make spell
- name: Build TinyGo release - name: Build TinyGo release
run: | run: |
make release deb -j3 STATIC=1 make release deb -j3 STATIC=1
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_amd64.deb cp -p build/release.deb /tmp/tinygo_amd64.deb
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: linux-amd64-double-zipped name: linux-amd64-double-zipped
path: | path: |
@@ -135,22 +114,18 @@ jobs:
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with:
submodules: true
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18.1'
cache: true cache: true
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 run: |
with: curl https://wasmtime.dev/install.sh -sSf | bash
version: "19.0.1" echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Install wasm-tools
uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Download release artifact - name: Download release artifact
uses: actions/download-artifact@v4 uses: actions/download-artifact@v2
with: with:
name: linux-amd64-double-zipped name: linux-amd64-double-zipped
- name: Extract release tarball - name: Extract release tarball
@@ -158,8 +133,18 @@ jobs:
mkdir -p ~/lib mkdir -p ~/lib
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
- run: make tinygo-test-wasip1-fast - name: Install apt dependencies
- run: make tinygo-test-wasip2-fast run: |
sudo apt-get install --no-install-recommends \
gcc-avr \
avr-libc
- name: "Install Xtensa toolchain"
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
- run: make tinygo-test-wasi-fast
- run: make smoketest - run: make smoketest
assert-test-linux: assert-test-linux:
# Run all tests that can run on Linux, with LLVM assertions enabled to catch # Run all tests that can run on Linux, with LLVM assertions enabled to catch
@@ -167,7 +152,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Install apt dependencies - name: Install apt dependencies
@@ -179,28 +164,28 @@ jobs:
qemu-system-arm \ qemu-system-arm \
qemu-system-riscv32 \ qemu-system-riscv32 \
qemu-user \ qemu-user \
gcc-avr \
avr-libc \
simavr \ simavr \
ninja-build ninja-build
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18.1'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v4 uses: actions/setup-node@v2
with: with:
node-version: '18' node-version: '14'
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 run: |
with: curl https://wasmtime.dev/install.sh -sSf | bash
version: "19.0.1" echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
- name: Setup `wasm-tools` - name: Cache LLVM source
uses: bytecodealliance/actions/wasm-tools/setup@v1 uses: actions/cache@v3
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-18-linux-asserts-v1 key: llvm-source-14-linux-asserts-v2
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -210,22 +195,11 @@ jobs:
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source run: make llvm-source
- name: Save LLVM source cache - name: Cache LLVM build
uses: actions/cache/save@v4 uses: actions/cache@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@v4
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-18-linux-asserts-v2 key: llvm-build-14-linux-asserts-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -237,14 +211,8 @@ jobs:
make llvm-build ASSERT=1 make llvm-build ASSERT=1
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
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 - name: Cache Binaryen
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-asserts-v1 key: binaryen-linux-asserts-v1
@@ -253,10 +221,10 @@ jobs:
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: make binaryen run: make binaryen
- name: Cache wasi-libc - name: Cache wasi-libc
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-wasi-libc id: cache-wasi-libc
with: with:
key: wasi-libc-sysroot-linux-asserts-v6 key: wasi-libc-sysroot-linux-asserts-v5
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- name: Build wasi-libc - name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
@@ -270,10 +238,16 @@ jobs:
echo "$(pwd)/build" >> $GITHUB_PATH echo "$(pwd)/build" >> $GITHUB_PATH
- name: Test stdlib packages - name: Test stdlib packages
run: make tinygo-test run: make tinygo-test
- name: Install Xtensa toolchain
run: |
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
- run: make smoketest - run: make smoketest
- run: make wasmtest - run: make wasmtest
- run: make tinygo-baremetal - run: make tinygo-baremetal
build-linux-cross: build-linux-arm:
# Build ARM Linux binaries, ready for release. # Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against # 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 # an older glibc version and therefore are compatible with a wide range of
@@ -282,38 +256,28 @@ jobs:
# in that process to avoid doing lots of duplicate work and to avoid # in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as # complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball. # part of the release tarball.
strategy: runs-on: ubuntu-18.04
matrix:
goarch: [ arm, arm64 ]
include:
- goarch: arm64
toolchain: aarch64-linux-gnu
libc: arm64
- goarch: arm
toolchain: arm-linux-gnueabihf
libc: armhf
runs-on: ubuntu-20.04
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Install apt dependencies - name: Install apt dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
qemu-user \ qemu-user \
g++-${{ matrix.toolchain }} \ g++-arm-linux-gnueabihf \
libc6-dev-${{ matrix.libc }}-cross libc6-dev-armhf-cross
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18.1'
cache: true cache: true
- name: Restore LLVM source cache - name: Cache LLVM source
uses: actions/cache/restore@v4 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-18-linux-v1 key: llvm-source-14-linux-v2
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -323,22 +287,11 @@ jobs:
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source run: make llvm-source
- name: Save LLVM source cache - name: Cache LLVM build
uses: actions/cache/save@v4 uses: actions/cache@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@v4
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-18-linux-${{ matrix.goarch }}-v2 key: llvm-build-14-linux-arm-v1
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -349,27 +302,21 @@ jobs:
# Install build dependencies. # Install build dependencies.
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
# build! # build!
make llvm-build CROSS=${{ matrix.toolchain }} make llvm-build CROSS=arm-linux-gnueabihf
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save LLVM build cache
uses: actions/cache/save@v4
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 - name: Cache Binaryen
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-${{ matrix.goarch }}-v4 key: binaryen-linux-arm-v1
path: build/wasm-opt path: build/wasm-opt
- name: Build Binaryen - name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true' if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: | run: |
sudo apt-get install --no-install-recommends ninja-build sudo apt-get install --no-install-recommends ninja-build
git submodule update --init lib/binaryen git submodule update --init lib/binaryen
make CROSS=${{ matrix.toolchain }} binaryen make CROSS=arm-linux-gnueabihf binaryen
- name: Install fpm - name: Install fpm
run: | run: |
sudo gem install --version 4.0.7 public_suffix sudo gem install --version 4.0.7 public_suffix
@@ -377,9 +324,9 @@ jobs:
sudo gem install --no-document fpm sudo gem install --no-document fpm
- name: Build TinyGo binary - name: Build TinyGo binary
run: | run: |
make CROSS=${{ matrix.toolchain }} make CROSS=arm-linux-gnueabihf
- name: Download amd64 release - name: Download amd64 release
uses: actions/download-artifact@v4 uses: actions/download-artifact@v2
with: with:
name: linux-amd64-double-zipped name: linux-amd64-double-zipped
- name: Extract amd64 release - name: Extract amd64 release
@@ -390,15 +337,112 @@ jobs:
run: | run: |
cp -p build/tinygo build/release/tinygo/bin cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin cp -p build/wasm-opt build/release/tinygo/bin
- name: Create ${{ matrix.goarch }} release - name: Create arm release
run: | run: |
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }} make release deb RELEASEONLY=1 DEB_ARCH=armhf
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb cp -p build/release.deb /tmp/tinygo_armhf.deb
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: linux-${{ matrix.goarch }}-double-zipped name: linux-arm-double-zipped
path: | path: |
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz /tmp/tinygo.linux-arm.tar.gz
/tmp/tinygo_${{ matrix.libc }}.deb /tmp/tinygo_armhf.deb
build-linux-arm64:
# Build ARM64 Linux binaries, ready for release.
# It is set to "needs: build-linux" because it modifies the release created
# in that process to avoid doing lots of duplicate work and to avoid
# complications around precompiled libraries such as compiler-rt shipped as
# part of the release tarball.
runs-on: ubuntu-18.04
needs: build-linux
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install apt dependencies
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends \
qemu-user \
g++-aarch64-linux-gnu \
libc6-dev-arm64-cross \
ninja-build
- name: Install Go
uses: actions/setup-go@v3
with:
go-version: '1.18.1'
cache: true
- name: Cache LLVM source
uses: actions/cache@v3
id: cache-llvm-source
with:
key: llvm-source-14-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
llvm-project/compiler-rt
llvm-project/lld/include
llvm-project/llvm/include
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache LLVM build
uses: actions/cache@v3
id: cache-llvm-build
with:
key: llvm-build-14-linux-arm64-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
run: |
# fetch LLVM source
rm -rf llvm-project
make llvm-source
# build!
make llvm-build CROSS=aarch64-linux-gnu
# Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Cache Binaryen
uses: actions/cache@v3
id: cache-binaryen
with:
key: binaryen-linux-arm64-v1
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
run: |
git submodule update --init lib/binaryen
make CROSS=aarch64-linux-gnu binaryen
- name: Install fpm
run: |
sudo gem install --version 4.0.7 public_suffix
sudo gem install --version 2.7.6 dotenv
sudo gem install --no-document fpm
- name: Build TinyGo binary
run: |
make CROSS=aarch64-linux-gnu
- name: Download amd64 release
uses: actions/download-artifact@v2
with:
name: linux-amd64-double-zipped
- name: Extract amd64 release
run: |
mkdir -p build/release
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release
run: |
cp -p build/tinygo build/release/tinygo/bin
cp -p build/wasm-opt build/release/tinygo/bin
- name: Create arm64 release
run: |
make release deb RELEASEONLY=1 DEB_ARCH=arm64
cp -p build/release.tar.gz /tmp/tinygo.linux-arm64.tar.gz
cp -p build/release.deb /tmp/tinygo_arm64.deb
- name: Publish release artifact
uses: actions/upload-artifact@v2
with:
name: linux-arm64-double-zipped
path: |
/tmp/tinygo.linux-arm64.tar.gz
/tmp/tinygo_arm64.deb
-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@v4
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
tinygo/llvm-18
ghcr.io/${{ github.repository_owner }}/llvm-18
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@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v5
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
-43
View File
@@ -1,43 +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: Checkout
uses: actions/checkout@v4
- name: Pull musl
run: |
git submodule update --init lib/musl
- name: Restore LLVM source cache
uses: actions/cache/restore@v4
id: cache-llvm-source
with:
key: llvm-source-18-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@v4
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@v22
- 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-18 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-18-dev \
clang-18 \
libclang-18-dev \
lld-18
-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@v4
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@v4
id: cache-llvm-source
with:
key: llvm-source-18-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@v4
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
+19 -139
View File
@@ -15,12 +15,6 @@ jobs:
build-windows: build-windows:
runs-on: windows-2022 runs-on: windows-2022
steps: steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2 - uses: brechtm/setup-scoop@v2
with: with:
scoop_update: 'false' scoop_update: 'false'
@@ -29,19 +23,19 @@ jobs:
run: | run: |
scoop install ninja binaryen scoop install ninja binaryen
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Install Go - name: Install Go
uses: actions/setup-go@v5 uses: actions/setup-go@v3
with: with:
go-version: '1.23' go-version: '1.18.1'
cache: true cache: true
- name: Restore cached LLVM source - name: Cache LLVM source
uses: actions/cache/restore@v4 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-18-windows-v1 key: llvm-source-14-windows-v2
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -51,22 +45,11 @@ jobs:
- name: Download LLVM source - name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true' if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source run: make llvm-source
- name: Save cached LLVM source - name: Cache LLVM build
uses: actions/cache/save@v4 uses: actions/cache@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 cached LLVM build
uses: actions/cache/restore@v4
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-18-windows-v2 key: llvm-build-14-windows-v2
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -79,29 +62,21 @@ jobs:
make llvm-build CCACHE=OFF make llvm-build CCACHE=OFF
# Remove unnecessary object files (to reduce cache size). # Remove unnecessary object files (to reduce cache size).
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \; find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
- name: Save cached LLVM build
uses: actions/cache/save@v4
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
path: llvm-build
- name: Cache wasi-libc sysroot - name: Cache wasi-libc sysroot
uses: actions/cache@v4 uses: actions/cache@v3
id: cache-wasi-libc id: cache-wasi-libc
with: with:
key: wasi-libc-sysroot-v5 key: wasi-libc-sysroot-v4
path: lib/wasi-libc/sysroot path: lib/wasi-libc/sysroot
- name: Build wasi-libc - name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true' if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc run: make wasi-libc
- name: Install wasmtime - name: Install wasmtime
run: | run: |
scoop install wasmtime@14.0.4 scoop install wasmtime
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-short" run: make test GOTESTFLAGS="-v -short"
- name: Build TinyGo release tarball - name: Build TinyGo release tarball
shell: bash shell: bash
run: make build/release -j4 run: make build/release -j4
@@ -116,109 +91,14 @@ jobs:
# - have a dobule-zipped artifact when downloaded from the UI # - have a dobule-zipped artifact when downloaded from the UI
# - have a very slow artifact upload # - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast. # We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v2
with: with:
name: windows-amd64-double-zipped name: release-double-zipped
path: build/release/release.zip 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
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop install binaryen
- name: Checkout
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Smoke tests - name: Smoke tests
shell: bash shell: bash
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo run: make smoketest TINYGO=$(PWD)/build/tinygo AVR=0 XTENSA=0
stdlib-test-windows:
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- name: Checkout
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Test stdlib packages - name: Test stdlib packages
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo run: make tinygo-test
- name: Test stdlib packages on wasi
stdlib-wasi-test-windows: run: make tinygo-test-wasi-fast
runs-on: windows-2022
needs: build-windows
steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies
shell: bash
run: |
scoop install binaryen && scoop install wasmtime@14.0.4
- name: Checkout
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v4
with:
name: windows-amd64-double-zipped
path: build/
- name: Unzip TinyGo build
shell: bash
working-directory: build
run: 7z x release.zip -r
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+1 -6
View File
@@ -1,8 +1,3 @@
.DS_Store
.vscode
go.work
go.work.sum
docs/_build docs/_build
src/device/avr/*.go src/device/avr/*.go
src/device/avr/*.ld src/device/avr/*.ld
@@ -22,7 +17,7 @@ src/device/kendryte/*.go
src/device/kendryte/*.s src/device/kendryte/*.s
src/device/rp/*.go src/device/rp/*.go
src/device/rp/*.s src/device/rp/*.s
./vendor vendor
llvm-build llvm-build
llvm-project llvm-project
build/* build/*
+2 -13
View File
@@ -9,11 +9,10 @@
url = https://github.com/avr-rust/avr-mcu.git url = https://github.com/avr-rust/avr-mcu.git
[submodule "lib/cmsis-svd"] [submodule "lib/cmsis-svd"]
path = lib/cmsis-svd path = lib/cmsis-svd
url = https://github.com/cmsis-svd/cmsis-svd-data.git url = https://github.com/tinygo-org/cmsis-svd
branch = main
[submodule "lib/wasi-libc"] [submodule "lib/wasi-libc"]
path = lib/wasi-libc path = lib/wasi-libc
url = https://github.com/WebAssembly/wasi-libc url = https://github.com/CraneStation/wasi-libc
[submodule "lib/picolibc"] [submodule "lib/picolibc"]
path = lib/picolibc path = lib/picolibc
url = https://github.com/keith-packard/picolibc.git url = https://github.com/keith-packard/picolibc.git
@@ -32,13 +31,3 @@
[submodule "lib/macos-minimal-sdk"] [submodule "lib/macos-minimal-sdk"]
path = lib/macos-minimal-sdk path = lib/macos-minimal-sdk
url = https://github.com/aykevl/macos-minimal-sdk.git url = https://github.com/aykevl/macos-minimal-sdk.git
[submodule "lib/renesas-svd"]
path = lib/renesas-svd
url = https://github.com/tinygo-org/renesas-svd.git
[submodule "src/net"]
path = src/net
url = https://github.com/tinygo-org/net.git
branch = dev
[submodule "lib/wasi-cli"]
path = lib/wasi-cli
url = https://github.com/WebAssembly/wasi-cli
+1 -17
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 LLVM, Clang and LLD are quite light on dependencies, requiring only standard
build tools to be built. Go is of course necessary to build TinyGo itself. build tools to be built. Go is of course necessary to build TinyGo itself.
* Go (1.19+) * Go (1.18+)
* GNU Make
* Standard build tools (gcc/clang) * Standard build tools (gcc/clang)
* git * git
* CMake * 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 The rest of this guide assumes you're running Linux, but it should be equivalent
on a different system like Mac. 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 ## Download the source
The first step is to download the TinyGo sources (use `--recursive` if you clone The first step is to download the TinyGo sources (use `--recursive` if you clone
+4 -635
View File
@@ -1,634 +1,3 @@
0.33.0
---
* **general**
- use latest version of x/tools
- add chromeos 9p support for flashing
- sort compiler error messages by source position in a package
- don't include prebuilt libraries in the release to simplify packaging and reduce the release tarball size
- show runtime panic addresses for `tinygo run`
- support Go 1.23 (including all new language features)
- `test`: support GOOS/GOARCH pairs in the `-target` flag
- `test`: remove message after test binary built
* **compiler**
- remove unused registers for x86_64 linux syscalls
- remove old atomics workaround for AVR (not necessary in modern LLVM versions)
- support `golang.org/x/sys/unix` syscalls
- `builder`: remove workaround for generics race condition
- `builder`: add package ID to compiler and optimization error messages
- `builder`: show better error messages for some common linker errors
- `cgo`: support preprocessor macros passed on the command line
- `cgo`: use absolute paths for error messages
- `cgo`: add support for printf
- `loader`: handle `go list` errors inside TinyGo (for better error messages)
- `transform`: fix incorrect alignment of heap-to-stack transform
- `transform`: use thinlto-pre-link passes (instead of the full pipeline) to speed up compilation speed slightly
* **standard library**
- `crypto/tls`: add CipherSuiteName and some extra fields to ConnectionSTate
- `internal/abi`: implement initial version of this package
- `machine`: use new `internal/binary` package
- `machine`: rewrite Reply() to fix sending long replies in I2C Target Mode
- `machine/usb/descriptor`: Reset joystick physical
- `machine/usb/descriptor`: Drop second joystick hat
- `machine/usb/descriptor`: Add more HID... functions
- `machine/usb/descriptor`: Fix encoding of values
- `machine/usb/hid/joystick`: Allow more hat switches
- `os`: add `Chown`, `Truncate`
- `os/user`: use stdlib version of this package
- `reflect`: return correct name for the `unsafe.Pointer` type
- `reflect`: implement `Type.Overflow*` functions
- `runtime`: implement dummy `getAuxv` to satisfy golang.org/x/sys/
- `runtime`: don't zero out new allocations for `-gc=leaking` when they are already zeroed
- `runtime`: simplify slice growing/appending code
- `runtime`: print a message when a fatal signal like SIGSEGV happens
- `runtime/debug`: add `GoVersion` to `debug.BuildInfo`
- `sync`: add `Map.Clear()`
- `sync/atomic`: add And* and Or* compiler intrinsics needed for Go 1.23
- `syscall`: add `Fork` and `Execve`
- `syscall`: add all MacOS errno values
- `testing`: stub out `T.Deadline`
- `unique`: implement custom (naive) version of the unique package
* **targets**
- `arm`: support `GOARM=*,softfloat` (softfloat support for ARM v5, v6, and v7)
- `mips`: add linux/mipsle (and experimental linux/mips) support
- `mips`: add `GOMIPS=softfloat` support
- `wasip2`: add WASI preview 2 support
- `wasm/js`: add `node:` prefix in `require()` call of wasm_exec.js
- `wasm-unknown`: make sure the `os` package can be imported
- `wasm-unknown`: remove import-memory flag
0.32.0
---
* **general**
- fix wasi-libc include headers on Nix
- apply OpenOCD commands after target configuration
- fix a minor race condition when determining the build tags
- support UF2 drives with a space in their name on Linux
- add LLVM 18 support
- drop support for Go 1.18 to be able to stay up to date
* **compiler**
- move `-panic=trap` support to the compiler/runtime
- fix symbol table index for WebAssembly archives
- fix ed25519 build errors by adjusting the alias names
- add aliases to generic AES functions
- fix race condition by temporarily applying a proposed patch
- `builder`: keep un-wasm-opt'd .wasm if -work was passed
- `builder`: make sure wasm-opt command line is printed if asked
- `cgo`: implement shift operations in preprocessor macros
- `interp`: checking for methodset existence
* **standard library**
- `machine`: add `__tinygo_spi_tx` function to simulator
- `machine`: fix simulator I2C support
- `machine`: add GetRNG support to simulator
- `machine`: add `TxFifoFreeLevel` for CAN
- `os`: add `Link`
- `os`: add `FindProcess` for posix
- `os`: add `Process.Release` for unix
- `os`: add `SetReadDeadline` stub
- `os`, `os/signal`: add signal stubs
- `os/user`: add stubs for `Lookup{,Group}` and `Group`
- `reflect`: use int in `StringHeader` and `SliceHeader` on non-AVR platforms
- `reflect`: fix `NumMethods` for Interface type
- `runtime`: skip negative sleep durations in sleepTicks
* **targets**
- `esp32`: add I2C support
- `rp2040`: move UART0 and UART1 to common file
- `rp2040`: make all RP2040 boards available for simulation
- `rp2040`: fix timeUnit type
- `stm32`: add i2c `Frequency` and `SetBaudRate` function for chips that were missing implementation
- `wasm-unknown`: add math and memory builtins that LLVM needs
- `wasip1`: replace existing `-target=wasi` support with wasip1 as supported in Go 1.21+
* **boards**
- `adafruit-esp32-feather-v2`: add the Adafruit ESP32 Feather V2
- `badger2040-w`: add support for the Badger2040 W
- `feather-nrf52840-sense`: fix lack of LXFO
- `m5paper`: add support for the M5 Paper
- `mksnanov3`: limit programming speed to 1800 kHz
- `nucleol476rg`: add stm32 nucleol476rg support
- `pico-w`: add the Pico W (which is near-idential to the pico target)
- `thingplus-rp2040`, `waveshare-rp2040-zero`: add WS2812 definition
- `pca10059-s140v7`: add this variant to the PCA10059 board
0.31.2
---
* **general**
* update the `net` submodule to updated version with `Buffers` implementation
* **compiler**
* `syscall`: add wasm_unknown tag to some additional files so it can compile more code
* **standard library**
* `runtime`: add Frame.Entry field
0.31.1
---
* **general**
* fix Binaryen build in make task
* update final build stage of Docker `dev` image to go1.22
* only use GHA cache for building Docker `dev` image
* update the `net` submodule to latest version
* **compiler**
* `interp`: make getelementptr offsets signed
* `interp`: return a proper error message when indexing out of range
0.31.0
---
* **general**
* remove LLVM 14 support
* add LLVM 17 support, and use it by default
* add Nix flake support
* update bundled Binaryen to version 116
* add `ports` subcommand that lists available serial ports for `-port` and `-monitor`
* support wasmtime version 14
* add `-serial=rtt` for serial output over SWD
* add Go 1.22 support and use it by default
* change minimum Node.js version from 16 to 18
* **compiler**
* use the new LLVM pass manager
* allow systems with more stack space to allocate larger values on the stack
* `build`: fix a crash due to sharing GlobalValues between build instances
* `cgo`: add `C._Bool` type
* `cgo`: fix calling CGo callback inside generic function
* `compileopts`: set `purego` build tag by default so that more packages can be built
* `compileopts`: force-enable CGo to avoid build issues
* `compiler`: fix crash on type assert on interfaces with no methods
* `interp`: print LLVM instruction in traceback
* `interp`: support runtime times by running them at runtime
* `loader`: enforce Go language version in the type checker (this may break existing programs with an incorrect Go version in go.mod)
* `transform`: fix bug in StringToBytes optimization pass
* **standard library**
* `crypto/tls`: stub out a lot of functions
* `internal/task`, `machine`: make TinyGo code usable with "big Go" CGo
* `machine`: implement `I2C.SetBaudRate` consistently across chips
* `machine`: implement `SPI.Configure` consistently across chips
* `machine`: add `DeviceID` for nrf, rp2040, sam, stm32
* `machine`: use smaller UART buffer size on atmega chips
* `machine/usb`: allow setting a serial number using a linker flag
* `math`: support more math functions on baremetal (picolibc) systems
* `net`: replace entire net package with a new one based on the netdev driver
* `os/user`: add bare-bones implementation of this package
* `reflect`: stub `CallSlice` and `FuncOf`
* `reflect`: add `TypeFor[T]`
* `reflect`: update `IsZero` to Go 1.22 semantics
* `reflect`: move indirect values into interface when setting interfaces
* `runtime`: stub `Breakpoint`
* `sync`: implement trylock
* **targets**
* `atmega`: use UART double speed mode for fewer errors and higher throughput
* `atmega328pb`: refactor to enable extra uart
* `avr`: don't compile large parts of picolibc (math, stdio) for LLVM 17 support
* `esp32`: switch over to the official SVD file
* `esp32c3`: implement USB_SERIAL for USBCDC communication
* `esp32c3`: implement I2C
* `esp32c3`: implement RNG
* `esp32c3`: add more ROM functions and update linker script for the in-progress wifi support
* `esp32c3`: update to newer SVD files
* `rp2040`: add support for UART hardware flow control
* `rp2040`: add definition for `machine.PinToggle`
* `rp2040`: set XOSC startup delay multiplier
* `samd21`: add support for UART hardware flow control
* `samd51`: add support for UART hardware flow control
* `wasm`: increase default stack size to 64k for wasi/wasm targets
* `wasm`: bump wasi-libc version to SDK 20
* `wasm`: remove line of dead code in wasm_exec.js
* **new targets/boards**
* `qtpy-esp32c3`: add Adafruit QT Py ESP32-C3 board
* `mksnanov3`: add support for the MKS Robin Nano V3.x
* `nrf52840-generic`: add generic nrf52840 chip support
* `thumby`: add support for Thumby
* `wasm`: add new `wasm-unknown` target that doesn't depend on WASI or a browser
* **boards**
* `arduino-mkrwifi1010`, `arduino-nano33`, `nano-rp2040`, `matrixportal-m4`, `metro-m4-airlift`, `pybadge`, `pyportal`: add `ninafw` build tag and some constants for BLE support
* `gopher-badge`: fix typo in USB product name
* `nano-rp2040`: add UART1 and correct mappings for NINA via UART
* `pico`: bump default stack size from 2kB to 8kB
* `wioterminal`: expose UART4
0.30.0
---
* **general**
- add LLVM 16 support, use it by default
* **compiler**
- `build`: work around a race condition by building Go SSA serially
- `compiler`: fix a crash by not using the LLVM global context types
- `interp`: don't copy unknown values in `runtime.sliceCopy` to fix miscompile
- `interp`: fix crash in error report by not returning raw LLVM values
* **standard library**
- `machine/usb/adc/midi`: various improvements and API changes
- `reflect`: add support for `[...]T``[]T` in reflect
* **targets**
- `atsamd21`, `atsamd51`: add support for USB INTERRUPT OUT
- `rp2040`: always use the USB device enumeration fix, even in chips that supposedly have the HW fix
- `wasm`: increase default stack size to 32k for wasi/wasm
* **boards**
- `gobadge`: add GoBadge target as alias for PyBadge :)
- `gemma-m0`: add support for the Adafruit Gemma M0
0.29.0
---
* **general**
- Go 1.21 support
- use https for renesas submodule #3856
- ci: rename release-double-zipped to something more useful
- ci: update Node.js from version 14 to version 16
- ci: switch GH actions builds to use Go 1.21 final release
- docker: update clang to version 15
- docker: use Go 1.21 for Docker dev container build
- `main`: add target JSON file in `tinygo info` output
- `main`: improve detection of filesystems
- `main`: use `go env` instead of doing all detection manually
- make: add make task to generate Renesas device wrappers
- make: add task to check NodeJS version before running tests
- add submodule for Renesas SVD file mirror repo
- update to go-serial package v1.6.0
- `testing`: add Testing function
- `tools/gen-device-svd`: small changes needed for Renesas MCUs
* **compiler**
- `builder`: update message for max supported Go version
- `compiler,reflect`: NumMethods reports exported methods only
- `compiler`: add compiler-rt and wasm symbols to table
- `compiler`: add compiler-rt to wasm.json
- `compiler`: add min and max builtin support
- `compiler`: implement clear builtin for maps
- `compiler`: implement clear builtin for slices
- `compiler`: improve panic message when a runtime call is unavailable
- `compiler`: update .ll test output
- `loader`: merge go.env file which is now required starting in Go 1.21 to correctly get required packages
* **standard library**
- `os`: define ErrNoDeadline
- `reflect`: Add FieldByNameFunc
- `reflect`: add SetZero
- `reflect`: fix iterating over maps with interface{} keys
- `reflect`: implement Value.Grow
- `reflect`: remove unnecessary heap allocations
- `reflect`: use .key() instead of a type assert
- `sync`: add implementation from upstream Go for OnceFunc, OnceValue, and OnceValues
* **targets**
- `machine`: UART refactor (#3832)
- `machine/avr`: pin change interrupt
- `machine/macropad_rp2040`: add machine.BUTTON
- `machine/nrf`: add I2C timeout
- `machine/nrf`: wait for stop condition after reading from the I2C bus
- `machine/nRF52`: set SPI TX/RX lengths even data is empty. Fixes #3868 (#3877)
- `machine/rp2040`: add missing suffix to CMD_READ_STATUS
- `machine/rp2040`: add NoPin support
- `machine/rp2040`: move flash related functions into separate file from C imports for correct - LSP. Fixes #3852
- `machine/rp2040`: wait for 1000 us after flash reset to avoid issues with busy USB bus
- `machine/samd51,rp2040,nrf528xx,stm32`: implement watchdog
- `machine/samd51`: fix i2cTimeout was decreasing due to cache activation
- `machine/usb`: Add support for HID Keyboard LEDs
- `machine/usb`: allow USB Endpoint settings to be changed externally
- `machine/usb`: refactor endpoint configuration
- `machine/usb`: remove usbDescriptorConfig
- `machine/usb/hid,joystick`: fix hidreport (3) (#3802)
- `machine/usb/hid`: add RxHandler interface
- `machine/usb/hid`: rename Handler() to TxHandler()
- `wasi`: allow zero inodes when reading directories
- `wasm`: add support for GOOS=wasip1
- `wasm`: fix functions exported through //export
- `wasm`: remove i64 workaround, use BigInt instead
- `example`: adjust time offset
- `example`: simplify pininterrupt
* **boards**
- `targets`: add AKIZUKI DENSHI AE-RP2040
- `targets`: adding new uf2 target for PCA10056 (#3765)
0.28.0
---
* **general**
- fix parallelism in the compiler on Windows by building LLVM with thread support
- support qemu-user debugging
- make target JSON msd-volume-name an array
- print source location when a panic happens in -monitor
- `test`: don't print `ok` for a successful compile-only
* **compiler**
- `builder`: remove non-ThinLTO build mode
- `builder`: fail earlier if Go is not available
- `builder`: improve `-size=full` in a number of ways
- `builder`: implement Nordic DFU file writer in Go
- `cgo`: allow `LDFLAGS: --export=...`
- `compiler`: support recursive slice types
- `compiler`: zero struct padding during map operations
- `compiler`: add llvm.ident metadata
- `compiler`: remove `unsafe.Pointer(uintptr(v) + idx)` optimization (use `unsafe.Add` instead)
- `compiler`: add debug info to `//go:embed` data structures for better `-size` output
- `compiler`: add debug info to string constants
- `compiler`: fix a minor race condition
- `compiler`: emit correct alignment in debug info for global variables
- `compiler`: correctly generate reflect data for local named types
- `compiler`: add alloc attributes to `runtime.alloc`, reducing flash usage slightly
- `compiler`: for interface maps, use the original named type if available
- `compiler`: implement most math/bits functions as LLVM intrinsics
- `compiler`: ensure all defers have been seen before creating rundefers
* **standard library**
- `internal/task`: disallow blocking inside an interrupt
- `machine`: add `CPUReset`
- `machine/usb/hid`: add MediaKey support
- `machine/usb/hid/joystick`: move joystick under HID
- `machine/usb/hid/joystick`: allow joystick settings override
- `machine/usb/hid/joystick`: handle case where we cannot find the correct HID descriptor
- `machine/usb/hid/mouse`: add support for mouse back and forward
- `machine/usb`: add ability to override default VID, PID, manufacturer name, and product name
- `net`: added missing `TCPAddr` and `UDPAddr` implementations
- `os`: add IsTimeout function
- `os`: fix resource leak in `(*File).Close`
- `os`: add `(*File).Sync`
- `os`: implement `(*File).ReadDir` for wasi
- `os`: implement `(*File).WriteAt`
- `reflect`: make sure null bytes are supported in tags
- `reflect`: refactor this package to enable many new features
- `reflect`: add map type methods: `Elem` and `Key`
- `reflect`: add map methods: `MapIndex`, `MapRange`/`MapIter`, `SetMapIndex`, `MakeMap`, `MapKeys`
- `reflect`: add slice methods: `Append`, `MakeSlice`, `Slice`, `Slice3`, `Copy`, `Bytes`, `SetLen`
- `reflect`: add misc methods: `Zero`, `Addr`, `UnsafeAddr`, `OverflowFloat`, `OverflowInt`, `OverflowUint`, `SetBytes`, `Convert`, `CanInt`, `CanFloat`, `CanComplex`, `Comparable`
- `reflect`: add type methods: `String`, `PkgPath`, `FieldByName`, `FieldByIndex`, `NumMethod`
- `reflect`: add stubs for `Type.Method`, `CanConvert`, `ArrayOf`, `StructOf`, `MapOf`
- `reflect`: add stubs for channel select routines/types
- `reflect`: allow nil rawType to call Kind()
- `reflect`: ensure all ValueError panics have Kind fields
- `reflect`: add support for named types
- `reflect`: improve `Value.String()`
- `reflect`: set `Index` and `PkgPath` field in `Type.Field`
- `reflect`: `Type.AssignableTo`: you can assign anything to `interface{}`
- `reflect`: add type check to `Value.Field`
- `reflect`: let `TypeOf(nil)` return nil
- `reflect`: move `StructField.Anonymous` field to match upstream location
- `reflect`: add `UnsafePointer` for Func types
- `reflect`: `MapIter.Next` needs to allocate new keys/values every time
- `reflect`: fix `IsNil` for interfaces
- `reflect`: fix `Type.Name` to return an empty string for non-named types
- `reflect`: add `VisibleFields`
- `reflect`: properly handle embedded structs
- `reflect`: make sure `PointerTo` works for named types
- `reflect`: `Set`: convert non-interface to interface
- `reflect`: `Set`: fix direction of assignment check
- `reflect`: support channel directions
- `reflect`: print struct tags in Type.String()
- `reflect`: properly handle read-only values
- `runtime`: allow custom-gc SetFinalizer and clarify KeepAlive
- `runtime`: implement KeepAlive using inline assembly
- `runtime`: check for heap allocations inside interrupts
- `runtime`: properly turn pointer into empty interface when hashing
- `runtime`: improve map size hint usage
- `runtime`: zero map key/value on deletion to so GC doesn't see them
- `runtime`: print the address where a panic happened
- `runtime/debug`: stub `SetGCPercent`, `BuildInfo.Settings`
- `runtime/metrics`: add this package as a stub
- `syscall`: `Stat_t` timespec fields are Atimespec on darwin
- `syscall`: add `Timespec.Unix()` for wasi
- `syscall`: add fsync using libc
- `testing`: support -test.count
- `testing`: make test output unbuffered when verbose
- `testing`: add -test.skip
- `testing`: move runtime.GC() call to runN to match upstream
- `testing`: add -test.shuffle to order randomize test and benchmark order
* **targets**
- `arm64`: fix register save/restore to include vector registers
- `attiny1616`: add support for this chip
- `cortexm`: refactor EnableInterrupts and DisableInterrupts to avoid `arm.AsmFull`
- `cortexm`: enable functions in RAM for go & cgo
- `cortexm`: convert SystemStack from `AsmFull` to C inline assembly
- `cortexm`: fix crash due to wrong stack size offset
- `nrf`: samd21, stm32: add flash API
- `nrf`: fix memory issue in ADC read
- `nrf`: new peripheral type for nrf528xx chips
- `nrf`: implement target mode
- `nrf`: improve ADC and add oversampling, longer sample time, and reference voltage
- `rp2040`: change calling order for device enumeration fix to do first
- `rp2040`: rtc delayed interrupt
- `rp2040`: provide better errors for invalid pins on I2C and SPI
- `rp2040`: change uart to allow for a single pin
- `rp2040`: implement Flash interface
- `rp2040`: remove SPI `DataBits` property
- `rp2040`: unify all linker scripts using LDFLAGS
- `rp2040`: remove SPI deadline for improved performance
- `rp2040`: use 4MHz as default frequency for SPI
- `rp2040`: implement target mode
- `rp2040`: use DMA for send-only SPI transfers
- `samd21`: rearrange switch case for get pin cfg
- `samd21`: fix issue with WS2812 driver by making pin accesses faster
- `samd51`: enable CMCC cache for greatly improved performance
- `samd51`: remove extra BK0RDY clear
- `samd51`: implement Flash interface
- `samd51`: use correct SPI frequency
- `samd51`: remove extra BK0RDY clear
- `samd51`: fix ADC multisampling
- `wasi`: allow users to set the `runtime_memhash_tsip` or `runtime_memhash_fnv` build tags
- `wasi`: set `WASMTIME_BACKTRACE_DETAILS` when running in wasmtime.
- `wasm`: implement the `//go:wasmimport` directive
* **boards**
- `gameboy-advance`: switch to use register definitions in device/gba
- `gameboy-advance`: rename display and make pointer receivers
- `gopher-badge`: Added Gopher Badge support
- `lorae5`: add needed definition for UART2
- `lorae5`: correct mapping for I2C bus, add pin mapping to enable power
- `pinetime`: update the target file (rename from pinetime-devkit0)
- `qtpy`: fix bad pin assignment
- `wioterminal`: fix pin definition of BCM13
- `xiao`: Pins D4 & D5 are I2C1. Use pins D2 & D3 for I2C0.
- `xiao`: add DefaultUART
0.27.0
---
* **general**
- all: update musl
- all: remove "acm:"` prefix for USB vid/pid pair
- all: add support for LLVM 15
- all: use DWARF version 4
- all: add initial (incomplete) support for Go 1.20
- all: add `-gc=custom` option
- `main`: print ldflags including ThinLTO flags with -x
- `main`: fix error message when a serial port can't be accessed
- `main`: add `-timeout` flag to allow setting how long TinyGo will try looking for a MSD volume for flashing
- `test`: print PASS on pass when running standalone test binaries
- `test`: fix printing of benchmark output
- `test`: print package name when compilation failed (not just when the test failed)
* **compiler**
- refactor to support LLVM 15
- `builder`: print compiler commands while building a library
- `compiler`: fix stack overflow when creating recursive pointer types (fix for LLVM 15+ only)
- `compiler`: allow map keys and values of ≥256 bytes
- `cgo`: add support for `C.float` and `C.double`
- `cgo`: support anonymous enums included in multiple Go files
- `cgo`: add support for bitwise operators
- `interp`: add support for constant icmp instructions
- `transform`: fix memory corruption issues
* **standard library**
- `machine/usb`: remove allocs in USB ISR
- `machine/usb`: add `Port()` and deprecate `New()` to have the API better match the singleton that is actually being returned
- `machine/usb`: change HID usage-maximum to 0xFF
- `machine/usb`: add USB HID joystick support
- `machine/usb`: change to not send before endpoint initialization
- `net`: implement `Pipe`
- `os`: add stub for `os.Chtimes`
- `reflect`: stub out `Type.FieldByIndex`
- `reflect`: add `Value.IsZero` method
- `reflect`: fix bug in `.Field` method when the field fits in a pointer but the parent doesn't
- `runtime`: switch some `panic()` calls in the gc to `runtimePanic()` for consistency
- `runtime`: add xorshift-based fastrand64
- `runtime`: fix alignment for arm64, arm, xtensa, riscv
- `runtime`: implement precise GC
- `runtime/debug`: stub `PrintStack`
- `sync`: implement simple pooling in `sync.Pool`
- `syscall`: stubbed `Setuid`, Exec and friends
- `syscall`: add more stubs as needed for Go 1.20 support
- `testing`: implement `t.Setenv`
- `unsafe`: add support for Go 1.20 slice/string functions
* **targets**
- `all`: do not set stack size per board
- `all`: update picolibc to v1.7.9
- `atsame5x`: fix CAN extendedID handling
- `atsame5x`: reduce heap allocation
- `avr`: drop GNU toolchain dependency
- `avr`: fix .data initialization for binaries over 64kB
- `avr`: support ThinLTO
- `baremetal`: implements calloc
- `darwin`: fix `syscall.Open` on darwin/arm64
- `darwin`: fix error with `tinygo lldb`
- `esp`: use LLVM Xtensa linker instead of Espressif toolchain
- `esp`: use ThinLTO for Xtensa
- `esp32c3`: add SPI support
- `linux`: include musl `getpagesize` function in release
- `nrf51`: add ADC implementation
- `nrf52840`: add PDM support
- `riscv`: add "target-abi" metadata flag
- `rp2040`: remove mem allocation in GPIO ISR
- `rp2040`: avoid allocating clock on heap
- `rp2040`: add basic GPIO support for PIO
- `rp2040`: fix USB interrupt issue
- `rp2040`: fix RP2040-E5 USB errata
- `stm32`: always set ADC pins to pullups floating
- `stm32f1`, `stm32f4`: fix ADC by clearing the correct bit for rank after each read
- `stm32wl`: Fix incomplete RNG initialisation
- `stm32wlx`: change order for init so clock speeds are set before peripheral start
- `wasi`: makes wasmtime "run" explicit
- `wasm`: fix GC scanning of allocas
- `wasm`: allow custom malloc implementation
- `wasm`: remove `-wasm-abi=` flag (use `-target` instead)
- `wasm`: fix scanning of the stack
- `wasm`: fix panic when allocating 0 bytes using malloc
- `wasm`: always run wasm-opt even with `-scheduler=none`
- `wasm`: avoid miscompile with ThinLTO
- `wasm`: allow the emulator to expand `{tmpDir}`
- `wasm`: support ThinLTO
- `windows`: update mingw-w64 version to avoid linker warning
- `windows`: add ARM64 support
* **boards**
- Add Waveshare RP2040 Zero
- Add Arduino Leonardo support
- Add Adafruit KB2040
- Add Adafruit Feather M0 Express
- Add Makerfabs ESP32C3SPI35 TFT Touchscreen board
- Add Espressif ESP32-C3-DevKit-RUST-1 board
- `lgt92`: fix OpenOCD configuration
- `xiao-rp2040`: fix D9 and D10 constants
- `xiao-rp2040`: add pin definitions
0.26.0
---
* **general**
- remove support for LLVM 13
- remove calls to deprecated ioutil package
- move from `os.IsFoo` to `errors.Is(err, ErrFoo)`
- fix for builds using an Android host
- make interp timeout configurable from command line
- ignore ports with VID/PID if there is no candidates
- drop support for Go 1.16 and Go 1.17
- update serial package to v1.3.5 for latest bugfixes
- remove GOARM from `tinygo info`
- add flag for setting the goroutine stack size
- add serial port monitoring functionality
* **compiler**
- `cgo`: implement support for static functions
- `cgo`: fix panic when FuncType.Results is nil
- `compiler`: add aliases for `edwards25519/field.feMul` and `field.feSquare`
- `compiler`: fix incorrect DWARF type in some generic parameters
- `compiler`: use LLVM math builtins everywhere
- `compiler`: replace some math operation bodies with LLVM intrinsics
- `compiler`: replace math aliases with intrinsics
- `compiler`: fix `unsafe.Sizeof` for chan and map values
- `compileopts`: use tags parser from buildutil
- `compileopts`: use backticks for regexp to avoid extra escapes
- `compileopts`: fail fast on duplicate values in target field slices
- `compileopts`: fix windows/arm target triple
- `compileopts`: improve error handling when loading target/*.json
- `compileopts`: add support for stlink-dap programmer
- `compileopts`: do not complain about `-no-debug` on MacOS
- `goenv`: support `GOOS=android`
- `interp`: fix reading from external global
- `loader`: fix link error for `crypto/internal/boring/sig.StandardCrypto`
* **standard library**
- rename assembly files to .S extension
- `machine`: add PWM peripheral comments to pins
- `machine`: improve UARTParity slightly
- `machine`: do not export DFU_MAGIC_* constants on nrf52840
- `machine`: rename `PinInputPullUp`/`PinInputPullDown`
- `machine`: add `KHz`, `MHz`, `GHz` constants, deprecate `TWI_FREQ_*` constants
- `machine`: remove level triggered pin interrupts
- `machine`: do not expose `RESET_MAGIC_VALUE`
- `machine`: use `NoPin` constant where appropriate (instead of `0` for example)
- `net`: sync net.go with Go 1.18 stdlib
- `os`: add `SyscallError.Timeout`
- `os`: add `ErrProcessDone` error
- `reflect`: implement `CanInterface` and fix string `Index`
- `runtime`: make `MemStats` available to leaking collector
- `runtime`: add `MemStats.TotalAlloc`
- `runtime`: add `MemStats.Mallocs` and `Frees`
- `runtime`: add support for `time.NewTimer` and `time.NewTicker`
- `runtime`: implement `resetTimer`
- `runtime`: ensure some headroom for the GC to run
- `runtime`: make gc and scheduler asserts settable with build tags
- `runtime/pprof`: add `WriteHeapProfile`
- `runtime/pprof`: `runtime/trace`: stub some additional functions
- `sync`: implement `Map.LoadAndDelete`
- `syscall`: group WASI consts by purpose
- `syscall`: add WASI `{D,R}SYNC`, `NONBLOCK` FD flags
- `syscall`: add ENOTCONN on darwin
- `testing`: add support for -benchmem
* **targets**
- remove USB vid/pid pair of bootloader
- `esp32c3`: remove unused `UARTStopBits` constants
- `nrf`: implement `GetRNG` function
- `nrf`: `rp2040`: add `machine.ReadTemperature`
- `nrf52`: cleanup s140v6 and s140v7 uf2 targets
- `rp2040`: implement semi-random RNG based on ROSC based on pico-sdk
- `wasm`: add summary of wasm examples and fix callback bug
- `wasm`: do not allow undefined symbols (`--allow-undefined`)
- `wasm`: make sure buffers returned by `malloc` are kept until `free` is called
- `windows`: save and restore xmm registers when switching goroutines
* **boards**
- add Pimoroni's Tufty2040
- add XIAO ESP32C3
- add Adafruit QT2040
- add Adafruit QT Py RP2040
- `esp32c3-12f`: `matrixportal-m4`: `p1am-100`: remove duplicate build tags
- `hifive1-qemu`: remove this emulated board
- `wioterminal`: add UART3 for RTL8720DN
- `xiao-ble`: fix usbpid
0.25.0 0.25.0
--- ---
@@ -866,7 +235,7 @@
- `interp`: always run atomic and volatile loads/stores at runtime - `interp`: always run atomic and volatile loads/stores at runtime
- `interp`: bump timeout to 180 seconds - `interp`: bump timeout to 180 seconds
- `interp`: handle type assertions on nil interfaces - `interp`: handle type assertions on nil interfaces
- `loader`: eliminate goroot cache inconsistency - `loader`: elminate goroot cache inconsistency
- `loader`: respect $GOROOT when running `go list` - `loader`: respect $GOROOT when running `go list`
- `transform`: allocate the correct amount of bytes in an alloca - `transform`: allocate the correct amount of bytes in an alloca
- `transform`: remove switched func lowering - `transform`: remove switched func lowering
@@ -1565,7 +934,7 @@
- `sync`: add WaitGroup - `sync`: add WaitGroup
* **targets** * **targets**
- `arm`: allow nesting in DisableInterrupts and EnableInterrupts - `arm`: allow nesting in DisableInterrupts and EnableInterrupts
- `arm`: make FPU configuration consistent - `arm`: make FPU configuraton consistent
- `arm`: do not mask fault handlers in critical sections - `arm`: do not mask fault handlers in critical sections
- `atmega2560`: fix pin mapping for pins D2, D5 and the L port - `atmega2560`: fix pin mapping for pins D2, D5 and the L port
- `atsamd`: return an error when an incorrect PWM pin is used - `atsamd`: return an error when an incorrect PWM pin is used
@@ -1594,7 +963,7 @@
- `nrf`: add microbit-s110v8 target - `nrf`: add microbit-s110v8 target
- `nrf`: fix bug in SPI.Tx - `nrf`: fix bug in SPI.Tx
- `nrf`: support debugging the PCA10056 - `nrf`: support debugging the PCA10056
- `pygamer`: add Adafruit PyGamer support - `pygamer`: add Adafruit PyGamer suport
- `riscv`: fix interrupt configuration bug - `riscv`: fix interrupt configuration bug
- `riscv`: disable linker relaxations during gp init - `riscv`: disable linker relaxations during gp init
- `stm32f4disco`: add new target with ST-Link v2.1 debugger - `stm32f4disco`: add new target with ST-Link v2.1 debugger
@@ -1956,7 +1325,7 @@
- allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it - allow packages like github.com/tinygo-org/tinygo/src/\* by aliasing it
- remove `//go:volatile` support - remove `//go:volatile` support
It has been replaced with the runtime/volatile package. It has been replaced with the runtime/volatile package.
- allow pointers in map keys - allow poiners in map keys
- support non-constant syscall numbers - support non-constant syscall numbers
- implement non-blocking selects - implement non-blocking selects
- add support for the `-tags` flag - add support for the `-tags` flag
+29 -22
View File
@@ -1,15 +1,10 @@
# tinygo-llvm stage obtains the llvm source for TinyGo # tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.23 AS tinygo-llvm FROM golang:1.18 AS tinygo-llvm
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-15 ninja-build && \ apt-get install -y apt-utils make cmake clang-11 binutils-avr gcc-avr avr-libc ninja-build
rm -rf \
/var/lib/apt/lists/* \
/var/log/* \
/var/tmp/* \
/tmp/*
COPY ./GNUmakefile /tinygo/GNUmakefile COPY ./Makefile /tinygo/Makefile
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
make llvm-source make llvm-source
@@ -20,24 +15,36 @@ FROM tinygo-llvm AS tinygo-llvm-build
RUN cd /tinygo/ && \ RUN cd /tinygo/ && \
make llvm-build make llvm-build
# tinygo-compiler-build stage builds the compiler itself # tinygo-xtensa stage installs tools needed for ESP32
FROM tinygo-llvm-build AS tinygo-compiler-build FROM tinygo-llvm-build AS tinygo-xtensa
ARG xtensa_version="1.22.0-80-g6c4433a-5.2.0"
RUN cd /tmp/ && \
wget -q https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
tar xzf xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
cp ./xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/ && \
rm -rf /tmp/xtensa*
# tinygo-compiler stage builds the compiler itself
FROM tinygo-xtensa AS tinygo-compiler
COPY . /tinygo COPY . /tinygo
# build the compiler and tools # update submodules
RUN cd /tinygo/ && \ 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 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.23 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"] CMD ["tinygo"]
+2 -2
View File
@@ -1,7 +1,7 @@
Copyright (c) 2018-2023 The TinyGo Authors. All rights reserved. Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library. TinyGo includes portions of the Go standard library.
Copyright (c) 2009-2023 The Go Authors. All rights reserved. Copyright (c) 2009-2022 The Go Authors. All rights reserved.
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information. LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
+77 -232
View File
@@ -10,7 +10,7 @@ LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
# Try to autodetect LLVM build tools. # Try to autodetect LLVM build tools.
# Versions are listed here in descending priority order. # Versions are listed here in descending priority order.
LLVM_VERSIONS = 18 17 16 15 LLVM_VERSIONS = 14 13 12 11
errifempty = $(if $(1),$(1),$(error $(2))) errifempty = $(if $(1),$(1),$(error $(2)))
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2))) detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
toolSearchPathsVersion = $(1)-$(2) toolSearchPathsVersion = $(1)-$(2)
@@ -30,8 +30,10 @@ GO ?= go
export GOROOT = $(shell $(GO) env GOROOT) export GOROOT = $(shell $(GO) env GOROOT)
# Flags to pass to go test. # Flags to pass to go test.
GOTESTFLAGS ?= GOTESTFLAGS ?= -v
GOTESTPKGS ?= ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
# md5sum binary
MD5SUM = md5sum
# tinygo binary for tests # tinygo binary for tests
TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo) TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
@@ -54,12 +56,6 @@ else
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF' LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
endif endif
# Enable AddressSanitizer
ifeq (1, $(ASAN))
LLVM_OPTION += -DLLVM_USE_SANITIZER=Address
CGO_LDFLAGS += -fsanitize=address
endif
ifeq (1, $(STATIC)) ifeq (1, $(STATIC))
# Build TinyGo as a fully statically linked binary (no dynamically loaded # Build TinyGo as a fully statically linked binary (no dynamically loaded
# libraries such as a libc). This is not supported with glibc which is used # libraries such as a libc). This is not supported with glibc which is used
@@ -111,15 +107,17 @@ endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp .PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsmanifest
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
EXE = .exe EXE = .exe
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
# PIC needs to be disabled for libclang to work. # LLVM compiled using MinGW on Windows appears to have problems with threads.
LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF # Without this flag, linking results in errors like these:
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++ CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
@@ -128,14 +126,14 @@ ifeq ($(OS),Windows_NT)
USE_SYSTEM_BINARYEN ?= 1 USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),Darwin) else ifeq ($(shell uname -s),Darwin)
MD5SUM ?= md5 MD5SUM = md5
CGO_LDFLAGS += -lxar CGO_LDFLAGS += -lxar
USE_SYSTEM_BINARYEN ?= 1 USE_SYSTEM_BINARYEN ?= 1
else ifeq ($(shell uname -s),FreeBSD) else ifeq ($(shell uname -s),FreeBSD)
MD5SUM ?= md5 MD5SUM = md5
START_GROUP = -Wl,--start-group START_GROUP = -Wl,--start-group
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
else else
@@ -143,11 +141,8 @@ else
END_GROUP = -Wl,--end-group END_GROUP = -Wl,--end-group
endif endif
# md5sum binary default, can be overridden by an environment variable
MD5SUM ?= md5sum
# Libraries that should be linked in for the statically linked Clang. # Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++ CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD. # Libraries that should be linked in for the statically linked LLD.
@@ -155,7 +150,7 @@ LLD_LIB_NAMES = lldCOFF lldCommon lldELF lldMachO lldMinGW lldWasm
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP) LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
# Other libraries that are needed to link TinyGo. # Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMX86TargetMCA
# All libraries to be built and linked with the tinygo binary (lib/lib*.a). # All libraries to be built and linked with the tinygo binary (lib/lib*.a).
LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES) LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
@@ -166,26 +161,26 @@ LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
# library path (for ninja). # library path (for ninja).
# This list also includes a few tools that are necessary as part of the full # This list also includes a few tools that are necessary as part of the full
# TinyGo build. # TinyGo build.
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES))) NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES)))
# For static linking. # For static linking.
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++17 CGO_CXXFLAGS=-std=c++14
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA) CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif endif
clean: ## Remove build directory clean:
@rm -rf build @rm -rf build
FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform FMT_PATHS = ./*.go builder cgo/*.go compiler interp loader src transform
fmt: ## Reformat source fmt:
@gofmt -l -w $(FMT_PATHS) @gofmt -l -w $(FMT_PATHS)
fmt-check: ## Warn if any source needs reformatting fmt-check:
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1 @unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp ## Generate microcontroller-specific sources gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
gen-device: gen-device-stm32 gen-device: gen-device-stm32
endif endif
@@ -233,20 +228,18 @@ gen-device-rp: build/gen-device-svd
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/ ./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
GO111MODULE=off $(GO) fmt ./src/device/rp GO111MODULE=off $(GO) fmt ./src/device/rp
gen-device-renesas: build/gen-device-svd # Get LLVM sources.
./build/gen-device-svd -source=https://github.com/tinygo-org/renesas-svd lib/renesas-svd/ src/device/renesas/
GO111MODULE=off $(GO) fmt ./src/device/renesas
$(LLVM_PROJECTDIR)/llvm: $(LLVM_PROJECTDIR)/llvm:
git clone -b tinygo_xtensa_release_18.1.2 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR) git clone -b xtensa_release_14.0.0-patched --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources llvm-source: $(LLVM_PROJECTDIR)/llvm
# Configure LLVM. # Configure LLVM.
TINYGO_SOURCE_DIR=$(shell pwd) TINYGO_SOURCE_DIR=$(shell pwd)
$(LLVM_BUILDDIR)/build.ninja: $(LLVM_BUILDDIR)/build.ninja:
mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;AVR;Mips;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_ZSTD=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION) mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja ## Build LLVM # Build LLVM.
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS) cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS)
ifneq ($(USE_SYSTEM_BINARYEN),1) ifneq ($(USE_SYSTEM_BINARYEN),1)
@@ -255,7 +248,7 @@ ifneq ($(USE_SYSTEM_BINARYEN),1)
binaryen: build/wasm-opt$(EXE) binaryen: build/wasm-opt$(EXE)
build/wasm-opt$(EXE): build/wasm-opt$(EXE):
mkdir -p build mkdir -p build
cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON -DBUILD_TESTS=OFF -DENABLE_WERROR=OFF $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE) cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE)
cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE) cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE)
endif endif
@@ -264,36 +257,15 @@ endif
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi @if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
cd lib/wasi-libc && $(MAKE) -j4 EXTRA_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC="$(CLANG)" AR=$(LLVM_AR) NM=$(LLVM_NM) cd lib/wasi-libc && make -j4 WASM_CFLAGS="-O2 -g -DNDEBUG -mnontrapping-fptoint -msign-ext" MALLOC_IMPL=none CC=$(CLANG) AR=$(LLVM_AR) NM=$(LLVM_NM)
# Generate WASI syscall bindings
WASM_TOOLS_MODULE=github.com/ydnar/wasm-tools-go
.PHONY: wasi-syscall
wasi-syscall: wasi-cm
go run -modfile ./internal/wasm-tools/go.mod $(WASM_TOOLS_MODULE)/cmd/wit-bindgen-go generate --versioned -o ./src/internal -p internal --cm internal/cm ./lib/wasi-cli/wit
# Copy package cm into src/internal/cm # Build the Go compiler.
.PHONY: wasi-cm tinygo:
wasi-cm: @if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
# rm -rf ./src/internal/cm
rsync -rv --delete --exclude '*_test.go' $(shell go list -modfile ./internal/wasm-tools/go.mod -m -f {{.Dir}} $(WASM_TOOLS_MODULE))/cm ./src/internal/
# Check for Node.js used during WASM tests.
NODEJS_VERSION := $(word 1,$(subst ., ,$(shell node -v | cut -c 2-)))
MIN_NODEJS_VERSION=18
.PHONY: check-nodejs-version
check-nodejs-version:
ifeq (, $(shell which node))
@echo "Install NodeJS version 18+ to run tests."; exit 1;
endif
@if [ $(NODEJS_VERSION) -lt $(MIN_NODEJS_VERSION) ]; then echo "Install NodeJS version 18+ to run tests."; exit 1; fi
tinygo: ## Build the TinyGo compiler
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" . CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
test: wasi-libc check-nodejs-version test: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS) CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi # Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \ TEST_PACKAGES_SLOW = \
@@ -303,12 +275,12 @@ TEST_PACKAGES_SLOW = \
# Standard library packages that pass tests quickly on darwin, linux, wasi, and windows # Standard library packages that pass tests quickly on darwin, linux, wasi, and windows
TEST_PACKAGES_FAST = \ TEST_PACKAGES_FAST = \
compress/lzw \
compress/zlib \ compress/zlib \
container/heap \ container/heap \
container/list \ container/list \
container/ring \ container/ring \
crypto/des \ crypto/des \
crypto/internal/subtle \
crypto/md5 \ crypto/md5 \
crypto/rc4 \ crypto/rc4 \
crypto/sha1 \ crypto/sha1 \
@@ -319,7 +291,6 @@ TEST_PACKAGES_FAST = \
encoding \ encoding \
encoding/ascii85 \ encoding/ascii85 \
encoding/base32 \ encoding/base32 \
encoding/base64 \
encoding/csv \ encoding/csv \
encoding/hex \ encoding/hex \
go/scanner \ go/scanner \
@@ -332,6 +303,7 @@ TEST_PACKAGES_FAST = \
internal/profile \ internal/profile \
math \ math \
math/cmplx \ math/cmplx \
net \
net/http/internal/ascii \ net/http/internal/ascii \
net/mail \ net/mail \
os \ os \
@@ -344,7 +316,6 @@ TEST_PACKAGES_FAST = \
unicode \ unicode \
unicode/utf16 \ unicode/utf16 \
unicode/utf8 \ unicode/utf8 \
unique \
$(nil) $(nil)
# Assume this will go away before Go2, so only check minor version. # Assume this will go away before Go2, so only check minor version.
@@ -355,15 +326,13 @@ TEST_PACKAGES_FAST += crypto/elliptic/internal/fiat
endif endif
# archive/zip requires os.ReadAt, which is not yet supported on windows # archive/zip requires os.ReadAt, which is not yet supported on windows
# bytes requires mmap
# compress/flate appears to hang on wasi # compress/flate appears to hang on wasi
# compress/lzw appears to hang on wasi
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic # crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows # debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# image requires recover(), which is not yet supported on wasi # io/fs requires os.ReadDir, which is not yet supported on windows or wasi
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# mime/quotedprintable requires syscall.Faccessat
# strconv requires recover() which is not yet supported on wasi # strconv requires recover() which is not yet supported on wasi
# text/tabwriter requires recover(), which is not yet supported on wasi
# text/template/parse requires recover(), which is not yet supported on wasi # text/template/parse requires recover(), which is not yet supported on wasi
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi # testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
@@ -371,24 +340,22 @@ endif
TEST_PACKAGES_LINUX := \ TEST_PACKAGES_LINUX := \
archive/zip \ archive/zip \
compress/flate \ compress/flate \
compress/lzw \
crypto/hmac \ crypto/hmac \
debug/dwarf \ debug/dwarf \
debug/plan9obj \ debug/plan9obj \
image \ io/fs \
io/ioutil \ io/ioutil \
mime/quotedprintable \
net \
os/user \
strconv \ strconv \
text/tabwriter \ testing/fstest \
text/template/parse text/template/parse
TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX) TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
TEST_PACKAGES_WINDOWS := \ TEST_PACKAGES_WINDOWS := \
compress/flate \ compress/flate \
compress/lzw \
crypto/hmac \ crypto/hmac \
os/user \
strconv \ strconv \
text/template/parse \ text/template/parse \
$(nil) $(nil)
@@ -406,15 +373,12 @@ report-stdlib-tests-pass:
# Standard library packages that pass tests quickly on the current platform # Standard library packages that pass tests quickly on the current platform
ifeq ($(shell uname),Darwin) ifeq ($(shell uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true
endif endif
ifeq ($(shell uname),Linux) ifeq ($(shell uname),Linux)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
TEST_IOFS := true
endif endif
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false
endif endif
# Test known-working standard library packages. # Test known-working standard library packages.
@@ -422,12 +386,6 @@ endif
.PHONY: tinygo-test .PHONY: tinygo-test
tinygo-test: tinygo-test:
$(TINYGO) test $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW) $(TINYGO) test $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143.
ifeq ($(TEST_IOFS),true)
$(TINYGO) test -stack-size=6MB io/fs
endif
tinygo-test-fast: tinygo-test-fast:
$(TINYGO) test $(TEST_PACKAGES_HOST) $(TINYGO) test $(TEST_PACKAGES_HOST)
tinygo-bench: tinygo-bench:
@@ -437,38 +395,13 @@ tinygo-bench-fast:
# Same thing, except for wasi rather than the current platform. # Same thing, except for wasi rather than the current platform.
tinygo-test-wasi: tinygo-test-wasi:
$(TINYGO) test -target wasip1 $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1: tinygo-test-wasi-fast:
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi $(TINYGO) test -target wasi $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
tinygo-test-wasip1-fast: tinygo-bench-wasi:
$(TINYGO) test -target=wasip1 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
tinygo-bench-wasi-fast:
tinygo-test-wasip2-slow: $(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_FAST)
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \
TARGET=wasip2 \
TESTOPTS="-x -work" \
PACKAGES="$(TEST_PACKAGES_SLOW)" \
gotestsum --raw-command -- ./tools/tgtestjson.sh
tinygo-test-wasip2-sum-fast:
TINYGO=$(TINYGO) \
TARGET=wasip2 \
TESTOPTS="-x -work" \
PACKAGES="$(TEST_PACKAGES_FAST)" \
gotestsum --raw-command -- ./tools/tgtestjson.sh
tinygo-bench-wasip1:
$(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
tinygo-bench-wasip1-fast:
$(TINYGO) test -target wasip1 -bench . $(TEST_PACKAGES_FAST)
tinygo-bench-wasip2:
$(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW)
tinygo-bench-wasip2-fast:
$(TINYGO) test -target wasip2 -bench . $(TEST_PACKAGES_FAST)
# Test external packages in a large corpus. # Test external packages in a large corpus.
test-corpus: test-corpus:
@@ -476,7 +409,7 @@ test-corpus:
test-corpus-fast: test-corpus-fast:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml
test-corpus-wasi: wasi-libc test-corpus-wasi: wasi-libc
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1 CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasi
tinygo-baremetal: tinygo-baremetal:
# Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest. # Regression tests that run on a baremetal target and don't fit in either main_test.go or smoketest.
@@ -520,26 +453,16 @@ smoketest:
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/rtcinterrupt $(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/machinetest
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick $(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test $(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/time-offset
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-mouse
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard $(TINYGO) build -size short -o test.hex -target=wioterminal examples/hid-keyboard
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/i2c-target
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/watchdog
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/device-id
@$(MD5SUM) test.hex
# test simulated boards on play.tinygo.org # test simulated boards on play.tinygo.org
ifneq ($(WASM), 0) ifneq ($(WASM), 0)
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1 $(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
@@ -554,9 +477,7 @@ ifneq ($(WASM), 0)
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1 $(TINYGO) build -size short -o test.wasm -tags=circuitplay_bluefruit examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/machinetest $(TINYGO) build -size short -o test.wasm -tags=mch2022 examples/serial
@$(MD5SUM) test.wasm
$(TINYGO) build -size short -o test.wasm -tags=gopher_badge examples/blinky1
@$(MD5SUM) test.wasm @$(MD5SUM) test.wasm
endif endif
# test all targets/boards # test all targets/boards
@@ -586,16 +507,12 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2 $(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=bluemicro840 examples/blinky2
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=gemma-m0 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1 $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1 $(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
@@ -626,14 +543,12 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1 $(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pinetime examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1 $(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10059-s140v7 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1 $(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
@@ -652,7 +567,7 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy examples/machinetest $(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=teensy41 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=teensy41 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -684,31 +599,19 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=qtpy-rp2040 examples/echo $(TINYGO) build -size short -o test.hex -target=qtpy-rp2040 examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=kb2040 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=macropad-rp2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=macropad-rp2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=badger2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=badger2040-w examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=tufty2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=thingplus-rp2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-rp2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=xiao-rp2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=waveshare-rp2040-zero examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=challenger-rp2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=challenger-rp2040 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=trinkey-qt2040 examples/temp $(TINYGO) build -size short -o test.hex -target=trinkey-qt2040 examples/adc_rp2040
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=gopher-badge examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=ae-rp2040 examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=thumby examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
# test pwm # test pwm
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm $(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
@@ -724,8 +627,6 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi $(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/usb-midi
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nrf52840-s140v6-uf2-generic examples/machinetest
@$(MD5SUM) test.hex
ifneq ($(STM32), 0) ifneq ($(STM32), 0)
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1 $(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -741,8 +642,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l476rg examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1
@@ -761,17 +660,12 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=swan examples/blinky1 $(TINYGO) build -size short -o test.hex -target=swan examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=mksnanov3 examples/blinky1
@$(MD5SUM) test.hex
endif endif
$(TINYGO) build -size short -o test.hex -target=atmega328pb examples/blinkm ifneq ($(AVR), 0)
@$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/machinetest
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-leonardo examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm $(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
@@ -782,33 +676,30 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=attiny1616 examples/empty
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1 $(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
endif
ifneq ($(XTENSA), 0) ifneq ($(XTENSA), 0)
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1 $(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1 $(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/machinetest $(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest $(TINYGO) build -size short -o test.bin -target m5stack examples/serial
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest $(TINYGO) build -size short -o test.bin -target mch2022 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
endif endif
$(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest $(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest $(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/serial
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest $(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/serial
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/serial
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1 $(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
@@ -817,7 +708,6 @@ endif
ifneq ($(WASM), 0) ifneq ($(WASM), 0)
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main $(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
$(TINYGO) build -size short -o wasm.wasm -target=wasm-unknown examples/hello-wasm-unknown
endif endif
# test various compiler flags # test various compiler flags
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
@@ -826,16 +716,12 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo $(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo
@$(MD5SUM) test.hex
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial $(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
@$(MD5SUM) test.nro @$(MD5SUM) test.nro
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go $(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
GOOS=linux GOARCH=mips $(TINYGO) build -size short -o test.elf ./testdata/cgo
GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
GOOS=windows GOARCH=arm64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo
GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo
ifneq ($(OS),Windows_NT) ifneq ($(OS),Windows_NT)
@@ -854,7 +740,6 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS @mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
@mkdir -p build/release/tinygo/lib/macos-minimal-sdk @mkdir -p build/release/tinygo/lib/macos-minimal-sdk
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@mkdir -p build/release/tinygo/lib/musl/arch @mkdir -p build/release/tinygo/lib/musl/arch
@mkdir -p build/release/tinygo/lib/musl/crt @mkdir -p build/release/tinygo/lib/musl/crt
@@ -862,10 +747,10 @@ build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN
@mkdir -p build/release/tinygo/lib/nrfx @mkdir -p build/release/tinygo/lib/nrfx
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc @mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm @mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers @mkdir -p build/release/tinygo/lib/wasi-libc
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0
@mkdir -p build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus
@mkdir -p build/release/tinygo/lib/wasi-cli/ @mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4
@echo copying source files @echo copying source files
@cp -p build/tinygo$(EXE) build/release/tinygo/bin @cp -p build/tinygo$(EXE) build/release/tinygo/bin
ifneq ($(USE_SYSTEM_BINARYEN),1) ifneq ($(USE_SYSTEM_BINARYEN),1)
@@ -879,7 +764,6 @@ endif
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/mips build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch @cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt @cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl @cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
@@ -889,13 +773,9 @@ endif
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/locale build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/linux build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src @cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
@@ -905,7 +785,6 @@ endif
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt @cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common @cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
@cp -rp lib/mingw-w64/mingw-w64-crt/stdio/ucrt_* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/stdio
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers @cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults @cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx @cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
@@ -917,20 +796,17 @@ endif
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm @cp -rp lib/picolibc/newlib/libm/math build/release/tinygo/lib/picolibc/newlib/libm
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib @cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
@cp -rp lib/wasi-libc/libc-bottom-half/headers/public build/release/tinygo/lib/wasi-libc/libc-bottom-half/headers @cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp lib/wasi-libc/libc-top-half/musl/arch/generic build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch
@cp -rp lib/wasi-libc/libc-top-half/musl/arch/wasm32 build/release/tinygo/lib/wasi-libc/libc-top-half/musl/arch
@cp -rp lib/wasi-libc/libc-top-half/musl/src/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/internal build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/math build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/src/string build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/musl/include build/release/tinygo/lib/wasi-libc/libc-top-half/musl
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
@cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit
@cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
@cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
@cp -rp src build/release/tinygo/src @cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets @cp -rp targets build/release/tinygo/targets
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc
./build/release/tinygo/bin/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc
./build/release/tinygo/bin/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc
release: release:
tar -czf build/release.tar.gz -C build/release tinygo tar -czf build/release.tar.gz -C build/release tinygo
@@ -941,40 +817,9 @@ deb:
@mkdir -p build/release-deb/usr/local/lib @mkdir -p build/release-deb/usr/local/lib
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb fpm -f -s dir -t deb -n tinygo -a $(DEB_ARCH) -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
ifneq ($(RELEASEONLY), 1) ifneq ($(RELEASEONLY), 1)
release: build/release release: build/release
deb: build/release deb: build/release
endif endif
.PHONY: tools
tools:
cd internal/tools && go generate -tags tools ./
.PHONY: lint
lint: tools ## Lint source tree
revive -version
# TODO: lint more directories!
# revive.toml isn't flexible enough to filter out just one kind of error from a checker, so do it with grep here.
# Can't use grep with friendly formatter. Plain output isn't too bad, though.
# Use 'grep .' to get rid of stray blank line
revive -config revive.toml compiler/... src/{os,reflect}/*.go | grep -v "should have comment or be unexported" | grep '.' | awk '{print}; END {exit NR>0}'
SPELLDIRSCMD=find . -depth 1 -type d | egrep -wv '.git|lib|llvm|src'; find src -depth 1 | egrep -wv 'device|internal|net|vendor'; find src/internal -depth 1 -type d | egrep -wv src/internal/wasi
.PHONY: spell
spell: tools ## Spellcheck source tree
misspell -error --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md
.PHONY: spellfix
spellfix: tools ## Same as spell, but fixes what it finds
misspell -w --dict misspell.csv -i 'ackward,devided,extint,rela' $$( $(SPELLDIRSCMD) ) *.go *.md
# https://www.client9.com/self-documenting-makefiles/
.PHONY: help
help:
@awk -F ':|##' '/^[^\t].+?:.*?##/ {\
gsub(/\$$\(LLVM_BUILDDIR\)/, "$(LLVM_BUILDDIR)"); \
printf "\033[36m%-30s\033[0m %s\n", $$1, $$NF \
}' $(MAKEFILE_LIST)
#.DEFAULT_GOAL=help
+99 -52
View File
@@ -1,13 +1,11 @@
# TinyGo - Go compiler for small places # 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. 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.
## Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED: Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go ```go
@@ -37,62 +35,111 @@ The above program can be compiled and run without modification on an Arduino Uno
tinygo flash -target arduino examples/blinky1 tinygo flash -target arduino 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:wasm-module yourmodulename
//export add
func add(x, y uint32) uint32 {
return x + y
}
// main is required for the `wasip1` target, even if it isn't used.
func main() {}
```
This compiles the above TinyGo program for use on any WASI runtime:
```shell
tinygo build -o main.wasm -target=wasip1 main.go
```
## Installation ## 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. 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 and Linux.
You can compile TinyGo programs for over 94 different microcontroller boards. The following 91 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 M4](https://www.adafruit.com/product/3857)
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
* [Adafruit MacroPad RP2040](https://www.adafruit.com/product/5100)
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
* [Adafruit QT Py RP2040](https://www.adafruit.com/product/4900)
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
* [Adafruit Trinkey QT2040](https://adafruit.com/product/5056)
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
* [Arduino 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)
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
### WebAssembly For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
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/
## Currently supported features: ## Currently supported features:
+4 -32
View File
@@ -12,11 +12,10 @@ import (
"path/filepath" "path/filepath"
"time" "time"
wasm "github.com/aykevl/go-wasm"
"github.com/blakesmith/ar" "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: // given as a parameter. It is equivalent to the following command:
// //
// ar -rcs <archivePath> <objs...> // ar -rcs <archivePath> <objs...>
@@ -75,35 +74,8 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int fileIndex int
}{symbol.Name, i}) }{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 { } 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 // Close file, to avoid issues with too many open files (especially on
@@ -150,7 +122,7 @@ func makeArchive(arfile *os.File, objs []string) error {
} }
// Keep track of the start of the symbol table. // 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 { if err != nil {
return err return err
} }
@@ -172,7 +144,7 @@ func makeArchive(arfile *os.File, objs []string) error {
// Store the start index, for when we'll update the symbol table with // Store the start index, for when we'll update the symbol table with
// the correct file start indices. // the correct file start indices.
offset, err := arfile.Seek(0, io.SeekCurrent) offset, err := arfile.Seek(0, os.SEEK_CUR)
if err != nil { if err != nil {
return err return err
} }
+219 -252
View File
@@ -42,8 +42,8 @@ type BuildResult struct {
// information. Used for GDB for example. // information. Used for GDB for example.
Executable string Executable string
// A path to the output binary. It is stored in the tmpdir directory of the // A path to the output binary. It will be removed after Build returns, so
// Build function, so if it should be kept it must be copied or moved away. // if it should be kept it must be copied or moved away.
// It is often the same as Executable, but differs if the output format is // It is often the same as Executable, but differs if the output format is
// .hex for example (instead of the usual ELF). // .hex for example (instead of the usual ELF).
Binary string Binary string
@@ -83,7 +83,8 @@ type packageAction struct {
FileHashes map[string]string // hash of every file that's part of the package 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 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 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) UndefinedGlobals []string // globals that are left as external globals (no initializer)
} }
@@ -93,16 +94,23 @@ type packageAction struct {
// //
// The error value may be of type *MultiError. Callers will likely want to check // The error value may be of type *MultiError. Callers will likely want to check
// for this case and print such errors individually. // for this case and print such errors individually.
func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildResult, error) { func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
// Read the build ID of the tinygo binary. // Read the build ID of the tinygo binary.
// Used as a cache key for package builds. // Used as a cache key for package builds.
compilerBuildID, err := ReadBuildID() compilerBuildID, err := ReadBuildID()
if err != nil { if err != nil {
return BuildResult{}, err return err
} }
// Create a temporary directory for intermediary files.
dir, err := os.MkdirTemp("", "tinygo")
if err != nil {
return err
}
if config.Options.Work { if config.Options.Work {
fmt.Printf("WORK=%s\n", tmpdir) fmt.Printf("WORK=%s\n", dir)
} else {
defer os.RemoveAll(dir)
} }
// Look up the build cache directory, which is used to speed up incremental // Look up the build cache directory, which is used to speed up incremental
@@ -111,31 +119,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if cacheDir == "off" { if cacheDir == "off" {
// Use temporary build directory instead, effectively disabling the // Use temporary build directory instead, effectively disabling the
// build cache. // build cache.
cacheDir = tmpdir cacheDir = dir
}
// 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. // Check for a libc dependency.
@@ -145,70 +129,58 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var libcDependencies []*compileJob var libcDependencies []*compileJob
switch config.Target.Libc { switch config.Target.Libc {
case "darwin-libSystem": case "darwin-libSystem":
job := makeDarwinLibSystemJob(config, tmpdir) job := makeDarwinLibSystemJob(config, dir)
libcDependencies = append(libcDependencies, job) libcDependencies = append(libcDependencies, job)
case "musl": case "musl":
job, unlock, err := libMusl.load(config, tmpdir) job, unlock, err := Musl.load(config, dir)
if err != nil { if err != nil {
return BuildResult{}, err return err
} }
defer unlock() defer unlock()
libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o"))) libcDependencies = append(libcDependencies, dummyCompileJob(filepath.Join(filepath.Dir(job.result), "crt1.o")))
libcDependencies = append(libcDependencies, job) libcDependencies = append(libcDependencies, job)
case "picolibc": case "picolibc":
libcJob, unlock, err := libPicolibc.load(config, tmpdir) libcJob, unlock, err := Picolibc.load(config, dir)
if err != nil { if err != nil {
return BuildResult{}, err return err
} }
defer unlock() defer unlock()
libcDependencies = append(libcDependencies, libcJob) libcDependencies = append(libcDependencies, libcJob)
case "wasi-libc": case "wasi-libc":
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a") path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { 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`?") return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
} }
libcDependencies = append(libcDependencies, dummyCompileJob(path)) libcDependencies = append(libcDependencies, dummyCompileJob(path))
case "wasmbuiltins":
libcJob, unlock, err := libWasmBuiltins.load(config, tmpdir)
if err != nil {
return BuildResult{}, err
}
defer unlock()
libcDependencies = append(libcDependencies, libcJob)
case "mingw-w64": case "mingw-w64":
job, unlock, err := libMinGW.load(config, tmpdir) _, unlock, err := MinGW.load(config, dir)
if err != nil { if err != nil {
return BuildResult{}, err return err
} }
defer unlock() unlock()
libcDependencies = append(libcDependencies, job) libcDependencies = append(libcDependencies, makeMinGWExtraLibs(dir)...)
libcDependencies = append(libcDependencies, makeMinGWExtraLibs(tmpdir, config.GOARCH())...)
case "": case "":
// no library specified, so nothing to do // no library specified, so nothing to do
default: default:
return BuildResult{}, fmt.Errorf("unknown libc: %s", config.Target.Libc) return fmt.Errorf("unknown libc: %s", config.Target.Libc)
} }
optLevel, speedLevel, sizeLevel := config.OptLevel() optLevel, sizeLevel, _ := config.OptLevels()
compilerConfig := &compiler.Config{ compilerConfig := &compiler.Config{
Triple: config.Triple(), Triple: config.Triple(),
CPU: config.CPU(), CPU: config.CPU(),
Features: config.Features(), Features: config.Features(),
ABI: config.ABI(),
GOOS: config.GOOS(), GOOS: config.GOOS(),
GOARCH: config.GOARCH(), GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(), CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(), RelocationModel: config.RelocationModel(),
SizeLevel: sizeLevel, SizeLevel: sizeLevel,
TinyGoVersion: goenv.Version(),
Scheduler: config.Scheduler(), Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(), AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(), DefaultStackSize: config.Target.DefaultStackSize,
MaxStackAlloc: config.MaxStackAlloc(),
NeedsStackObjects: config.NeedsStackObjects(), NeedsStackObjects: config.NeedsStackObjects(),
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed Debug: true,
PanicStrategy: config.PanicStrategy(),
} }
// Load the target machine, which is the LLVM object that contains all // Load the target machine, which is the LLVM object that contains all
@@ -216,29 +188,20 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// address spaces, etc). // address spaces, etc).
machine, err := compiler.NewTargetMachine(compilerConfig) machine, err := compiler.NewTargetMachine(compilerConfig)
if err != nil { if err != nil {
return BuildResult{}, err return err
} }
defer machine.Dispose() defer machine.Dispose()
// Load entire program AST into memory. // 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), Sizes: compiler.Sizes(machine),
}) })
if err != nil { if err != nil {
return BuildResult{}, err return err
}
result := BuildResult{
ModuleRoot: lprogram.MainPkg().Module.Dir,
MainDir: lprogram.MainPkg().Dir,
ImportPath: lprogram.MainPkg().ImportPath,
}
if result.ModuleRoot == "" {
// If there is no module root, just the regular root.
result.ModuleRoot = lprogram.MainPkg().Root
} }
err = lprogram.Parse() err = lprogram.Parse()
if err != nil { if err != nil {
return result, err return err
} }
// Create the *ssa.Program. This does not yet build the entire SSA of the // Create the *ssa.Program. This does not yet build the entire SSA of the
@@ -250,12 +213,26 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var packageJobs []*compileJob var packageJobs []*compileJob
packageActionIDJobs := make(map[string]*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 var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string var undefinedGlobals []string
for name := range globalValues[pkg.Pkg.Path()] { for name := range config.Options.GlobalValues[pkg.Pkg.Path()] {
undefinedGlobals = append(undefinedGlobals, name) undefinedGlobals = append(undefinedGlobals, name)
} }
sort.Strings(undefinedGlobals) sort.Strings(undefinedGlobals)
@@ -293,7 +270,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
} }
} }
job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), tmpdir, compilerConfig) job.result, err = createEmbedObjectFile(string(data), hexSum, name, pkg.OriginalDir(), dir, compilerConfig)
return err return err
}, },
} }
@@ -307,7 +284,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
for _, imported := range pkg.Pkg.Imports() { for _, imported := range pkg.Pkg.Imports() {
job, ok := packageActionIDJobs[imported.Path()] job, ok := packageActionIDJobs[imported.Path()]
if !ok { if !ok {
return result, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) return fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path())
} }
importedPackages = append(importedPackages, job) importedPackages = append(importedPackages, job)
actionIDDependencies = append(actionIDDependencies, job) actionIDDependencies = append(actionIDDependencies, job)
@@ -325,6 +302,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
actionID := packageAction{ actionID := packageAction{
ImportPath: pkg.ImportPath, ImportPath: pkg.ImportPath,
CompilerBuildID: string(compilerBuildID), CompilerBuildID: string(compilerBuildID),
TinyGoVersion: goenv.Version,
LLVMVersion: llvm.Version, LLVMVersion: llvm.Version,
Config: compilerConfig, Config: compilerConfig,
CFlags: pkg.CFlags, CFlags: pkg.CFlags,
@@ -332,6 +310,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
EmbeddedFiles: make(map[string]string, len(allFiles)), EmbeddedFiles: make(map[string]string, len(allFiles)),
Imports: make(map[string]string, len(pkg.Pkg.Imports())), Imports: make(map[string]string, len(pkg.Pkg.Imports())),
OptLevel: optLevel, OptLevel: optLevel,
SizeLevel: sizeLevel,
UndefinedGlobals: undefinedGlobals, UndefinedGlobals: undefinedGlobals,
} }
for filePath, hash := range pkg.FileHashes { for filePath, hash := range pkg.FileHashes {
@@ -376,7 +355,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
defer mod.Context().Dispose() defer mod.Context().Dispose()
defer mod.Dispose() defer mod.Dispose()
if errs != nil { if errs != nil {
return newMultiError(errs, pkg.ImportPath) return newMultiError(errs)
} }
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification error after compiling package " + pkg.ImportPath) return errors.New("verification error after compiling package " + pkg.ImportPath)
@@ -391,7 +370,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Packages are compiled independently anyway. // Packages are compiled independently anyway.
for _, cgoHeader := range pkg.CGoHeaders { for _, cgoHeader := range pkg.CGoHeaders {
// Store the header text in a temporary file. // Store the header text in a temporary file.
f, err := os.CreateTemp(tmpdir, "cgosnippet-*.c") f, err := os.CreateTemp(dir, "cgosnippet-*.c")
if err != nil { if err != nil {
return err return err
} }
@@ -439,7 +418,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return errors.New("global not found: " + globalName) return errors.New("global not found: " + globalName)
} }
name := global.Name() name := global.Name()
newGlobal := llvm.AddGlobal(mod, global.GlobalValueType(), name+".tmp") newGlobal := llvm.AddGlobal(mod, global.Type().ElementType(), name+".tmp")
global.ReplaceAllUsesWith(newGlobal) global.ReplaceAllUsesWith(newGlobal)
global.EraseFromParentAsGlobal() global.EraseFromParentAsGlobal()
newGlobal.SetName(name) newGlobal.SetName(name)
@@ -538,13 +517,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
irbuilder := mod.Context().NewBuilder() irbuilder := mod.Context().NewBuilder()
defer irbuilder.Dispose() defer irbuilder.Dispose()
irbuilder.SetInsertPointAtEnd(block) irbuilder.SetInsertPointAtEnd(block)
ptrType := llvm.PointerType(mod.Context().Int8Type(), 0) i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init") pkgInit := mod.NamedFunction(pkg.Pkg.Path() + ".init")
if pkgInit.IsNil() { if pkgInit.IsNil() {
panic("init not found for " + pkg.Pkg.Path()) panic("init not found for " + pkg.Pkg.Path())
} }
irbuilder.CreateCall(pkgInit.GlobalValueType(), pkgInit, []llvm.Value{llvm.Undef(ptrType)}, "") irbuilder.CreateCall(pkgInit, []llvm.Value{llvm.Undef(i8ptrType)}, "")
} }
irbuilder.CreateRetVoid() irbuilder.CreateRetVoid()
@@ -579,7 +558,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run all optimization passes, which are much more effective now // Run all optimization passes, which are much more effective now
// that the optimizer can see the whole program at once. // that the optimizer can see the whole program at once.
err := optimizeProgram(mod, config, globalValues) err := optimizeProgram(mod, config)
if err != nil { if err != nil {
return err return err
} }
@@ -600,24 +579,29 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Run jobs to produce the LLVM module. // Run jobs to produce the LLVM module.
err := runJobs(programJob, config.Options.Semaphore) err := runJobs(programJob, config.Options.Semaphore)
if err != nil { if err != nil {
return result, err return err
} }
// Generate output. // Generate output.
switch outext { switch outext {
case ".o": case ".o":
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil { if err != nil {
return result, err return err
} }
defer llvmBuf.Dispose() defer llvmBuf.Dispose()
return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) return os.WriteFile(outpath, llvmBuf.Bytes(), 0666)
case ".bc": case ".bc":
buf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) var buf llvm.MemoryBuffer
if config.UseThinLTO() {
buf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
buf = llvm.WriteBitcodeToMemoryBuffer(mod)
}
defer buf.Dispose() defer buf.Dispose()
return result, os.WriteFile(outpath, buf.Bytes(), 0666) return os.WriteFile(outpath, buf.Bytes(), 0666)
case ".ll": case ".ll":
data := []byte(mod.String()) data := []byte(mod.String())
return result, os.WriteFile(outpath, data, 0666) return os.WriteFile(outpath, data, 0666)
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -628,13 +612,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// run all jobs in parallel as far as possible. // run all jobs in parallel as far as possible.
// Add job to write the output object file. // Add job to write the output object file.
objfile := filepath.Join(tmpdir, "main.o") objfile := filepath.Join(dir, "main.o")
outputObjectFileJob := &compileJob{ outputObjectFileJob := &compileJob{
description: "generate output file", description: "generate output file",
dependencies: []*compileJob{programJob}, dependencies: []*compileJob{programJob},
result: objfile, result: objfile,
run: func(*compileJob) error { run: func(*compileJob) error {
llvmBuf := llvm.WriteThinLTOBitcodeToMemoryBuffer(mod) var llvmBuf llvm.MemoryBuffer
if config.UseThinLTO() {
llvmBuf = llvm.WriteThinLTOBitcodeToMemoryBuffer(mod)
} else {
var err error
llvmBuf, err = machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
if err != nil {
return err
}
}
defer llvmBuf.Dispose() defer llvmBuf.Dispose()
return os.WriteFile(objfile, llvmBuf.Bytes(), 0666) return os.WriteFile(objfile, llvmBuf.Bytes(), 0666)
}, },
@@ -642,19 +635,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Prepare link command. // Prepare link command.
linkerDependencies := []*compileJob{outputObjectFileJob} linkerDependencies := []*compileJob{outputObjectFileJob}
result.Executable = filepath.Join(tmpdir, "main") executable := filepath.Join(dir, "main")
if config.GOOS() == "windows" { if config.GOOS() == "windows" {
result.Executable += ".exe" executable += ".exe"
} }
result.Binary = result.Executable // final file tmppath := executable // final file
ldflags := append(config.LDFlags(), "-o", result.Executable) ldflags := append(config.LDFlags(), "-o", executable)
// Add compiler-rt dependency if needed. Usually this is a simple load from // Add compiler-rt dependency if needed. Usually this is a simple load from
// a cache. // a cache.
if config.Target.RTLib == "compiler-rt" { if config.Target.RTLib == "compiler-rt" {
job, unlock, err := libCompilerRT.load(config, tmpdir) job, unlock, err := CompilerRT.load(config, dir)
if err != nil { if err != nil {
return result, err return err
} }
defer unlock() defer unlock()
linkerDependencies = append(linkerDependencies, job) linkerDependencies = append(linkerDependencies, job)
@@ -668,7 +661,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{ job := &compileJob{
description: "compile extra file " + path, description: "compile extra file " + path,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, config.CFlags(false), config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, config.CFlags(), config.UseThinLTO(), config.Options.PrintCommands)
job.result = result job.result = result
return err return err
}, },
@@ -686,7 +679,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
job := &compileJob{ job := &compileJob{
description: "compile CGo file " + abspath, description: "compile CGo file " + abspath,
run: func(job *compileJob) error { run: func(job *compileJob) error {
result, err := compileAndCacheCFile(abspath, tmpdir, pkg.CFlags, config.Options.PrintCommands) result, err := compileAndCacheCFile(abspath, dir, pkg.CFlags, config.UseThinLTO(), config.Options.PrintCommands)
job.result = result job.result = result
return err return err
}, },
@@ -707,18 +700,21 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Add embedded files. // Add embedded files.
linkerDependencies = append(linkerDependencies, embedFileObjects...) linkerDependencies = append(linkerDependencies, embedFileObjects...)
// Determine whether the compilation configuration would result in debug
// (DWARF) information in the object files.
var hasDebug = true
if config.GOOS() == "darwin" {
// Debug information isn't stored in the binary itself on MacOS but
// is left in the object files by default. The binary does store the
// path to these object files though.
hasDebug = false
}
// Strip debug information with -no-debug. // Strip debug information with -no-debug.
if hasDebug && !config.Debug() { if !config.Debug() {
for _, tag := range config.BuildTags() {
if tag == "baremetal" {
// Don't use -no-debug on baremetal targets. It makes no sense:
// the debug information isn't flashed to the device anyway.
return fmt.Errorf("stripping debug information is unnecessary for baremetal targets")
}
}
if config.GOOS() == "darwin" {
// Debug information isn't stored in the binary itself on MacOS but
// is left in the object files by default. The binary does store the
// path to these object files though.
return errors.New("cannot remove debug information: MacOS doesn't store debug info in the executable by default")
}
if config.Target.Linker == "wasm-ld" { if config.Target.Linker == "wasm-ld" {
// Don't just strip debug information, also compress relocations // Don't just strip debug information, also compress relocations
// while we're at it. Relocations can only be compressed when debug // while we're at it. Relocations can only be compressed when debug
@@ -729,7 +725,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
ldflags = append(ldflags, "--strip-debug") ldflags = append(ldflags, "--strip-debug")
} else { } else {
// Other linkers may have different flags. // Other linkers may have different flags.
return result, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) return errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker)
} }
} }
@@ -745,42 +741,43 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
} }
ldflags = append(ldflags, dependency.result) 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),
"--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),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
"-mllvm", "-code-model="+config.CodeModel())
}
if sizeLevel >= 2 {
// Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 19.
ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
if config.Options.PrintCommands != nil { if config.Options.PrintCommands != nil {
config.Options.PrintCommands(config.Target.Linker, ldflags...) config.Options.PrintCommands(config.Target.Linker, ldflags...)
} }
if config.UseThinLTO() {
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-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(optLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
} else {
// Options for the ELF linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(optLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
)
}
if config.CodeModel() != "default" {
ldflags = append(ldflags,
"-mllvm", "-code-model="+config.CodeModel())
}
if sizeLevel >= 2 {
// Workaround with roughly the same effect as
// https://reviews.llvm.org/D119342.
// Can hopefully be removed in LLVM 15.
ldflags = append(ldflags,
"-mllvm", "--rotation-max-header-size=0")
}
}
err = link(config.Target.Linker, ldflags...) err = link(config.Target.Linker, ldflags...)
if err != nil { if err != nil {
return err return &commandError{"failed to link", executable, err}
} }
var calculatedStacks []string var calculatedStacks []string
@@ -789,7 +786,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Try to determine stack sizes at compile time. // Try to determine stack sizes at compile time.
// Don't do this by default as it usually doesn't work on // Don't do this by default as it usually doesn't work on
// unsupported architectures. // unsupported architectures.
calculatedStacks, stackSizes, err = determineStackSizes(mod, result.Executable) calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
if err != nil { if err != nil {
return err return err
} }
@@ -799,50 +796,41 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if config.AutomaticStackSize() { if config.AutomaticStackSize() {
// Modify the .tinygo_stacksizes section that contains a stack size // Modify the .tinygo_stacksizes section that contains a stack size
// for each goroutine. // for each goroutine.
err = modifyStackSizes(result.Executable, stackSizeLoads, stackSizes) err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
if err != nil { if err != nil {
return fmt.Errorf("could not modify stack sizes: %w", err) return fmt.Errorf("could not modify stack sizes: %w", err)
} }
} }
if config.RP2040BootPatch() { if config.RP2040BootPatch() {
// Patch the second stage bootloader CRC into the .boot2 section // Patch the second stage bootloader CRC into the .boot2 section
err = patchRP2040BootCRC(result.Executable) err = patchRP2040BootCRC(executable)
if err != nil { if err != nil {
return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err) return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err)
} }
} }
// Run wasm-opt for wasm binaries // Run wasm-opt if necessary.
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" { if config.Scheduler() == "asyncify" {
optLevel, _, _ := config.OptLevel() var optLevel, shrinkLevel int
opt := "-" + optLevel switch config.Options.Opt {
case "none", "0":
var args []string case "1":
optLevel = 1
if config.Scheduler() == "asyncify" { case "2":
args = append(args, "--asyncify") optLevel = 2
case "s":
optLevel = 2
shrinkLevel = 1
case "z":
optLevel = 2
shrinkLevel = 2
default:
return fmt.Errorf("unknown opt level: %q", config.Options.Opt)
} }
cmd := exec.Command(goenv.Get("WASMOPT"), "--asyncify", "-g",
exeunopt := result.Executable "--optimize-level", strconv.Itoa(optLevel),
"--shrink-level", strconv.Itoa(shrinkLevel),
if config.Options.Work { executable, "--output", executable)
// Keep the work direction around => don't overwrite the .wasm binary with the optimized version
exeunopt += ".pre-wasm-opt"
os.Rename(result.Executable, exeunopt)
}
args = append(args,
opt,
"-g",
exeunopt,
"--output", result.Executable,
)
wasmopt := goenv.Get("WASMOPT")
if config.Options.PrintCommands != nil {
config.Options.PrintCommands(wasmopt, args...)
}
cmd := exec.Command(wasmopt, args...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -852,69 +840,13 @@ 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
args := []string{
"component",
"embed",
"-w", witWorld,
witPackage,
result.Executable,
"-o", result.Executable,
}
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 failed: %w", err)
}
// wasm-tools component new embedded.wasm -o component.wasm
args = []string{
"component",
"new",
result.Executable,
"-o", result.Executable,
}
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 failed: %w", err)
}
}
// Print code size if requested. // Print code size if requested.
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" { if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
packagePathMap := make(map[string]string, len(lprogram.Packages)) packagePathMap := make(map[string]string, len(lprogram.Packages))
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
} }
sizes, err := loadProgramSize(result.Executable, packagePathMap) sizes, err := loadProgramSize(executable, packagePathMap)
if err != nil { if err != nil {
return err return err
} }
@@ -950,7 +882,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// is simpler and cannot be parallelized. // is simpler and cannot be parallelized.
err = runJobs(linkJob, config.Options.Semaphore) err = runJobs(linkJob, config.Options.Semaphore)
if err != nil { if err != nil {
return result, err return err
} }
// Get an Intel .hex file or .bin file from the .elf file. // Get an Intel .hex file or .bin file from the .elf file.
@@ -961,38 +893,56 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
case "hex", "bin": case "hex", "bin":
// Extract raw binary, either encoding it as a hex file or as a raw // Extract raw binary, either encoding it as a hex file or as a raw
// firmware file. // firmware file.
result.Binary = filepath.Join(tmpdir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
err := objcopy(result.Executable, result.Binary, outputBinaryFormat) err := objcopy(executable, tmppath, outputBinaryFormat)
if err != nil { if err != nil {
return result, err return err
} }
case "uf2": case "uf2":
// Get UF2 from the .elf file. // Get UF2 from the .elf file.
result.Binary = filepath.Join(tmpdir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
err := convertELFFileToUF2File(result.Executable, result.Binary, config.Target.UF2FamilyID) err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
if err != nil { if err != nil {
return result, err return err
} }
case "esp32", "esp32-img", "esp32c3", "esp8266": case "esp32", "esp32-img", "esp32c3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM // Special format for the ESP family of chips (parsed by the ROM
// bootloader). // bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext) tmppath = filepath.Join(dir, "main"+outext)
err := makeESPFirmareImage(result.Executable, result.Binary, outputBinaryFormat) err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
if err != nil { if err != nil {
return result, err return err
} }
case "nrf-dfu": case "nrf-dfu":
// special format for nrfutil for Nordic chips // special format for nrfutil for Nordic chips
result.Binary = filepath.Join(tmpdir, "main"+outext) tmphexpath := filepath.Join(dir, "main.hex")
err = makeDFUFirmwareImage(config.Options, result.Executable, result.Binary) err := objcopy(executable, tmphexpath, "hex")
if err != nil { if err != nil {
return result, err return err
}
tmppath = filepath.Join(dir, "main"+outext)
err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath)
if err != nil {
return err
} }
default: default:
return result, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
} }
return result, nil // If there's a module root, use that.
moduleroot := lprogram.MainPkg().Module.Dir
if moduleroot == "" {
// if not, just the regular root
moduleroot = lprogram.MainPkg().Root
}
return action(BuildResult{
Executable: executable,
Binary: tmppath,
MainDir: lprogram.MainPkg().Dir,
ModuleRoot: moduleroot,
ImportPath: lprogram.MainPkg().ImportPath,
})
} }
// createEmbedObjectFile creates a new object file with the given contents, for // createEmbedObjectFile creates a new object file with the given contents, for
@@ -1104,7 +1054,7 @@ func createEmbedObjectFile(data, hexSum, sourceFile, sourceDir, tmpdir string, c
// optimizeProgram runs a series of optimizations and transformations that are // optimizeProgram runs a series of optimizations and transformations that are
// needed to convert a program to its final form. Some transformations are not // needed to convert a program to its final form. Some transformations are not
// optional and must be run as the compiler expects them to run. // optional and must be run as the compiler expects them to run.
func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues map[string]map[string]string) error { func optimizeProgram(mod llvm.Module, config *compileopts.Config) error {
err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA()) err := interp.Run(mod, config.Options.InterpTimeout, config.DumpSSA())
if err != nil { if err != nil {
return err return err
@@ -1122,17 +1072,34 @@ func optimizeProgram(mod llvm.Module, config *compileopts.Config, globalValues m
} }
} }
if config.GOOS() != "darwin" && !config.UseThinLTO() {
transform.ApplyFunctionSections(mod) // -ffunction-sections
}
// Insert values from -ldflags="-X ..." into the IR. // Insert values from -ldflags="-X ..." into the IR.
err = setGlobalValues(mod, globalValues) err = setGlobalValues(mod, config.Options.GlobalValues)
if err != nil { if err != nil {
return err return err
} }
// Run most of the whole-program optimizations (including the whole // Browsers cannot handle external functions that have type i64 because it
// O0/O1/O2/Os/Oz optimization pipeline). // cannot be represented exactly in JavaScript (JS only has doubles). To
errs := transform.Optimize(mod, config) // keep functions interoperable, pass int64 types as pointers to
// stack-allocated values.
// Use -wasm-abi=generic to disable this behaviour.
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 { if len(errs) > 0 {
return newMultiError(errs, "") return newMultiError(errs)
} }
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil { if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
return errors.New("verification failure after LLVM optimization passes") return errors.New("verification failure after LLVM optimization passes")
@@ -1170,7 +1137,7 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
// A strin is a {ptr, len} pair. We need these types to build the // A strin is a {ptr, len} pair. We need these types to build the
// initializer. // initializer.
initializerType := global.GlobalValueType() initializerType := global.Type().ElementType()
if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" { if initializerType.TypeKind() != llvm.StructTypeKind || initializerType.StructName() == "" {
return fmt.Errorf("%s: not a string", globalName) return fmt.Errorf("%s: not a string", globalName)
} }
@@ -1189,7 +1156,7 @@ func setGlobalValues(mod llvm.Module, globals map[string]map[string]string) erro
// Create the string value, which is a {ptr, len} pair. // Create the string value, which is a {ptr, len} pair.
zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false)
ptr := llvm.ConstGEP(bufInitializer.Type(), buf, []llvm.Value{zero, zero}) ptr := llvm.ConstGEP(buf, []llvm.Value{zero, zero})
if ptr.Type() != elementTypes[0] { if ptr.Type() != elementTypes[0] {
return fmt.Errorf("%s: not a string", globalName) return fmt.Errorf("%s: not a string", globalName)
} }
@@ -1259,7 +1226,7 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
} }
// Goroutines need to be started and finished and take up some stack space // 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. // tinygo_startTask.
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 { if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs) return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
+10 -21
View File
@@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -33,10 +34,8 @@ func TestClangAttributes(t *testing.T) {
"k210", "k210",
"nintendoswitch", "nintendoswitch",
"riscv-qemu", "riscv-qemu",
"wasip1", "wasi",
"wasip2",
"wasm", "wasm",
"wasm-unknown",
} }
if hasBuiltinTools { if hasBuiltinTools {
// hasBuiltinTools is set when TinyGo is statically linked with LLVM, // hasBuiltinTools is set when TinyGo is statically linked with LLVM,
@@ -53,30 +52,18 @@ func TestClangAttributes(t *testing.T) {
for _, options := range []*compileopts.Options{ for _, options := range []*compileopts.Options{
{GOOS: "linux", GOARCH: "386"}, {GOOS: "linux", GOARCH: "386"},
{GOOS: "linux", GOARCH: "amd64"}, {GOOS: "linux", GOARCH: "amd64"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "5"},
{GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "6"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"}, {GOOS: "linux", GOARCH: "arm", GOARM: "7"},
{GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"},
{GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"},
{GOOS: "linux", GOARCH: "arm64"}, {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: "amd64"},
{GOOS: "darwin", GOARCH: "arm64"}, {GOOS: "darwin", GOARCH: "arm64"},
{GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "arm64"},
{GOOS: "wasip1", GOARCH: "wasm"},
} { } {
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
if options.GOARCH == "arm" { if options.GOARCH == "arm" {
name += ",GOARM=" + options.GOARM name += ",GOARM=" + options.GOARM
} }
if options.GOARCH == "mips" || options.GOARCH == "mipsle" {
name += ",GOMIPS=" + options.GOMIPS
}
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
testClangAttributes(t, options) testClangAttributes(t, options)
}) })
@@ -85,6 +72,7 @@ func TestClangAttributes(t *testing.T) {
func testClangAttributes(t *testing.T, options *compileopts.Options) { func testClangAttributes(t *testing.T, options *compileopts.Options) {
testDir := t.TempDir() testDir := t.TempDir()
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
ctx := llvm.NewContext() ctx := llvm.NewContext()
defer ctx.Dispose() defer ctx.Dispose()
@@ -94,8 +82,9 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
t.Fatalf("could not load target: %s", err) t.Fatalf("could not load target: %s", err)
} }
config := compileopts.Config{ config := compileopts.Config{
Options: options, Options: options,
Target: target, Target: target,
ClangHeaders: clangHeaderPath,
} }
// Create a very simple C input file. // Create a very simple C input file.
@@ -107,7 +96,7 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// Compile this file using Clang. // Compile this file using Clang.
outpath := filepath.Join(testDir, "test.bc") 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" { if config.GOOS() == "darwin" {
// Silence some warnings that happen when testing GOOS=darwin on // Silence some warnings that happen when testing GOOS=darwin on
// something other than MacOS. // something other than MacOS.
+10 -34
View File
@@ -77,15 +77,6 @@ func ReadBuildID() ([]byte, error) {
} }
return raw[4:], nil 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: default:
// On other platforms (such as Windows) there isn't such a convenient // 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 // 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. // directly. Luckily the build ID is always at the start of the file.
// For details, see: // For details, see:
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go // https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
id, err := readRawGoBuildID(f, 4096) fileStart := make([]byte, 4096)
if len(id) != 0 || err != nil { _, err := io.ReadFull(f, fileStart)
return id, 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", 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) return nil, fmt.Errorf("could not find build ID in %s", 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
} }
+14 -58
View File
@@ -3,19 +3,19 @@ package builder
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
// These are the GENERIC_SOURCES according to CMakeList.txt except for // These are the GENERIC_SOURCES according to CMakeList.txt.
// divmodsi4.c and udivmodsi4.c.
var genericBuiltins = []string{ var genericBuiltins = []string{
"absvdi2.c", "absvdi2.c",
"absvsi2.c", "absvsi2.c",
"absvti2.c", "absvti2.c",
"adddf3.c", "adddf3.c",
"addsf3.c", "addsf3.c",
"addtf3.c",
"addvdi3.c", "addvdi3.c",
"addvsi3.c", "addvsi3.c",
"addvti3.c", "addvti3.c",
@@ -40,12 +40,12 @@ var genericBuiltins = []string{
"divdf3.c", "divdf3.c",
"divdi3.c", "divdi3.c",
"divmoddi4.c", "divmoddi4.c",
//"divmodsi4.c",
"divmodti4.c",
"divsc3.c", "divsc3.c",
"divsf3.c", "divsf3.c",
"divsi3.c", "divsi3.c",
"divtc3.c",
"divti3.c", "divti3.c",
"divtf3.c",
"extendsfdf2.c", "extendsfdf2.c",
"extendhfsf2.c", "extendhfsf2.c",
"ffsdi2.c", "ffsdi2.c",
@@ -91,6 +91,7 @@ var genericBuiltins = []string{
"mulsc3.c", "mulsc3.c",
"mulsf3.c", "mulsf3.c",
"multi3.c", "multi3.c",
"multf3.c",
"mulvdi3.c", "mulvdi3.c",
"mulvsi3.c", "mulvsi3.c",
"mulvti3.c", "mulvti3.c",
@@ -110,11 +111,13 @@ var genericBuiltins = []string{
"popcountti2.c", "popcountti2.c",
"powidf2.c", "powidf2.c",
"powisf2.c", "powisf2.c",
"powitf2.c",
"subdf3.c", "subdf3.c",
"subsf3.c", "subsf3.c",
"subvdi3.c", "subvdi3.c",
"subvsi3.c", "subvsi3.c",
"subvti3.c", "subvti3.c",
"subtf3.c",
"trampoline_setup.c", "trampoline_setup.c",
"truncdfhf2.c", "truncdfhf2.c",
"truncdfsf2.c", "truncdfsf2.c",
@@ -123,7 +126,6 @@ var genericBuiltins = []string{
"ucmpti2.c", "ucmpti2.c",
"udivdi3.c", "udivdi3.c",
"udivmoddi4.c", "udivmoddi4.c",
//"udivmodsi4.c",
"udivmodti4.c", "udivmodti4.c",
"udivsi3.c", "udivsi3.c",
"udivti3.c", "udivti3.c",
@@ -132,38 +134,6 @@ var genericBuiltins = []string{
"umodti3.c", "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{ var aeabiBuiltins = []string{
"arm/aeabi_cdcmp.S", "arm/aeabi_cdcmp.S",
"arm/aeabi_cdcmpeq_check_nan.c", "arm/aeabi_cdcmpeq_check_nan.c",
@@ -192,21 +162,12 @@ var aeabiBuiltins = []string{
"udivmodsi4.c", "udivmodsi4.c",
} }
var avrBuiltins = []string{ // CompilerRT is a library with symbols required by programs compiled with LLVM.
"avr/divmodhi4.S", // These symbols are for operations that cannot be emitted with a single
"avr/divmodqi4.S",
"avr/mulhi3.S",
"avr/mulqi3.S",
"avr/udivmodhi4.S",
"avr/udivmodqi4.S",
}
// libCompilerRT 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. // instruction or a short sequence of instructions for that target.
// //
// For more information, see: https://compiler-rt.llvm.org/ // For more information, see: https://compiler-rt.llvm.org/
var libCompilerRT = Library{ var CompilerRT = Library{
name: "compiler-rt", name: "compiler-rt",
cflags: func(target, headerPath string) []string { cflags: func(target, headerPath string) []string {
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
@@ -220,16 +181,11 @@ var libCompilerRT = Library{
// Development build. // Development build.
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins") return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins")
}, },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) []string {
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
switch compileopts.CanonicalArchName(target) { if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
case "arm":
builtins = append(builtins, aeabiBuiltins...) builtins = append(builtins, aeabiBuiltins...)
case "avr":
builtins = append(builtins, avrBuiltins...)
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
builtins = append(builtins, genericBuiltins128...)
} }
return builtins, nil return builtins
}, },
} }
+16 -8
View File
@@ -56,7 +56,7 @@ import (
// depfile but without invalidating its name. For this reason, the depfile is // depfile but without invalidating its name. For this reason, the depfile is
// written on each new compilation (even when it seems unnecessary). However, it // written on each new compilation (even when it seems unnecessary). However, it
// could in rare cases lead to a stale file fetched from the cache. // could in rare cases lead to a stale file fetched from the cache.
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) { func compileAndCacheCFile(abspath, tmpdir string, cflags []string, thinlto bool, printCommands func(string, ...string)) (string, error) {
// Hash input file. // Hash input file.
fileHash, err := hashFile(abspath) fileHash, err := hashFile(abspath)
if err != nil { if err != nil {
@@ -67,6 +67,11 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock")) unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
defer unlock() defer unlock()
ext := ".o"
if thinlto {
ext = ".bc"
}
// Create cache key for the dependencies file. // Create cache key for the dependencies file.
buf, err := json.Marshal(struct { buf, err := json.Marshal(struct {
Path string Path string
@@ -99,7 +104,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
} }
// Obtain hashes of all the files listed as a dependency. // Obtain hashes of all the files listed as a dependency.
outpath, err := makeCFileCachePath(dependencies, depfileNameHash) outpath, err := makeCFileCachePath(dependencies, depfileNameHash, ext)
if err == nil { if err == nil {
if _, err := os.Stat(outpath); err == nil { if _, err := os.Stat(outpath); err == nil {
return outpath, nil return outpath, nil
@@ -112,7 +117,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
return "", err return "", err
} }
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc") objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*"+ext)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -122,8 +127,11 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
return "", err return "", err
} }
depTmpFile.Close() depTmpFile.Close()
flags := append([]string{}, cflags...) // copy cflags flags := append([]string{}, cflags...) // copy cflags
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name(), "-flto=thin") // autogenerate dependencies flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
if thinlto {
flags = append(flags, "-flto=thin")
}
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath) flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
if strings.ToLower(filepath.Ext(abspath)) == ".s" { if strings.ToLower(filepath.Ext(abspath)) == ".s" {
// If this is an assembly file (.s or .S, lowercase or uppercase), then // If this is an assembly file (.s or .S, lowercase or uppercase), then
@@ -181,7 +189,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
} }
// Move temporary object file to final location. // Move temporary object file to final location.
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash) outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash, ext)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -196,7 +204,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
// Create a cache path (a path in GOCACHE) to store the output of a compiler // Create a cache path (a path in GOCACHE) to store the output of a compiler
// job. This path is based on the dep file name (which is a hash of metadata // job. This path is based on the dep file name (which is a hash of metadata
// including compiler flags) and the hash of all input files in the paths slice. // including compiler flags) and the hash of all input files in the paths slice.
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) { func makeCFileCachePath(paths []string, depfileNameHash, ext string) (string, error) {
// Hash all input files. // Hash all input files.
fileHashes := make(map[string]string, len(paths)) fileHashes := make(map[string]string, len(paths))
for _, path := range paths { for _, path := range paths {
@@ -221,7 +229,7 @@ func makeCFileCachePath(paths []string, depfileNameHash string) (string, error)
outFileNameBuf := sha512.Sum512_224(buf) outFileNameBuf := sha512.Sum512_224(buf)
cacheKey := hex.EncodeToString(outFileNameBuf[:]) cacheKey := hex.EncodeToString(outFileNameBuf[:])
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".bc") outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+ext)
return outpath, nil return outpath, nil
} }
+19 -63
View File
@@ -1,11 +1,4 @@
//go:build byollvm // +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 --------------------------------------===// //===-- cc1as.cpp - Clang Assembler --------------------------------------===//
// //
@@ -28,8 +21,8 @@
#include "clang/Frontend/TextDiagnosticPrinter.h" #include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h" #include "clang/Frontend/Utils.h"
#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/StringSwitch.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/DataLayout.h" #include "llvm/IR/DataLayout.h"
#include "llvm/MC/MCAsmBackend.h" #include "llvm/MC/MCAsmBackend.h"
#include "llvm/MC/MCAsmInfo.h" #include "llvm/MC/MCAsmInfo.h"
@@ -53,6 +46,7 @@
#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h" #include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormattedStream.h" #include "llvm/Support/FormattedStream.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h" #include "llvm/Support/Path.h"
#include "llvm/Support/Process.h" #include "llvm/Support/Process.h"
@@ -61,10 +55,7 @@
#include "llvm/Support/TargetSelect.h" #include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Timer.h" #include "llvm/Support/Timer.h"
#include "llvm/Support/raw_ostream.h" #include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Host.h"
#include "llvm/TargetParser/Triple.h"
#include <memory> #include <memory>
#include <optional>
#include <system_error> #include <system_error>
using namespace clang; using namespace clang;
using namespace clang::driver; using namespace clang::driver;
@@ -82,10 +73,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Parse the arguments. // Parse the arguments.
const OptTable &OptTbl = getDriverOptTable(); const OptTable &OptTbl = getDriverOptTable();
llvm::opt::Visibility VisibilityMask(options::CC1AsOption); const unsigned IncludedFlagsBitmask = options::CC1AsOption;
unsigned MissingArgIndex, MissingArgCount; unsigned MissingArgIndex, MissingArgCount;
InputArgList Args = InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask); IncludedFlagsBitmask);
// Check for missing argument error. // Check for missing argument error.
if (MissingArgCount) { if (MissingArgCount) {
@@ -98,7 +89,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) { for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
auto ArgString = A->getAsString(Args); auto ArgString = A->getAsString(Args);
std::string Nearest; 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; Diags.Report(diag::err_drv_unknown_argument) << ArgString;
else else
Diags.Report(diag::err_drv_unknown_argument_with_suggestion) Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
@@ -110,17 +101,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Target Options // Target Options
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)); Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
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.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
Opts.Features = Args.getAllArgValues(OPT_target_feature); Opts.Features = Args.getAllArgValues(OPT_target_feature);
@@ -139,12 +119,11 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.CompressDebugSections = Opts.CompressDebugSections =
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue()) llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
.Case("none", llvm::DebugCompressionType::None) .Case("none", llvm::DebugCompressionType::None)
.Case("zlib", llvm::DebugCompressionType::Zlib) .Case("zlib", llvm::DebugCompressionType::Z)
.Case("zstd", llvm::DebugCompressionType::Zstd)
.Default(llvm::DebugCompressionType::None); .Default(llvm::DebugCompressionType::None);
} }
Opts.RelaxELFRelocations = !Args.hasArg(OPT_mrelax_relocations_no); Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32)) if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32))
Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64); Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64);
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags); Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
@@ -159,7 +138,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) { for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
auto Split = StringRef(Arg).split('='); 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 // Frontend Options
@@ -206,7 +186,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack); Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings); Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn); Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
Opts.NoTypeCheck = Args.hasArg(OPT_mno_type_check);
Opts.RelocationModel = Opts.RelocationModel =
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic")); std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi)); Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
@@ -224,19 +203,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
.Default(0); .Default(0);
} }
if (auto *A = Args.getLastArg(OPT_femit_dwarf_unwind_EQ)) {
Opts.EmitDwarfUnwind =
llvm::StringSwitch<EmitDwarfUnwindType>(A->getValue())
.Case("always", EmitDwarfUnwindType::Always)
.Case("no-compact-unwind", EmitDwarfUnwindType::NoCompactUnwind)
.Case("default", EmitDwarfUnwindType::Default);
}
Opts.EmitCompactUnwindNonCanonical =
Args.hasArg(OPT_femit_compact_unwind_non_canonical);
Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file);
return Success; return Success;
} }
@@ -270,8 +236,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true); MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
if (std::error_code EC = Buffer.getError()) { if (std::error_code EC = Buffer.getError()) {
return Diags.Report(diag::err_fe_error_reading) Error = EC.message();
<< Opts.InputFile << EC.message(); return Diags.Report(diag::err_fe_error_reading) << Opts.InputFile;
} }
SourceMgr SrcMgr; SourceMgr SrcMgr;
@@ -287,10 +253,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
assert(MRI && "Unable to create target register info!"); assert(MRI && "Unable to create target register info!");
MCTargetOptions MCOptions; MCTargetOptions MCOptions;
MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind;
MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical;
MCOptions.AsSecureLogFile = Opts.AsSecureLogFile;
std::unique_ptr<MCAsmInfo> MAI( std::unique_ptr<MCAsmInfo> MAI(
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions)); TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
assert(MAI && "Unable to create target asm info!"); assert(MAI && "Unable to create target asm info!");
@@ -337,10 +299,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// MCObjectFileInfo needs a MCContext reference in order to initialize itself. // MCObjectFileInfo needs a MCContext reference in order to initialize itself.
std::unique_ptr<MCObjectFileInfo> MOFI( std::unique_ptr<MCObjectFileInfo> MOFI(
TheTarget->createMCObjectFileInfo(Ctx, PIC)); TheTarget->createMCObjectFileInfo(Ctx, PIC));
if (Opts.DarwinTargetVariantTriple)
MOFI->setDarwinTargetVariantTriple(*Opts.DarwinTargetVariantTriple);
if (!Opts.DarwinTargetVariantSDKVersion.empty())
MOFI->setDarwinTargetVariantSDKVersion(Opts.DarwinTargetVariantSDKVersion);
Ctx.setObjectFileInfo(MOFI.get()); Ctx.setObjectFileInfo(MOFI.get());
if (Opts.SaveTemporaryLabels) if (Opts.SaveTemporaryLabels)
@@ -380,7 +338,6 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
MCOptions.MCNoWarn = Opts.NoWarn; MCOptions.MCNoWarn = Opts.NoWarn;
MCOptions.MCFatalWarnings = Opts.FatalWarnings; MCOptions.MCFatalWarnings = Opts.FatalWarnings;
MCOptions.MCNoTypeCheck = Opts.NoTypeCheck;
MCOptions.ABIName = Opts.TargetABI; MCOptions.ABIName = Opts.TargetABI;
// FIXME: There is a bit of code duplication with addPassesToEmitFile. // FIXME: There is a bit of code duplication with addPassesToEmitFile.
@@ -390,7 +347,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCCodeEmitter> CE; std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding) if (Opts.ShowEncoding)
CE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx)); CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB( std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
@@ -410,7 +367,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
} }
std::unique_ptr<MCCodeEmitter> CE( std::unique_ptr<MCCodeEmitter> CE(
TheTarget->createMCCodeEmitter(*MCII, Ctx)); TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
std::unique_ptr<MCAsmBackend> MAB( std::unique_ptr<MCAsmBackend> MAB(
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions)); TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
assert(MAB && "Unable to create asm backend!"); assert(MAB && "Unable to create asm backend!");
@@ -432,7 +389,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) { if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) {
MCSection *AsmLabel = Ctx.getMachOSection( MCSection *AsmLabel = Ctx.getMachOSection(
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly()); "__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
Str.get()->switchSection(AsmLabel); Str.get()->SwitchSection(AsmLabel);
Str.get()->emitZeros(1); Str.get()->emitZeros(1);
} }
@@ -521,10 +478,9 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
if (Asm.ShowHelp) { if (Asm.ShowHelp) {
getDriverOptTable().printHelp( getDriverOptTable().printHelp(
llvm::outs(), "clang -cc1as [options] file...", llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler", /*ShowHidden=*/false, "Clang Integrated Assembler",
/*ShowAllAliases=*/false, /*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
llvm::opt::Visibility(driver::options::CC1AsOption)); /*ShowAllAliases=*/false);
return 0; return 0;
} }
+1 -25
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 ----------------------------------------===// //===-- cc1as.h - Clang Assembler ----------------------------------------===//
// //
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
@@ -47,7 +44,7 @@ struct AssemblerInvocation {
std::string DwarfDebugFlags; std::string DwarfDebugFlags;
std::string DwarfDebugProducer; std::string DwarfDebugProducer;
std::string DebugCompilationDir; 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 CompressDebugSections =
llvm::DebugCompressionType::None; llvm::DebugCompressionType::None;
std::string MainFileName; std::string MainFileName;
@@ -85,17 +82,9 @@ struct AssemblerInvocation {
unsigned NoExecStack : 1; unsigned NoExecStack : 1;
unsigned FatalWarnings : 1; unsigned FatalWarnings : 1;
unsigned NoWarn : 1; unsigned NoWarn : 1;
unsigned NoTypeCheck : 1;
unsigned IncrementalLinkerCompatible : 1; unsigned IncrementalLinkerCompatible : 1;
unsigned EmbedBitcode : 1; 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.
unsigned EmitCompactUnwindNonCanonical : 1;
/// The name of the relocation model to use. /// The name of the relocation model to use.
std::string RelocationModel; std::string RelocationModel;
@@ -103,16 +92,6 @@ struct AssemblerInvocation {
/// otherwise. /// otherwise.
std::string TargetABI; std::string TargetABI;
/// 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;
/// @} /// @}
public: public:
@@ -129,13 +108,10 @@ public:
NoExecStack = 0; NoExecStack = 0;
FatalWarnings = 0; FatalWarnings = 0;
NoWarn = 0; NoWarn = 0;
NoTypeCheck = 0;
IncrementalLinkerCompatible = 0; IncrementalLinkerCompatible = 0;
Dwarf64 = 0; Dwarf64 = 0;
DwarfVersion = 0; DwarfVersion = 0;
EmbedBitcode = 0; EmbedBitcode = 0;
EmitDwarfUnwind = EmitDwarfUnwindType::Default;
EmitCompactUnwindNonCanonical = false;
} }
static bool CreateFromArgs(AssemblerInvocation &Res, static bool CreateFromArgs(AssemblerInvocation &Res,
+2 -2
View File
@@ -1,4 +1,4 @@
//go:build byollvm // +build byollvm
#include <clang/Basic/DiagnosticOptions.h> #include <clang/Basic/DiagnosticOptions.h>
#include <clang/CodeGen/CodeGenAction.h> #include <clang/CodeGen/CodeGenAction.h>
@@ -11,7 +11,7 @@
#include <clang/FrontendTool/Utils.h> #include <clang/FrontendTool/Utils.h>
#include <llvm/ADT/IntrusiveRefCntPtr.h> #include <llvm/ADT/IntrusiveRefCntPtr.h>
#include <llvm/Option/Option.h> #include <llvm/Option/Option.h>
#include <llvm/TargetParser/Host.h> #include <llvm/Support/Host.h>
using namespace llvm; using namespace llvm;
using namespace clang; using namespace clang;
+13 -6
View File
@@ -1,6 +1,7 @@
package builder package builder
import ( import (
"errors"
"fmt" "fmt"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
@@ -23,20 +24,26 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec.OpenOCDCommands = options.OpenOCDCommands spec.OpenOCDCommands = options.OpenOCDCommands
} }
major, minor, err := goenv.GetGorootVersion() goroot := goenv.Get("GOROOT")
if goroot == "" {
return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
major, minor, err := goenv.GetGorootVersion(goroot)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
} }
if major != 1 || minor < 19 || minor > 23 { if major != 1 || minor < 18 || minor > 19 {
// Note: when this gets updated, also update the Go compatibility matrix: return nil, fmt.Errorf("requires go version 1.18 through 1.19, got go%d.%d", major, minor)
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.19 through 1.23, got go%d.%d", major, minor)
} }
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
return &compileopts.Config{ return &compileopts.Config{
Options: options, Options: options,
Target: spec, Target: spec,
GoMinorVersion: minor, GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig, TestConfig: options.TestConfig,
}, nil }, 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 // MultiError is a list of multiple errors (actually: diagnostics) returned
// during LLVM IR generation. // during LLVM IR generation.
type MultiError struct { type MultiError struct {
ImportPath string Errs []error
Errs []error
} }
func (e *MultiError) Error() string { 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 // 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 // returns that error directly when there is only one. Passing an empty slice
// will return nil (because there is no error). // will lead to a panic.
// The importPath may be passed if this error is for a single package. func newMultiError(errs []error) error {
func newMultiError(errs []error, importPath string) error {
switch len(errs) { switch len(errs) {
case 0: case 0:
return nil panic("attempted to create empty MultiError")
case 1: case 1:
return errs[0] return errs[0]
default: default:
return &MultiError{importPath, errs} return &MultiError{errs}
} }
} }
+1 -1
View File
@@ -23,7 +23,7 @@ type espImageSegment struct {
data []byte data []byte
} }
// makeESPFirmareImage 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 // 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. // family, and is parsed by the on-chip mask ROM bootloader.
// //
+1 -1
View File
@@ -134,7 +134,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
numRunningJobs-- numRunningJobs--
<-sema <-sema
if jobRunnerDebug { if jobRunnerDebug {
fmt.Println("## finished:", completed.description, "(time "+completed.duration.String()+")") fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
} }
if completed.err != nil { if completed.err != nil {
// Wait for any current jobs to finish. // Wait for any current jobs to finish.
+30 -40
View File
@@ -29,12 +29,25 @@ type Library struct {
sourceDir func() string sourceDir func() string
// The source files, relative to sourceDir. // The source files, relative to sourceDir.
librarySources func(target string) ([]string, error) librarySources func(target string) []string
// The source code for the crt1.o file, relative to sourceDir. // The source code for the crt1.o file, relative to sourceDir.
crt1Source string 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 // 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 // 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 // cached. The path is stored as job.result but is only valid after the job has
@@ -129,11 +142,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Note: -fdebug-prefix-map is necessary to make the output archive // Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive // reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run. // 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) args := append(l.cflags(target, headerPath), "-c", "-Oz", "-g", "-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() cpu := config.CPU()
if cpu != "" { if cpu != "" {
// X86 has deprecated the -mcpu flag, so we need to use -march instead. // X86 has deprecated the -mcpu flag, so we need to use -march instead.
@@ -146,40 +155,31 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
args = append(args, "-mcpu="+cpu) args = append(args, "-mcpu="+cpu)
} }
} }
if config.ABI() != "" { if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
args = append(args, "-mabi="+config.ABI())
}
switch compileopts.CanonicalArchName(target) {
case "arm":
if strings.Split(target, "-")[2] == "linux" { if strings.Split(target, "-")[2] == "linux" {
args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
} else { } else {
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") 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 // 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 // from what most code (and certainly compiler-rt) expects. So we need
// to force the compiler to use 64-bit floating point numbers for // to force the compiler to use 64-bit floating point numbers for
// double. // double.
args = append(args, "-mdouble=64") args = append(args, "-mdouble=64")
case "riscv32":
args = append(args, "-march=rv32imac", "-fforce-enable-int128")
case "riscv64":
args = append(args, "-march=rv64gc")
case "mips":
args = append(args, "-fno-pic")
} }
if config.Target.SoftFloat { if strings.HasPrefix(target, "riscv32-") {
// Use softfloat instead of floating point instructions. This is args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
// supported on many architectures. }
args = append(args, "-msoft-float") if strings.HasPrefix(target, "riscv64-") {
} else { args = append(args, "-march=rv64gc", "-mabi=lp64")
if strings.HasPrefix(target, "armv5") { }
// On ARMv5 we need to explicitly enable hardware floating point if strings.HasPrefix(target, "xtensa") {
// instructions: Clang appears to assume the hardware doesn't have a // Hack to work around an issue in the Xtensa port:
// FPU otherwise. // https://github.com/espressif/llvm-project/issues/52
args = append(args, "-mfpu=vfpv2") // Hopefully this will be fixed soon (LLVM 14).
} args = append(args, "-D__ELF__")
} }
var once sync.Once var once sync.Once
@@ -219,11 +219,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Create jobs to compile all sources. These jobs are depended upon by the // Create jobs to compile all sources. These jobs are depended upon by the
// archive job above, so must be run first. // archive job above, so must be run first.
paths, err := l.librarySources(target) for _, path := range l.librarySources(target) {
if err != nil {
return nil, nil, err
}
for _, path := range paths {
// Strip leading "../" parts off the path. // Strip leading "../" parts off the path.
cleanpath := path cleanpath := path
for strings.HasPrefix(cleanpath, "../") { for strings.HasPrefix(cleanpath, "../") {
@@ -239,9 +235,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
var compileArgs []string var compileArgs []string
compileArgs = append(compileArgs, args...) compileArgs = append(compileArgs, args...)
compileArgs = append(compileArgs, "-o", objpath, srcpath) compileArgs = append(compileArgs, "-o", objpath, srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
}
err := runCCompiler(compileArgs...) err := runCCompiler(compileArgs...)
if err != nil { if err != nil {
return &commandError{"failed to build", srcpath, err} return &commandError{"failed to build", srcpath, err}
@@ -268,9 +261,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
} }
tmpfile.Close() tmpfile.Close()
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath) compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
if config.Options.PrintCommands != nil {
config.Options.PrintCommands("clang", compileArgs...)
}
err = runCCompiler(compileArgs...) err = runCCompiler(compileArgs...)
if err != nil { if err != nil {
return &commandError{"failed to build", srcpath, err} return &commandError{"failed to build", srcpath, err}
+18 -21
View File
@@ -1,32 +1,29 @@
//go:build byollvm // +build byollvm
// This file provides C wrappers for liblld. // This file provides C wrappers for liblld.
#include <lld/Common/Driver.h> #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" { extern "C" {
bool tinygo_link(int argc, char **argv) { bool tinygo_link_elf(int argc, char **argv) {
configure();
std::vector<const char*> args(argv, argv + argc); std::vector<const char*> args(argv, argv + argc);
lld::Result r = lld::lldMain(args, llvm::outs(), llvm::errs(), LLD_ALL_DRIVERS); return lld::elf::link(args, llvm::outs(), llvm::errs(), false, false);
return !r.retCode; }
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" } // external "C"
+11 -39
View File
@@ -1,7 +1,6 @@
package builder package builder
import ( import (
"fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
@@ -10,7 +9,7 @@ import (
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
var libMinGW = Library{ var MinGW = Library{
name: "mingw-w64", name: "mingw-w64",
makeHeaders: func(target, includeDir string) error { makeHeaders: func(target, includeDir string) error {
// copy _mingw.h // copy _mingw.h
@@ -27,30 +26,14 @@ var libMinGW = Library{
_, err = io.Copy(outf, inf) _, err = io.Copy(outf, inf)
return err 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 { cflags: func(target, headerPath string) []string {
mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") // No flags necessary because there are no files to compile.
return []string{ return nil
"-nostdlibinc",
"-isystem", mingwDir + "/mingw-w64-headers/crt",
"-I", mingwDir + "/mingw-w64-headers/defaults/include",
"-I" + headerPath,
}
}, },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) []string {
// These files are needed so that printf and the like are supported. // We only use the UCRT DLL file. No source files necessary.
sources := []string{ return nil
"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
}, },
} }
@@ -60,7 +43,7 @@ var libMinGW = Library{
// //
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to // TODO: cache the result. At the moment, it costs a few hundred milliseconds to
// compile these files. // compile these files.
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob { func makeMinGWExtraLibs(tmpdir string) []*compileJob {
var jobs []*compileJob var jobs []*compileJob
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
// Normally all the api-ms-win-crt-*.def files are all compiled to a single // Normally all the api-ms-win-crt-*.def files are all compiled to a single
@@ -69,7 +52,7 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
for _, name := range []string{ for _, name := range []string{
"kernel32.def.in", "kernel32.def.in",
"api-ms-win-crt-conio-l1-1-0.def", "api-ms-win-crt-conio-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def.in", "api-ms-win-crt-convert-l1-1-0.def",
"api-ms-win-crt-environment-l1-1-0.def", "api-ms-win-crt-environment-l1-1-0.def",
"api-ms-win-crt-filesystem-l1-1-0.def", "api-ms-win-crt-filesystem-l1-1-0.def",
"api-ms-win-crt-heap-l1-1-0.def", "api-ms-win-crt-heap-l1-1-0.def",
@@ -91,27 +74,16 @@ func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
result: outpath, result: outpath,
run: func(job *compileJob) error { run: func(job *compileJob) error {
defpath := inpath defpath := inpath
var archDef, emulation string
switch goarch {
case "amd64":
archDef = "-DDEF_X64"
emulation = "i386pep"
case "arm64":
archDef = "-DDEF_ARM64"
emulation = "arm64pe"
default:
return fmt.Errorf("unsupported architecture for mingw-w64: %s", goarch)
}
if strings.HasSuffix(inpath, ".in") { if strings.HasSuffix(inpath, ".in") {
// .in files need to be preprocessed by a preprocessor (-E) // .in files need to be preprocessed by a preprocessor (-E)
// first. // first.
defpath = outpath + ".def" defpath = outpath + ".def"
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", archDef, "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/") err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", "-DDEF_X64", "-DDATA", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
if err != nil { if err != nil {
return err return err
} }
} }
return link("ld.lld", "-m", emulation, "-o", outpath, defpath) return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
}, },
} }
jobs = append(jobs, job) jobs = append(jobs, job)
+7 -22
View File
@@ -12,7 +12,7 @@ import (
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
var libMusl = Library{ var Musl = Library{
name: "musl", name: "musl",
makeHeaders: func(target, includeDir string) error { makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits") bits := filepath.Join(includeDir, "bits")
@@ -77,7 +77,7 @@ var libMusl = Library{
cflags: func(target, headerPath string) []string { cflags: func(target, headerPath string) []string {
arch := compileopts.MuslArchitecture(target) arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl") muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
cflags := []string{ return []string{
"-std=c99", // same as in musl "-std=c99", // same as in musl
"-D_XOPEN_SOURCE=700", // same as in musl "-D_XOPEN_SOURCE=700", // same as in musl
// Musl triggers some warnings and we don't want to show any // Musl triggers some warnings and we don't want to show any
@@ -90,10 +90,6 @@ var libMusl = Library{
"-Wno-ignored-attributes", "-Wno-ignored-attributes",
"-Wno-string-plus-int", "-Wno-string-plus-int",
"-Wno-ignored-pragmas", "-Wno-ignored-pragmas",
"-Wno-tautological-constant-out-of-range-compare",
"-Wno-deprecated-non-prototype",
"-Wno-format",
"-Wno-parentheses",
"-Qunused-arguments", "-Qunused-arguments",
// Select include dirs. Don't include standard library includes // Select include dirs. Don't include standard library includes
// (that would introduce host dependencies and other complications), // (that would introduce host dependencies and other complications),
@@ -107,10 +103,9 @@ var libMusl = Library{
"-I" + muslDir + "/include", "-I" + muslDir + "/include",
"-fno-stack-protector", "-fno-stack-protector",
} }
return cflags
}, },
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) []string {
arch := compileopts.MuslArchitecture(target) arch := compileopts.MuslArchitecture(target)
globs := []string{ globs := []string{
"env/*.c", "env/*.c",
@@ -121,25 +116,18 @@ var libMusl = Library{
"internal/syscall_ret.c", "internal/syscall_ret.c",
"internal/vdso.c", "internal/vdso.c",
"legacy/*.c", "legacy/*.c",
"locale/*.c",
"linux/*.c",
"malloc/*.c", "malloc/*.c",
"malloc/mallocng/*.c",
"mman/*.c", "mman/*.c",
"math/*.c", "math/*.c",
"multibyte/*.c",
"signal/*.c", "signal/*.c",
"stdio/*.c", "stdio/*.c",
"string/*.c", "string/*.c",
"thread/" + arch + "/*.s", "thread/" + arch + "/*.s",
"thread/" + arch + "/*.c",
"thread/*.c", "thread/*.c",
"time/*.c", "time/*.c",
"unistd/*.c", "unistd/*.c",
} }
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
var sources []string var sources []string
seenSources := map[string]struct{}{} seenSources := map[string]struct{}{}
@@ -153,16 +141,13 @@ var libMusl = Library{
// > ErrBadPattern, when pattern is malformed. // > ErrBadPattern, when pattern is malformed.
// So the only possible error is when the (statically defined) // So the only possible error is when the (statically defined)
// pattern is wrong. In other words, a programming bug. // pattern is wrong. In other words, a programming bug.
return nil, fmt.Errorf("musl: could not glob source dirs: %w", err) panic("could not glob source dirs: " + err.Error())
}
if len(matches) == 0 {
return nil, fmt.Errorf("musl: did not find any files for pattern %#v", pattern)
} }
for _, match := range matches { for _, match := range matches {
relpath, err := filepath.Rel(basepath, match) relpath, err := filepath.Rel(basepath, match)
if err != nil { if err != nil {
// Not sure if this is even possible. // Not sure if this is even possible.
return nil, err panic(err)
} }
// Make sure architecture specific files override generic files. // Make sure architecture specific files override generic files.
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/") id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
@@ -174,7 +159,7 @@ var libMusl = Library{
sources = append(sources, relpath) sources = append(sources, relpath)
} }
} }
return sources, nil return sources
}, },
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
} }
+13 -103
View File
@@ -1,117 +1,27 @@
package builder package builder
import ( import (
"archive/zip" "fmt"
"bytes" "io"
"encoding/binary" "os/exec"
"encoding/json"
"os"
"github.com/sigurn/crc16"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
) )
// Structure of the manifest.json file. // https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
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"`
}
// 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 { func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
// Read ELF file as input and convert it to a binary image file. cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
_, data, err := extractROM(infile)
if err != nil { if options.PrintCommands != nil {
return err options.PrintCommands(cmdLine[0], cmdLine[1:]...)
} }
// Create the zip file in memory. cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
// It won't be very large anyway. cmd.Stdout = io.Discard
buf := &bytes.Buffer{} err := cmd.Run()
w := zip.NewWriter(buf)
// Write the application binary to the zip file.
binw, err := w.Create("application.bin")
if err != nil { if err != nil {
return err return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
} }
_, err = binw.Write(data) return nil
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)
} }
+157 -179
View File
@@ -3,14 +3,13 @@ package builder
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv" "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. // based on newlib.
var libPicolibc = Library{ var Picolibc = Library{
name: "picolibc", name: "picolibc",
makeHeaders: func(target, includeDir string) error { makeHeaders: func(target, includeDir string) error {
f, err := os.Create(filepath.Join(includeDir, "picolibc.h")) f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
@@ -26,14 +25,11 @@ var libPicolibc = Library{
"-Wall", "-Wall",
"-std=gnu11", "-std=gnu11",
"-D_COMPILING_NEWLIB", "-D_COMPILING_NEWLIB",
"-D_HAVE_ALIAS_ATTRIBUTE", "-DHAVE_ALIAS_ATTRIBUTE",
"-DTINY_STDIO", "-DTINY_STDIO",
"-DPOSIX_IO",
"-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default
"-D_IEEE_LIBM", "-D_IEEE_LIBM",
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU "-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
"-D__OBSOLETE_MATH_DOUBLE=0", "-D__OBSOLETE_MATH_DOUBLE=0",
"-D_WANT_IO_C99_FORMATS",
"-nostdlibinc", "-nostdlibinc",
"-isystem", newlibDir + "/libc/include", "-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio", "-I" + newlibDir + "/libc/tinystdio",
@@ -42,24 +38,111 @@ var libPicolibc = Library{
} }
}, },
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") }, sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
librarySources: func(target string) ([]string, error) { librarySources: func(target string) []string {
sources := append([]string(nil), picolibcSources...) return 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
}, },
} }
var picolibcSources = []string{ var picolibcSources = []string{
"../../picolibc-stdio.c", "../../picolibc-stdio.c",
"libc/tinystdio/asprintf.c",
"libc/tinystdio/atod_engine.c",
"libc/tinystdio/atod_ryu.c",
"libc/tinystdio/atof_engine.c",
"libc/tinystdio/atof_ryu.c",
//"libc/tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
"libc/tinystdio/clearerr.c",
"libc/tinystdio/compare_exchange.c",
"libc/tinystdio/dtoa_data.c",
"libc/tinystdio/dtoa_engine.c",
"libc/tinystdio/dtoa_ryu.c",
"libc/tinystdio/ecvtbuf.c",
"libc/tinystdio/ecvt.c",
"libc/tinystdio/ecvt_data.c",
"libc/tinystdio/ecvtfbuf.c",
"libc/tinystdio/ecvtf.c",
"libc/tinystdio/ecvtf_data.c",
"libc/tinystdio/exchange.c",
//"libc/tinystdio/fclose.c", // posix-io
"libc/tinystdio/fcvtbuf.c",
"libc/tinystdio/fcvt.c",
"libc/tinystdio/fcvtfbuf.c",
"libc/tinystdio/fcvtf.c",
"libc/tinystdio/fdevopen.c",
//"libc/tinystdio/fdopen.c", // posix-io
"libc/tinystdio/feof.c",
"libc/tinystdio/ferror.c",
"libc/tinystdio/fflush.c",
"libc/tinystdio/fgetc.c",
"libc/tinystdio/fgets.c",
"libc/tinystdio/fileno.c",
"libc/tinystdio/filestrget.c",
"libc/tinystdio/filestrputalloc.c",
"libc/tinystdio/filestrput.c",
//"libc/tinystdio/fopen.c", // posix-io
"libc/tinystdio/fprintf.c",
"libc/tinystdio/fputc.c",
"libc/tinystdio/fputs.c",
"libc/tinystdio/fread.c",
"libc/tinystdio/fscanf.c",
"libc/tinystdio/fseek.c",
"libc/tinystdio/ftell.c",
"libc/tinystdio/ftoa_data.c",
"libc/tinystdio/ftoa_engine.c",
"libc/tinystdio/ftoa_ryu.c",
"libc/tinystdio/fwrite.c",
"libc/tinystdio/gcvtbuf.c",
"libc/tinystdio/gcvt.c",
"libc/tinystdio/gcvtfbuf.c",
"libc/tinystdio/gcvtf.c",
"libc/tinystdio/getchar.c",
"libc/tinystdio/gets.c",
"libc/tinystdio/matchcaseprefix.c",
"libc/tinystdio/perror.c",
//"libc/tinystdio/posixiob.c", // posix-io
//"libc/tinystdio/posixio.c", // posix-io
"libc/tinystdio/printf.c",
"libc/tinystdio/putchar.c",
"libc/tinystdio/puts.c",
"libc/tinystdio/ryu_divpow2.c",
"libc/tinystdio/ryu_log10.c",
"libc/tinystdio/ryu_log2pow5.c",
"libc/tinystdio/ryu_pow5bits.c",
"libc/tinystdio/ryu_table.c",
"libc/tinystdio/ryu_umul128.c",
"libc/tinystdio/scanf.c",
"libc/tinystdio/setbuf.c",
"libc/tinystdio/setvbuf.c",
//"libc/tinystdio/sflags.c", // posix-io
"libc/tinystdio/snprintf.c",
"libc/tinystdio/snprintfd.c",
"libc/tinystdio/snprintff.c",
"libc/tinystdio/sprintf.c",
"libc/tinystdio/sprintfd.c",
"libc/tinystdio/sprintff.c",
"libc/tinystdio/sscanf.c",
"libc/tinystdio/strfromd.c",
"libc/tinystdio/strfromf.c",
"libc/tinystdio/strtod.c",
"libc/tinystdio/strtod_l.c",
"libc/tinystdio/strtof.c",
//"libc/tinystdio/strtold.c", // have_long_double and not long_double_equals_double
//"libc/tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
"libc/tinystdio/ungetc.c",
"libc/tinystdio/vasprintf.c",
"libc/tinystdio/vfiprintf.c",
"libc/tinystdio/vfiscanf.c",
"libc/tinystdio/vfprintf.c",
"libc/tinystdio/vfprintff.c",
"libc/tinystdio/vfscanf.c",
"libc/tinystdio/vfscanff.c",
"libc/tinystdio/vprintf.c",
"libc/tinystdio/vscanf.c",
"libc/tinystdio/vsnprintf.c",
"libc/tinystdio/vsprintf.c",
"libc/tinystdio/vsscanf.c",
"libc/string/bcmp.c", "libc/string/bcmp.c",
"libc/string/bcopy.c", "libc/string/bcopy.c",
"libc/string/bzero.c", "libc/string/bzero.c",
@@ -163,87 +246,6 @@ var picolibcSources = []string{
"libc/string/wmempcpy.c", "libc/string/wmempcpy.c",
"libc/string/wmemset.c", "libc/string/wmemset.c",
"libc/string/xpg_strerror_r.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_finite.c",
"libm/common/sf_copysign.c", "libm/common/sf_copysign.c",
@@ -298,7 +300,6 @@ var picolibcSourcesLarge = []string{
"libm/common/s_expm1.c", "libm/common/s_expm1.c",
"libm/common/s_ilogb.c", "libm/common/s_ilogb.c",
"libm/common/s_infinity.c", "libm/common/s_infinity.c",
"libm/common/s_iseqsig.c",
"libm/common/s_isinf.c", "libm/common/s_isinf.c",
"libm/common/s_isinfd.c", "libm/common/s_isinfd.c",
"libm/common/s_isnan.c", "libm/common/s_isnan.c",
@@ -316,7 +317,6 @@ var picolibcSourcesLarge = []string{
"libm/common/s_fmax.c", "libm/common/s_fmax.c",
"libm/common/s_fmin.c", "libm/common/s_fmin.c",
"libm/common/s_fpclassify.c", "libm/common/s_fpclassify.c",
"libm/common/s_getpayload.c",
"libm/common/s_lrint.c", "libm/common/s_lrint.c",
"libm/common/s_llrint.c", "libm/common/s_llrint.c",
"libm/common/s_lround.c", "libm/common/s_lround.c",
@@ -331,6 +331,7 @@ var picolibcSourcesLarge = []string{
"libm/common/exp2.c", "libm/common/exp2.c",
"libm/common/exp_data.c", "libm/common/exp_data.c",
"libm/common/math_err_with_errno.c", "libm/common/math_err_with_errno.c",
"libm/common/math_err_xflow.c",
"libm/common/math_err_uflow.c", "libm/common/math_err_uflow.c",
"libm/common/math_err_oflow.c", "libm/common/math_err_oflow.c",
"libm/common/math_err_divzero.c", "libm/common/math_err_divzero.c",
@@ -338,14 +339,6 @@ var picolibcSourcesLarge = []string{
"libm/common/math_err_may_uflow.c", "libm/common/math_err_may_uflow.c",
"libm/common/math_err_check_uflow.c", "libm/common/math_err_check_uflow.c",
"libm/common/math_err_check_oflow.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", "libm/common/log.c",
"libm/common/log_data.c", "libm/common/log_data.c",
"libm/common/log2.c", "libm/common/log2.c",
@@ -353,91 +346,76 @@ var picolibcSourcesLarge = []string{
"libm/common/pow.c", "libm/common/pow.c",
"libm/common/pow_log_data.c", "libm/common/pow_log_data.c",
"libm/math/k_cos.c", "libm/math/e_acos.c",
"libm/math/k_rem_pio2.c", "libm/math/e_acosh.c",
"libm/math/k_sin.c", "libm/math/e_asin.c",
"libm/math/k_tan.c", "libm/math/e_atan2.c",
"libm/math/kf_cos.c", "libm/math/e_atanh.c",
"libm/math/kf_rem_pio2.c", "libm/math/e_cosh.c",
"libm/math/kf_sin.c", "libm/math/e_exp.c",
"libm/math/kf_tan.c", "libm/math/ef_acos.c",
"libm/math/s_acos.c", "libm/math/ef_acosh.c",
"libm/math/s_acosh.c", "libm/math/ef_asin.c",
"libm/math/s_asin.c", "libm/math/ef_atan2.c",
"libm/math/ef_atanh.c",
"libm/math/ef_cosh.c",
"libm/math/ef_exp.c",
"libm/math/ef_fmod.c",
"libm/math/ef_hypot.c",
"libm/math/ef_j0.c",
"libm/math/ef_j1.c",
"libm/math/ef_jn.c",
"libm/math/ef_lgamma.c",
"libm/math/ef_log10.c",
"libm/math/ef_log.c",
"libm/math/e_fmod.c",
"libm/math/ef_pow.c",
"libm/math/ef_remainder.c",
"libm/math/ef_rem_pio2.c",
"libm/math/ef_scalb.c",
"libm/math/ef_sinh.c",
"libm/math/ef_sqrt.c",
"libm/math/ef_tgamma.c",
"libm/math/e_hypot.c",
"libm/math/e_j0.c",
"libm/math/e_j1.c",
"libm/math/e_jn.c",
"libm/math/e_lgamma.c",
"libm/math/e_log10.c",
"libm/math/e_log.c",
"libm/math/e_pow.c",
"libm/math/e_remainder.c",
"libm/math/e_rem_pio2.c",
"libm/math/erf_lgamma.c",
"libm/math/er_lgamma.c",
"libm/math/e_scalb.c",
"libm/math/e_sinh.c",
"libm/math/e_sqrt.c",
"libm/math/e_tgamma.c",
"libm/math/s_asinh.c", "libm/math/s_asinh.c",
"libm/math/s_atan.c", "libm/math/s_atan.c",
"libm/math/s_atan2.c",
"libm/math/s_atanh.c",
"libm/math/s_ceil.c", "libm/math/s_ceil.c",
"libm/math/s_cos.c", "libm/math/s_cos.c",
"libm/math/s_cosh.c",
"libm/math/s_drem.c",
"libm/math/s_erf.c", "libm/math/s_erf.c",
"libm/math/s_exp.c",
"libm/math/s_exp2.c",
"libm/math/s_fabs.c", "libm/math/s_fabs.c",
"libm/math/s_floor.c",
"libm/math/s_fmod.c",
"libm/math/s_frexp.c",
"libm/math/s_gamma.c",
"libm/math/s_hypot.c",
"libm/math/s_j0.c",
"libm/math/s_j1.c",
"libm/math/s_jn.c",
"libm/math/s_lgamma.c",
"libm/math/s_log.c",
"libm/math/s_log10.c",
"libm/math/s_pow.c",
"libm/math/s_rem_pio2.c",
"libm/math/s_remainder.c",
"libm/math/s_scalb.c",
"libm/math/s_signif.c",
"libm/math/s_sin.c",
"libm/math/s_sincos.c",
"libm/math/s_sinh.c",
"libm/math/s_sqrt.c",
"libm/math/s_tan.c",
"libm/math/s_tanh.c",
"libm/math/s_tgamma.c",
"libm/math/sf_acos.c",
"libm/math/sf_acosh.c",
"libm/math/sf_asin.c",
"libm/math/sf_asinh.c", "libm/math/sf_asinh.c",
"libm/math/sf_atan.c", "libm/math/sf_atan.c",
"libm/math/sf_atan2.c",
"libm/math/sf_atanh.c",
"libm/math/sf_ceil.c", "libm/math/sf_ceil.c",
"libm/math/sf_cos.c", "libm/math/sf_cos.c",
"libm/math/sf_cosh.c",
"libm/math/sf_drem.c",
"libm/math/sf_erf.c", "libm/math/sf_erf.c",
"libm/math/sf_exp.c",
"libm/math/sf_exp2.c",
"libm/math/sf_fabs.c", "libm/math/sf_fabs.c",
"libm/math/sf_floor.c", "libm/math/sf_floor.c",
"libm/math/sf_fmod.c",
"libm/math/sf_frexp.c", "libm/math/sf_frexp.c",
"libm/math/sf_gamma.c", "libm/math/sf_ldexp.c",
"libm/math/sf_hypot.c", "libm/math/s_floor.c",
"libm/math/sf_j0.c", "libm/math/s_frexp.c",
"libm/math/sf_j1.c",
"libm/math/sf_jn.c",
"libm/math/sf_lgamma.c",
"libm/math/sf_log.c",
"libm/math/sf_log10.c",
"libm/math/sf_log2.c",
"libm/math/sf_pow.c",
"libm/math/sf_rem_pio2.c",
"libm/math/sf_remainder.c",
"libm/math/sf_scalb.c",
"libm/math/sf_signif.c", "libm/math/sf_signif.c",
"libm/math/sf_sin.c", "libm/math/sf_sin.c",
"libm/math/sf_sincos.c",
"libm/math/sf_sinh.c",
"libm/math/sf_sqrt.c",
"libm/math/sf_tan.c", "libm/math/sf_tan.c",
"libm/math/sf_tanh.c", "libm/math/sf_tanh.c",
"libm/math/sf_tgamma.c", "libm/math/s_ldexp.c",
"libm/math/sr_lgamma.c", "libm/math/s_signif.c",
"libm/math/srf_lgamma.c", "libm/math/s_sin.c",
"libm/math/s_tan.c",
"libm/math/s_tanh.c",
} }
+34 -107
View File
@@ -75,7 +75,6 @@ func (ps *packageSize) RAM() uint64 {
type addressLine struct { type addressLine struct {
Address uint64 Address uint64
Length uint64 // length of this chunk Length uint64 // length of this chunk
Align uint64 // (maximum) alignment of this line
File string // file path as stored in DWARF File string // file path as stored in DWARF
IsVariable bool // true if this is a variable (or constant), false if it is code IsVariable bool // true if this is a variable (or constant), false if it is code
} }
@@ -87,7 +86,6 @@ type memorySection struct {
Type memoryType Type memoryType
Address uint64 Address uint64
Size uint64 Size uint64
Align uint64
} }
type memoryType int type memoryType int
@@ -119,13 +117,17 @@ var (
// alloc: heap allocations during init interpretation // alloc: heap allocations during init interpretation
// pack: data created when storing a constant in an interface for example // pack: data created when storing a constant in an interface for example
// string: buffer behind strings // string: buffer behind strings
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`) packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`)
// Reflect sidetables. Created by the reflect lowering pass.
// See src/reflect/sidetables.go.
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
) )
// readProgramSizeFromDWARF reads the source location for each line of code and // 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 // each variable in the program, as far as this is stored in the DWARF debug
// information. // 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() r := data.Reader()
var lines []*dwarf.LineFile var lines []*dwarf.LineFile
var addresses []addressLine var addresses []addressLine
@@ -197,7 +199,6 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
line := addressLine{ line := addressLine{
Address: prevLineEntry.Address + codeOffset, Address: prevLineEntry.Address + codeOffset,
Length: lineEntry.Address - prevLineEntry.Address, Length: lineEntry.Address - prevLineEntry.Address,
Align: codeAlignment,
File: prevLineEntry.File.Name, File: prevLineEntry.File.Name,
} }
if line.Length != 0 { if line.Length != 0 {
@@ -222,9 +223,20 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
// Try to parse the location. While this could in theory be a very // Try to parse the location. While this could in theory be a very
// complex expression, usually it's just a DW_OP_addr opcode // complex expression, usually it's just a DW_OP_addr opcode
// followed by an address. // followed by an address.
addr, err := readDWARFConstant(r.AddressSize(), location.Val.([]uint8)) locationCode := location.Val.([]uint8)
if err != nil { if locationCode[0] != 3 { // DW_OP_addr
continue // ignore the error, we don't know what to do with it 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) // Parse the type of the global variable, which (importantly)
@@ -235,16 +247,9 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
return nil, err 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{ addresses = append(addresses, addressLine{
Address: addr, Address: addr,
Length: uint64(typ.Size()), Length: uint64(typ.Size()),
Align: alignment,
File: lines[file.Val.(int64)].Name, File: lines[file.Val.(int64)].Name,
IsVariable: true, IsVariable: true,
}) })
@@ -255,52 +260,6 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
return addresses, nil 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. // Read a MachO object file and return a line table.
// Also return an index from symbol name to start address in the 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) { func readMachOSymbolAddresses(path string) (map[string]int, []addressLine, error) {
@@ -322,7 +281,7 @@ func readMachOSymbolAddresses(path string) (map[string]int, []addressLine, error
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
lines, err := readProgramSizeFromDWARF(dwarf, 0, 0, false) lines, err := readProgramSizeFromDWARF(dwarf, 0, false)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -379,15 +338,10 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Load the binary file, which could be in a number of file formats. // Load the binary file, which could be in a number of file formats.
var sections []memorySection var sections []memorySection
if file, err := elf.NewFile(f); err == nil { 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. // Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF() data, _ := file.DWARF()
if data != nil { if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0, codeAlignment, true) addresses, err = readProgramSizeFromDWARF(data, 0, true)
if err != nil { if err != nil {
// However, _do_ report an error here. Something must have gone // However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data. // wrong while trying to parse DWARF data.
@@ -421,7 +375,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
if section.Flags&elf.SHF_ALLOC == 0 { if section.Flags&elf.SHF_ALLOC == 0 {
continue continue
} }
if packageSymbolRegexp.MatchString(symbol.Name) || symbol.Name == "__isr_vector" { if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
addresses = append(addresses, addressLine{ addresses = append(addresses, addressLine{
Address: symbol.Value, Address: symbol.Value,
Length: symbol.Size, Length: symbol.Size,
@@ -445,7 +399,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
Align: section.Addralign,
Type: memoryStack, Type: memoryStack,
}) })
} else { } else {
@@ -453,7 +406,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
Align: section.Addralign,
Type: memoryBSS, Type: memoryBSS,
}) })
} }
@@ -462,7 +414,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
Align: section.Addralign,
Type: memoryCode, Type: memoryCode,
}) })
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 { } else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
@@ -470,7 +421,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
Align: section.Addralign,
Type: memoryData, Type: memoryData,
}) })
} else if section.Type == elf.SHT_PROGBITS { } else if section.Type == elf.SHT_PROGBITS {
@@ -478,7 +428,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
Align: section.Addralign,
Type: memoryROData, Type: memoryROData,
}) })
} }
@@ -505,7 +454,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: uint64(section.Size), Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryCode, Type: memoryCode,
}) })
} else if sectionType == 1 { // S_ZEROFILL } else if sectionType == 1 { // S_ZEROFILL
@@ -513,7 +461,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: uint64(section.Size), Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryBSS, Type: memoryBSS,
}) })
} else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data) } else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data)
@@ -521,7 +468,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: uint64(section.Size), Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryROData, Type: memoryROData,
}) })
} else { } else {
@@ -529,7 +475,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: uint64(section.Size), Size: uint64(section.Size),
Align: uint64(section.Align),
Type: memoryData, Type: memoryData,
}) })
} }
@@ -613,7 +558,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Read DWARF information. The error is intentionally ignored. // Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF() data, _ := file.DWARF()
if data != nil { if data != nil {
addresses, err = readProgramSizeFromDWARF(data, 0, 0, true) addresses, err = readProgramSizeFromDWARF(data, 0, true)
if err != nil { if err != nil {
// However, _do_ report an error here. Something must have gone // However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data. // wrong while trying to parse DWARF data.
@@ -685,7 +630,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Read DWARF information. The error is intentionally ignored. // Read DWARF information. The error is intentionally ignored.
data, _ := file.DWARF() data, _ := file.DWARF()
if data != nil { if data != nil {
addresses, err = readProgramSizeFromDWARF(data, codeOffset, 0, true) addresses, err = readProgramSizeFromDWARF(data, codeOffset, true)
if err != nil { if err != nil {
// However, _do_ report an error here. Something must have gone // However, _do_ report an error here. Something must have gone
// wrong while trying to parse DWARF data. // wrong while trying to parse DWARF data.
@@ -845,18 +790,10 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
if addr < line.Address { if addr < line.Address {
// There is a gap: there is a space between the current and the // There is a gap: there is a space between the current and the
// previous line entry. // previous line entry.
// Check whether this is caused by alignment requirements. addSize("(unknown)", line.Address-addr, false)
addrAligned := (addr + line.Align - 1) &^ (line.Align - 1) if sizesDebug {
if line.Align > 1 && addrAligned >= line.Address { fmt.Printf("%08x..%08x %5d: unknown (gap)\n", addr, line.Address, line.Address-addr)
// It is, assume that's what causes the gap.
addSize("(padding)", line.Address-addr, true)
} else {
addSize("(unknown)", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
}
} }
addr = line.Address
} }
if addr > line.Address+line.Length { if addr > line.Address+line.Length {
// The current line is already covered by a previous line entry. // The current line is already covered by a previous line entry.
@@ -878,16 +815,9 @@ func readSection(section memorySection, addresses []addressLine, addSize func(st
} }
if addr < sectionEnd { if addr < sectionEnd {
// There is a gap at the end of the section. // There is a gap at the end of the section.
addrAligned := (addr + section.Align - 1) &^ (section.Align - 1) addSize("(unknown)", sectionEnd-addr, false)
if section.Align > 1 && addrAligned >= sectionEnd { if sizesDebug {
// The gap is caused by the section alignment. fmt.Printf("%08x..%08x %5d: unknown (end)\n", addr, sectionEnd, sectionEnd-addr)
// For example, if a .rodata section ends with a non-aligned string.
addSize("(padding)", sectionEnd-addr, true)
} else {
addSize("(unknown)", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
}
} }
} }
} }
@@ -903,15 +833,12 @@ func findPackagePath(path string, packagePathMap map[string]string) string {
// package, with a "C" prefix. For example: "C compiler-rt" for the // package, with a "C" prefix. For example: "C compiler-rt" for the
// compiler runtime library from LLVM. // compiler runtime library from LLVM.
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1] packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
} else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) {
packagePath = "C compiler-rt"
} else if packageSymbolRegexp.MatchString(path) { } else if packageSymbolRegexp.MatchString(path) {
// Parse symbol names like main$alloc or runtime$string. // Parse symbol names like main$alloc or runtime$string.
packagePath = path[:strings.LastIndex(path, "$")] packagePath = path[:strings.LastIndex(path, "$")]
} else if path == "__isr_vector" { } else if reflectDataRegexp.MatchString(path) {
packagePath = "C interrupt vector" // Parse symbol names like reflect.structTypesSidetable.
} else if path == "<Go type>" { packagePath = "Go reflect data"
packagePath = "Go types"
} else if path == "<Go interface assert>" { } else if path == "<Go interface assert>" {
// Interface type assert, generated by the interface lowering pass. // Interface type assert, generated by the interface lowering pass.
packagePath = "Go interface assert" packagePath = "Go interface assert"
-92
View File
@@ -1,92 +0,0 @@
package builder
import (
"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", 4484, 280, 0, 2252},
{"microbit", "examples/serial", 2732, 388, 8, 2256},
{"wioterminal", "examples/pininterrupt", 6016, 1484, 116, 6816},
// 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.
options := compileopts.Options{
Target: tc.target,
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(tc.path, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
// 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)
}
})
}
}
+28 -4
View File
@@ -1,4 +1,5 @@
//go:build byollvm //go:build byollvm
// +build byollvm
package builder package builder
@@ -12,7 +13,10 @@ import (
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h> #include <stdlib.h>
bool tinygo_clang_driver(int argc, char **argv); 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" import "C"
@@ -23,7 +27,16 @@ const hasBuiltinTools = true
// This version actually runs the tools because TinyGo was compiled while // This version actually runs the tools because TinyGo was compiled while
// linking statically with LLVM (with the byollvm build tag). // linking statically with LLVM (with the byollvm build tag).
func RunTool(tool string, args ...string) error { 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" {
linker = "mingw"
} else if args[0] == "-flavor" {
linker = args[1]
args = args[2:]
}
}
args = append([]string{"tinygo:" + tool}, args...)
var cflag *C.char var cflag *C.char
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag))) buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
@@ -39,8 +52,19 @@ func RunTool(tool string, args ...string) error {
switch tool { switch tool {
case "clang": case "clang":
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf)) ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
case "ld.lld", "wasm-ld": case "ld.lld":
ok = C.tinygo_link(C.int(len(args)), (**C.char)(buf)) 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: default:
return errors.New("unknown tool: " + tool) return errors.New("unknown tool: " + tool)
} }
+1
View File
@@ -1,4 +1,5 @@
//go:build !byollvm //go:build !byollvm
// +build !byollvm
package builder package builder
+22 -134
View File
@@ -1,21 +1,22 @@
package builder package builder
import ( import (
"bytes" "errors"
"fmt"
"go/scanner"
"go/token"
"os" "os"
"os/exec" "os/exec"
"regexp"
"strconv" "github.com/tinygo-org/tinygo/goenv"
"strings"
) )
// runCCompiler invokes a C compiler with the given arguments. // runCCompiler invokes a C compiler with the given arguments.
func runCCompiler(flags ...string) error { func runCCompiler(flags ...string) error {
if hasBuiltinTools { if hasBuiltinTools {
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if headerPath == "" {
return errors.New("could not locate Clang headers")
}
flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr cmd.Stderr = os.Stderr
@@ -28,135 +29,22 @@ func runCCompiler(flags ...string) error {
// link invokes a linker with the given name and flags. // link invokes a linker with the given name and flags.
func link(linker string, flags ...string) error { func link(linker string, flags ...string) error {
// We only support LLD. if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
if linker != "ld.lld" && linker != "wasm-ld" { // Run command with internal linker.
return fmt.Errorf("unexpected: linker %s should be ld.lld or wasm-ld", 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 // Fall back to external command.
if hasBuiltinTools { if _, ok := commands[linker]; ok {
cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...) return execCommand(linker, flags...)
} else {
name, err := LookupCommand(linker)
if err != nil {
return err
}
cmd = exec.Command(name, flags...)
} }
var buf bytes.Buffer
cmd := exec.Command(linker, flags...)
cmd.Stdout = os.Stdout cmd.Stdout = os.Stdout
cmd.Stderr = &buf cmd.Stderr = os.Stderr
err := cmd.Run() cmd.Dir = goenv.Get("TINYGOROOT")
if err != nil { return cmd.Run()
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: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[1]
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: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
region := matches[2]
n, err := strconv.ParseUint(matches[3], 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
} }
-81
View File
@@ -1,81 +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",
"-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) ([]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;
`
+19 -78
View File
@@ -25,20 +25,13 @@ import (
"golang.org/x/tools/go/ast/astutil" "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. // cgoPackage holds all CGo-related information of a package.
type cgoPackage struct { type cgoPackage struct {
generated *ast.File generated *ast.File
packageName string
cgoFiles []*ast.File
generatedPos token.Pos generatedPos token.Pos
errors []error errors []error
currentDir string // current working directory currentDir string // current working directory
packageDir string // full path to the package to process packageDir string // full path to the package to process
importPath string
fset *token.FileSet fset *token.FileSet
tokenFiles map[string]*token.File tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node definedGlobally map[string]ast.Node
@@ -46,15 +39,12 @@ type cgoPackage struct {
cflags []string // CFlags from #cgo lines cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte visitedFiles map[string][]byte
cgoHeaders []string
} }
// cgoFile holds information only for a single Go file (with one or more // cgoFile holds information only for a single Go file (with one or more
// `import "C"` statements). // `import "C"` statements).
type cgoFile struct { type cgoFile struct {
*cgoPackage *cgoPackage
file *ast.File
index int
defined map[string]ast.Node defined map[string]ast.Node
names map[string]clangCursor names map[string]clangCursor
} }
@@ -92,9 +82,6 @@ var cgoAliases = map[string]string{
"C.uint32_t": "uint32", "C.uint32_t": "uint32",
"C.uint64_t": "uint64", "C.uint64_t": "uint64",
"C.uintptr_t": "uintptr", "C.uintptr_t": "uintptr",
"C.float": "float32",
"C.double": "float64",
"C._Bool": "bool",
} }
// builtinAliases are handled specially because they only exist on the Go side // builtinAliases are handled specially because they only exist on the Go side
@@ -171,11 +158,9 @@ func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file // 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 // 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. // returns these in the []error slice but still modifies the AST.
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{ p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir, currentDir: dir,
importPath: importPath,
fset: fset, fset: fset,
tokenFiles: map[string]*token.File{}, tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{}, definedGlobally: map[string]ast.Node{},
@@ -209,7 +194,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// This is always a bug in the cgo package. // This is always a bug in the cgo package.
panic("unexpected error: " + err.Error()) 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 // If the Comments field is not set to nil, the go/format package will get
// confused about where comments should go. // confused about where comments should go.
p.generated.Comments = nil p.generated.Comments = nil
@@ -226,13 +210,13 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} }
} }
// Patch some types, for example *C.char in C.CString. // Patch some types, for example *C.char in C.CString.
cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker cf := p.newCGoFile()
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool { astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
return cf.walker(cursor, nil) return cf.walker(cursor, nil)
}, nil) }, nil)
// Find `import "C"` C fragments in the file. // Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files { for i, f := range files {
var cgoHeader string var cgoHeader string
for i := 0; i < len(f.Decls); i++ { for i := 0; i < len(f.Decls); i++ {
@@ -291,7 +275,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
cgoHeader += fragment cgoHeader += fragment
} }
p.cgoHeaders[i] = cgoHeader cgoHeaders[i] = cgoHeader
} }
// Define CFlags that will be used while parsing the package. // Define CFlags that will be used while parsing the package.
@@ -300,9 +284,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// have better alternatives anyway. // have better alternatives anyway.
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...) cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
cflagsForCGo = append(cflagsForCGo, p.cflags...) cflagsForCGo = append(cflagsForCGo, p.cflags...)
if clangHeaders != "" {
cflagsForCGo = append(cflagsForCGo, "-isystem", clangHeaders)
}
// Retrieve types such as C.int, C.longlong, etc from C. // Retrieve types such as C.int, C.longlong, etc from C.
p.newCGoFile(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) { p.newCGoFile().readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
gen := &ast.GenDecl{ gen := &ast.GenDecl{
TokPos: token.NoPos, TokPos: token.NoPos,
Tok: token.TYPE, Tok: token.TYPE,
@@ -316,15 +303,8 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Process CGo imports for each file. // Process CGo imports for each file.
for i, f := range files { for i, f := range files {
cf := p.newCGoFile(f, i) cf := p.newCGoFile()
// These types are aliased with the corresponding types in C. For cf.readNames(cgoHeaders[i], cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()), func(names map[string]clangCursor) {
// example, float in C is always 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) {
for _, name := range builtinAliases { for _, name := range builtinAliases {
// Names such as C.int should not be obtained from C. // Names such as C.int should not be obtained from C.
// This works around an issue in picolibc that has `#define int` // This works around an issue in picolibc that has `#define int`
@@ -340,14 +320,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Print the newly generated in-memory AST, for debugging. // Print the newly generated in-memory AST, for debugging.
//ast.Print(fset, p.generated) //ast.Print(fset, p.generated)
return p.cgoFiles, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
} }
func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile { func (p *cgoPackage) newCGoFile() *cgoFile {
return &cgoFile{ return &cgoFile{
cgoPackage: p, cgoPackage: p,
file: file,
index: index,
defined: make(map[string]ast.Node), defined: make(map[string]ast.Node),
names: make(map[string]clangCursor), names: make(map[string]clangCursor),
} }
@@ -963,9 +941,6 @@ func (p *cgoPackage) isEquivalentAST(a, b ast.Node) bool {
if !ok { if !ok {
return false return false
} }
if node == nil || b == nil {
return node == b
}
if len(node.List) != len(b.List) { if len(node.List) != len(b.List) {
return false return false
} }
@@ -1142,11 +1117,8 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
return alias return alias
} }
node := f.getASTDeclNode(name, found, iscall) node := f.getASTDeclNode(name, found, iscall)
if node, ok := node.(*ast.FuncDecl); ok { if _, ok := node.(*ast.FuncDecl); ok && !iscall {
if !iscall { return "C." + name + "$funcaddr"
return node.Name.Name + "$funcaddr"
}
return node.Name.Name
} }
return "C." + name return "C." + name
} }
@@ -1170,7 +1142,7 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
// Original cgo reports an error like // Original cgo reports an error like
// cgo: inconsistent definitions for C.myint // cgo: inconsistent definitions for C.myint
// which is far less helpful. // which is far less helpful.
f.addError(getPos(node), name+" defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type") f.addError(getPos(node), "defined previously at "+f.fset.Position(getPos(newNode)).String()+" with a different type")
} }
f.defined[name] = node f.defined[name] = node
return node return node
@@ -1178,39 +1150,11 @@ func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) as
// The declaration has no AST node. Create it now. // The declaration has no AST node. Create it now.
f.defined[name] = nil f.defined[name] = nil
node, extra := f.createASTNode(name, found) node, elaboratedType := f.createASTNode(name, found)
f.defined[name] = node f.defined[name] = node
f.definedGlobally[name] = node
switch node := node.(type) { switch node := node.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
if strings.HasPrefix(node.Doc.List[0].Text, "//export _Cgo_static_") {
// Static function. Only accessible in the current Go file.
globalName := strings.TrimPrefix(node.Doc.List[0].Text, "//export ")
// Make an alias. Normally this is done using the alias function
// attribute, but MacOS for some reason doesn't support this (even
// though the linker has support for aliases in the form of N_INDR).
// Therefore, create an actual function for MacOS.
var params []string
for _, param := range node.Type.Params.List {
params = append(params, param.Names[0].Name)
}
callInst := fmt.Sprintf("%s(%s);", name, strings.Join(params, ", "))
if node.Type.Results != nil {
callInst = "return " + callInst
}
aliasDeclaration := fmt.Sprintf(`
#ifdef __APPLE__
%s {
%s
}
#else
extern __typeof(%s) %s __attribute__((alias(%#v)));
#endif
`, extra.(string), callInst, name, globalName, name)
f.cgoHeaders[f.index] += "\n\n" + aliasDeclaration
} else {
// Regular (non-static) function.
f.definedGlobally[name] = node
}
f.generated.Decls = append(f.generated.Decls, node) f.generated.Decls = append(f.generated.Decls, node)
// Also add a declaration like the following: // Also add a declaration like the following:
// var C.foo$funcaddr unsafe.Pointer // var C.foo$funcaddr unsafe.Pointer
@@ -1218,7 +1162,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
Tok: token.VAR, Tok: token.VAR,
Specs: []ast.Spec{ Specs: []ast.Spec{
&ast.ValueSpec{ &ast.ValueSpec{
Names: []*ast.Ident{{Name: node.Name.Name + "$funcaddr"}}, Names: []*ast.Ident{{Name: "C." + name + "$funcaddr"}},
Type: &ast.SelectorExpr{ Type: &ast.SelectorExpr{
X: &ast.Ident{Name: "unsafe"}, X: &ast.Ident{Name: "unsafe"},
Sel: &ast.Ident{Name: "Pointer"}, Sel: &ast.Ident{Name: "Pointer"},
@@ -1227,10 +1171,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
}, },
}) })
case *ast.GenDecl: case *ast.GenDecl:
f.definedGlobally[name] = node
f.generated.Decls = append(f.generated.Decls, node) f.generated.Decls = append(f.generated.Decls, node)
case *ast.TypeSpec: case *ast.TypeSpec:
f.definedGlobally[name] = node
f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{ f.generated.Decls = append(f.generated.Decls, &ast.GenDecl{
Tok: token.TYPE, Tok: token.TYPE,
Specs: []ast.Spec{node}, Specs: []ast.Spec{node},
@@ -1244,8 +1186,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// If this is a struct or union it may need bitfields or union accessor // If this is a struct or union it may need bitfields or union accessor
// methods. // methods.
switch elaboratedType := extra.(type) { if elaboratedType != nil {
case *elaboratedTypeInfo:
// Add struct bitfields. // Add struct bitfields.
for _, bitfield := range elaboratedType.bitfields { for _, bitfield := range elaboratedType.bitfields {
f.createBitfieldGetter(bitfield, "C."+name) f.createBitfieldGetter(bitfield, "C."+name)
-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
}
}
+9 -101
View File
@@ -7,12 +7,10 @@ import (
"go/ast" "go/ast"
"go/format" "go/format"
"go/parser" "go/parser"
"go/scanner"
"go/token" "go/token"
"go/types" "go/types"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"runtime" "runtime"
"strings" "strings"
"testing" "testing"
@@ -23,15 +21,9 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
// normalizeResult normalizes Go source code that comes out of tests across // normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions. // platforms and Go versions.
func normalizeResult(t *testing.T, result string) string { func normalizeResult(result string) string {
result = strings.ReplaceAll(result, "\r\n", "\n") actual := strings.ReplaceAll(result, "\r\n", "\n")
return actual
// This changed to 'undefined:', in Go 1.20.
result = strings.ReplaceAll(result, ": undeclared name:", ": undefined:")
// Go 1.20 added a bit more detail
result = regexp.MustCompile(`(unknown field z in struct literal).*`).ReplaceAllString(result, "$1")
return result
} }
func TestCGo(t *testing.T) { func TestCGo(t *testing.T) {
@@ -56,7 +48,7 @@ func TestCGo(t *testing.T) {
} }
// Process the AST with CGo. // Process the AST with CGo.
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags) cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags, "")
// Check the AST for type errors. // Check the AST for type errors.
var typecheckErrors []error var typecheckErrors []error
@@ -67,7 +59,7 @@ func TestCGo(t *testing.T) {
Importer: simpleImporter{}, Importer: simpleImporter{},
Sizes: types.SizesFor("gccgo", "arm"), 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 { if err != nil && len(typecheckErrors) == 0 {
// Only report errors when no type errors are found (an // Only report errors when no type errors are found (an
// unexpected condition). // unexpected condition).
@@ -92,11 +84,11 @@ func TestCGo(t *testing.T) {
} }
buf.WriteString("\n") buf.WriteString("\n")
} }
err = format.Node(buf, fset, cgoFiles[0]) err = format.Node(buf, fset, cgoAST)
if err != nil { if err != nil {
t.Errorf("could not write out CGo AST: %v", err) t.Errorf("could not write out CGo AST: %v", err)
} }
actual := normalizeResult(t, buf.String()) actual := normalizeResult(buf.String())
// Read the file with the expected output, to compare against. // Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go") outfile := filepath.Join("testdata", name+".out.go")
@@ -123,84 +115,6 @@ func TestCGo(t *testing.T) {
} }
} }
func Test_cgoPackage_isEquivalentAST(t *testing.T) {
fieldA := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "a"}}
fieldB := &ast.Field{Type: &ast.BasicLit{Kind: token.STRING, Value: "b"}}
listOfFieldA := &ast.FieldList{List: []*ast.Field{fieldA}}
listOfFieldB := &ast.FieldList{List: []*ast.Field{fieldB}}
funcDeclA := &ast.FuncDecl{Name: &ast.Ident{Name: "a"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldA}}
funcDeclB := &ast.FuncDecl{Name: &ast.Ident{Name: "b"}, Type: &ast.FuncType{Params: &ast.FieldList{}, Results: listOfFieldB}}
funcDeclNoResults := &ast.FuncDecl{Name: &ast.Ident{Name: "C"}, Type: &ast.FuncType{Params: &ast.FieldList{}}}
testCases := []struct {
name string
a, b ast.Node
expected bool
}{
{
name: "both nil",
expected: true,
},
{
name: "not same type",
a: fieldA,
b: &ast.FuncDecl{},
expected: false,
},
{
name: "Field same",
a: fieldA,
b: fieldA,
expected: true,
},
{
name: "Field different",
a: fieldA,
b: fieldB,
expected: false,
},
{
name: "FuncDecl Type Results nil",
a: funcDeclNoResults,
b: funcDeclNoResults,
expected: true,
},
{
name: "FuncDecl Type Results same",
a: funcDeclA,
b: funcDeclA,
expected: true,
},
{
name: "FuncDecl Type Results different",
a: funcDeclA,
b: funcDeclB,
expected: false,
},
{
name: "FuncDecl Type Results a nil",
a: funcDeclNoResults,
b: funcDeclB,
expected: false,
},
{
name: "FuncDecl Type Results b nil",
a: funcDeclA,
b: funcDeclNoResults,
expected: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
p := &cgoPackage{}
if got := p.isEquivalentAST(tc.a, tc.b); tc.expected != got {
t.Errorf("expected %v, got %v", tc.expected, got)
}
})
}
}
// simpleImporter implements the types.Importer interface, but only allows // simpleImporter implements the types.Importer interface, but only allows
// importing the unsafe package. // importing the unsafe package.
type simpleImporter struct { type simpleImporter struct {
@@ -217,16 +131,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). // also fixes Windows path name issues (backward slashes).
func formatDiagnostic(err error) string { func formatDiagnostic(err error) string {
var msg string msg := err.Error()
switch err := err.(type) {
case scanner.Error:
msg = err.Pos.String() + ": " + err.Msg
default:
msg = err.Error()
}
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
// Fix Windows path slashes. // Fix Windows path slashes.
msg = strings.ReplaceAll(msg, "testdata\\", "testdata/") msg = strings.ReplaceAll(msg, "testdata\\", "testdata/")
+4 -41
View File
@@ -14,11 +14,6 @@ import (
var ( var (
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error) prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
precedences = map[token.Token]int{ precedences = map[token.Token]int{
token.OR: precedenceOr,
token.XOR: precedenceXor,
token.AND: precedenceAnd,
token.SHL: precedenceShift,
token.SHR: precedenceShift,
token.ADD: precedenceAdd, token.ADD: precedenceAdd,
token.SUB: precedenceAdd, token.SUB: precedenceAdd,
token.MUL: precedenceMul, token.MUL: precedenceMul,
@@ -27,13 +22,8 @@ var (
} }
) )
// See: https://en.cppreference.com/w/c/language/operator_precedence
const ( const (
precedenceLowest = iota + 1 precedenceLowest = iota + 1
precedenceOr
precedenceXor
precedenceAnd
precedenceShift
precedenceAdd precedenceAdd
precedenceMul precedenceMul
precedencePrefix precedencePrefix
@@ -86,7 +76,7 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] { for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
switch 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.ADD, token.SUB, token.MUL, token.QUO, token.REM:
t.Next() t.Next()
leftExpr, err = parseBinaryExpr(t, leftExpr) leftExpr, err = parseBinaryExpr(t, leftExpr)
} }
@@ -195,9 +185,7 @@ func (t *tokenizer) Next() {
t.curValue = t.peekValue t.curValue = t.peekValue
// Parse the next peek token. // 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 { for {
if len(t.buf) == 0 { if len(t.buf) == 0 {
t.peekToken = token.EOF t.peekToken = token.EOF
@@ -209,28 +197,9 @@ func (t *tokenizer) Next() {
// Skip whitespace. // Skip whitespace.
// Based on this source, not sure whether it represents C whitespace: // Based on this source, not sure whether it represents C whitespace:
// https://en.cppreference.com/w/cpp/string/byte/isspace // https://en.cppreference.com/w/cpp/string/byte/isspace
if t.peekPos != token.NoPos { t.peekPos++
t.peekPos++
}
t.buf = t.buf[1:] 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 c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
// 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 == '^':
// Single-character tokens. // Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators. // TODO: ++ (increment) and -- (decrement) operators.
switch c { switch c {
@@ -248,12 +217,6 @@ func (t *tokenizer) Next() {
t.peekToken = token.QUO t.peekToken = token.QUO
case '%': case '%':
t.peekToken = token.REM t.peekToken = token.REM
case '&':
t.peekToken = token.AND
case '|':
t.peekToken = token.OR
case '^':
t.peekToken = token.XOR
} }
t.peekValue = t.buf[:1] t.peekValue = t.buf[:1]
t.buf = t.buf[1:] t.buf = t.buf[1:]
-8
View File
@@ -37,14 +37,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`, `5 % 5`},
{`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)`}, {`(5/5)`, `(5 / 5)`},
{`1 - 2`, `1 - 2`}, {`1 - 2`, `1 - 2`},
{`1 - 2 + 3`, `1 - 2 + 3`}, {`1 - 2 + 3`, `1 - 2 + 3`},
+60 -116
View File
@@ -4,10 +4,7 @@ package cgo
// modification. It does not touch the AST itself. // modification. It does not touch the AST itself.
import ( import (
"bytes"
"crypto/sha256"
"crypto/sha512" "crypto/sha512"
"encoding/hex"
"fmt" "fmt"
"go/ast" "go/ast"
"go/scanner" "go/scanner"
@@ -46,8 +43,6 @@ typedef struct {
GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu); GoCXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu);
unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data); unsigned tinygo_clang_visitChildren(GoCXCursor parent, CXCursorVisitor visitor, CXClientData client_data);
CXString tinygo_clang_getCursorSpelling(GoCXCursor c); CXString tinygo_clang_getCursorSpelling(GoCXCursor c);
CXString tinygo_clang_getCursorPrettyPrinted(GoCXCursor c, CXPrintingPolicy Policy);
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(GoCXCursor c);
enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c); enum CXCursorKind tinygo_clang_getCursorKind(GoCXCursor c);
CXType tinygo_clang_getCursorType(GoCXCursor c); CXType tinygo_clang_getCursorType(GoCXCursor c);
GoCXCursor tinygo_clang_getTypeDeclaration(CXType t); GoCXCursor tinygo_clang_getTypeDeclaration(CXType t);
@@ -55,13 +50,11 @@ CXType tinygo_clang_getTypedefDeclUnderlyingType(GoCXCursor c);
CXType tinygo_clang_getCursorResultType(GoCXCursor c); CXType tinygo_clang_getCursorResultType(GoCXCursor c);
int tinygo_clang_Cursor_getNumArguments(GoCXCursor c); int tinygo_clang_Cursor_getNumArguments(GoCXCursor c);
GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i); GoCXCursor tinygo_clang_Cursor_getArgument(GoCXCursor c, unsigned i);
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(GoCXCursor c);
CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c); CXSourceLocation tinygo_clang_getCursorLocation(GoCXCursor c);
CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c); CXSourceRange tinygo_clang_getCursorExtent(GoCXCursor c);
CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c); CXTranslationUnit tinygo_clang_Cursor_getTranslationUnit(GoCXCursor c);
long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c); long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
CXType tinygo_clang_getEnumDeclIntegerType(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_isBitField(GoCXCursor c);
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
@@ -196,7 +189,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// Convert the AST node under the given Clang cursor to a Go AST node and return // Convert the AST node under the given Clang cursor to a Go AST node and return
// it. // it.
func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) { func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, *elaboratedTypeInfo) {
kind := C.tinygo_clang_getCursorKind(c) kind := C.tinygo_clang_getCursorKind(c)
pos := f.getCursorPosition(c) pos := f.getCursorPosition(c)
switch kind { switch kind {
@@ -207,43 +200,19 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Kind: ast.Fun, Kind: ast.Fun,
Name: "C." + name, Name: "C." + name,
} }
exportName := name
localName := name
var stringSignature string
if C.tinygo_clang_Cursor_getStorageClass(c) == C.CX_SC_Static {
// A static function is assigned a globally unique symbol name based
// on the file path (like _Cgo_static_2d09198adbf58f4f4655_foo) and
// has a different Go name in the form of C.foo!symbols.go instead
// of just C.foo.
path := f.importPath + "/" + filepath.Base(f.fset.File(f.file.Pos()).Name())
staticIDBuf := sha256.Sum256([]byte(path))
staticID := hex.EncodeToString(staticIDBuf[:10])
exportName = "_Cgo_static_" + staticID + "_" + name
localName = name + "!" + filepath.Base(path)
// Create a signature. This is necessary for MacOS to forward the
// call, because MacOS doesn't support aliases like ELF and PE do.
// (There is N_INDR but __attribute__((alias("..."))) doesn't work).
policy := C.tinygo_clang_getCursorPrintingPolicy(c)
defer C.clang_PrintingPolicy_dispose(policy)
C.clang_PrintingPolicy_setProperty(policy, C.CXPrintingPolicy_TerseOutput, 1)
stringSignature = getString(C.tinygo_clang_getCursorPrettyPrinted(c, policy))
stringSignature = strings.Replace(stringSignature, " "+name+"(", " "+exportName+"(", 1)
stringSignature = strings.TrimPrefix(stringSignature, "static ")
}
args := make([]*ast.Field, numArgs) args := make([]*ast.Field, numArgs)
decl := &ast.FuncDecl{ decl := &ast.FuncDecl{
Doc: &ast.CommentGroup{ Doc: &ast.CommentGroup{
List: []*ast.Comment{ List: []*ast.Comment{
{ {
Slash: pos - 1, Slash: pos - 1,
Text: "//export " + exportName, Text: "//export " + name,
}, },
}, },
}, },
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "C." + localName, Name: "C." + name,
Obj: obj, Obj: obj,
}, },
Type: &ast.FuncType{ Type: &ast.FuncType{
@@ -294,7 +263,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
} }
} }
obj.Decl = decl obj.Decl = decl
return decl, stringSignature return decl, nil
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos) typ := f.makeASTRecordType(c, pos)
typeName := "C." + name typeName := "C." + name
@@ -370,45 +339,42 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
case C.CXCursor_MacroDefinition: case C.CXCursor_MacroDefinition:
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c) 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) tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken var size C.size_t
var numTokens C.unsigned sourcePtr := C.clang_getFileContents(tu, file, &size)
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens) if endOffset >= C.uint(size) {
tokens := unsafe.Slice(rawTokens, numTokens) f.addError(pos, "internal error: end offset of macro lies after end of file")
// Convert this range of tokens back to source text. return nil, nil
// 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) + len(name)
} 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)
}
} }
C.clang_disposeTokens(tu, rawTokens, numTokens) source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
value := sourceBuf.String() 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. // Try to convert this #define into a Go constant expression.
tokenPos := token.NoPos expr, scannerError := parseConst(pos+token.Pos(len(name)), f.fset, value)
if pos != token.NoPos {
tokenPos = pos + token.Pos(len(name))
}
expr, scannerError := parseConst(tokenPos, f.fset, value)
if scannerError != nil { if scannerError != nil {
f.errors = append(f.errors, *scannerError) f.errors = append(f.errors, *scannerError)
return nil, nil return nil, nil
@@ -538,26 +504,6 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
return C.CXChildVisit_Continue return C.CXChildVisit_Continue
} }
// Get the precise location in the source code. Used for uniquely identifying
// source locations.
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
var column C.unsigned
C.clang_getFileLocation(clangLocation, &file, &line, &column, nil)
location := token.Position{
Filename: getString(C.clang_getFileName(file)),
Line: int(line),
Column: int(column),
}
if location.Filename == "" || location.Line == 0 {
// Not sure when this would happen, but protect from it anyway.
f.addError(pos, "could not find file/line information")
}
return location
}
// getCursorPosition returns a usable token.Pos from a libclang cursor. // getCursorPosition returns a usable token.Pos from a libclang cursor.
func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos { func (p *cgoPackage) getCursorPosition(cursor C.GoCXCursor) token.Pos {
return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor)) return p.getClangLocationPosition(C.tinygo_clang_getCursorLocation(cursor), C.tinygo_clang_Cursor_getTranslationUnit(cursor))
@@ -593,13 +539,6 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
f := p.fset.AddFile(filename, -1, int(size)) f := p.fset.AddFile(filename, -1, int(size))
f.SetLines(lines) f.SetLines(lines)
p.tokenFiles[filename] = f 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] positionFile := p.tokenFiles[filename]
@@ -646,6 +585,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. // addErrorAt is a utility function to add an error to the list of errors.
func (p *cgoPackage) addErrorAt(position token.Position, msg string) { func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
if filepath.IsAbs(position.Filename) {
// Relative paths for readability, like other Go parser errors.
relpath, err := filepath.Rel(p.currentDir, position.Filename)
if err == nil {
position.Filename = relpath
}
}
p.errors = append(p.errors, scanner.Error{ p.errors = append(p.errors, scanner.Error{
Pos: position, Pos: position,
Msg: msg, Msg: msg,
@@ -658,19 +604,8 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr { func (f *cgoFile) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
// Strip typedefs, if any. // Strip typedefs, if any.
underlyingType := typ 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 { if underlyingType.kind == C.CXType_Typedef {
c := C.tinygo_clang_getTypeDeclaration(underlyingType) c := C.tinygo_clang_getTypeDeclaration(typ)
underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c) underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c)
// TODO: support a chain of typedefs. At the moment, it seems to get // 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 // stuck in an endless loop when trying to get to the most underlying
@@ -804,8 +739,6 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
return f.makeASTType(underlying, pos) return f.makeASTType(underlying, pos)
case C.CXType_Enum: case C.CXType_Enum:
return f.makeASTType(underlying, pos) return f.makeASTType(underlying, pos)
case C.CXType_Typedef:
return f.makeASTType(underlying, pos)
default: default:
typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(underlying.kind))
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling)) f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
@@ -824,9 +757,22 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
// makeASTRecordType will create an appropriate error. // makeASTRecordType will create an appropriate error.
cgoRecordPrefix = "record_" cgoRecordPrefix = "record_"
} }
if name == "" || C.tinygo_clang_Cursor_isAnonymous(cursor) != 0 { if name == "" {
// Anonymous record, probably inside a typedef. // Anonymous record, probably inside a typedef.
location := f.getUniqueLocationID(pos, cursor) clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
var column C.unsigned
C.clang_getFileLocation(clangLocation, &file, &line, &column, nil)
location := token.Position{
Filename: getString(C.clang_getFileName(file)),
Line: int(line),
Column: int(column),
}
if location.Filename == "" || location.Line == 0 {
// Not sure when this would happen, but protect from it anyway.
f.addError(pos, "could not find file/line information")
}
name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location) name = f.getUnnamedDeclName("_Ctype_"+cgoRecordPrefix+"__", location)
} else { } else {
name = cgoRecordPrefix + name name = cgoRecordPrefix + name
@@ -839,9 +785,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
cursor := C.tinygo_clang_getTypeDeclaration(typ) cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor)) name := getString(C.tinygo_clang_getCursorSpelling(cursor))
if name == "" { if name == "" {
// Anonymous enum, probably inside a typedef. name = f.getUnnamedDeclName("_Ctype_enum___", cursor)
location := f.getUniqueLocationID(pos, cursor)
name = f.getUnnamedDeclName("_Ctype_enum___", location)
} else { } else {
name = "enum_" + name name = "enum_" + name
} }
+16
View File
@@ -0,0 +1,16 @@
//go:build !byollvm
// +build !byollvm
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-14/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@14/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@14/include
#cgo freebsd CFLAGS: -I/usr/local/llvm14/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-14/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@14/lib -lclang -lffi
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@14/lib -lclang -lffi
#cgo freebsd LDFLAGS: -L/usr/local/llvm14/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm15
package cgo
/*
#cgo linux CFLAGS: -I/usr/lib/llvm-15/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@15/include
#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 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 && !llvm15 && !llvm16 && !llvm17
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"
+1 -17
View File
@@ -17,14 +17,6 @@ CXString tinygo_clang_getCursorSpelling(CXCursor c) {
return clang_getCursorSpelling(c); return clang_getCursorSpelling(c);
} }
CXString tinygo_clang_getCursorPrettyPrinted(CXCursor c, CXPrintingPolicy policy) {
return clang_getCursorPrettyPrinted(c, policy);
}
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(CXCursor c) {
return clang_getCursorPrintingPolicy(c);
}
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) { enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
return clang_getCursorKind(c); return clang_getCursorKind(c);
} }
@@ -53,10 +45,6 @@ CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
return clang_Cursor_getArgument(c, i); return clang_Cursor_getArgument(c, i);
} }
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) {
return clang_Cursor_getStorageClass(c);
}
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) { CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
return clang_getCursorLocation(c); return clang_getCursorLocation(c);
} }
@@ -77,10 +65,6 @@ CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
return clang_getEnumDeclIntegerType(c); return clang_getEnumDeclIntegerType(c);
} }
unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
return clang_Cursor_isAnonymous(c);
}
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) { unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
return clang_Cursor_isBitField(c); return clang_Cursor_isBitField(c);
} }
+1 -1
View File
@@ -142,7 +142,7 @@ var validLinkerFlags = []*regexp.Regexp{
re(`-L([^@\-].*)`), re(`-L([^@\-].*)`),
re(`-O`), re(`-O`),
re(`-O([^@\-].*)`), re(`-O([^@\-].*)`),
re(`--export=([^@\-].*)`), re(`--export=(.+)`), // for wasm-ld
re(`-f(no-)?(pic|PIC|pie|PIE)`), re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?openmp(-simd)?`), re(`-f(no-)?openmp(-simd)?`),
re(`-fsanitize=([^@\-].*)`), re(`-fsanitize=([^@\-].*)`),
-1
View File
@@ -108,7 +108,6 @@ var goodLinkerFlags = [][]string{
{"-Fbar"}, {"-Fbar"},
{"-lbar"}, {"-lbar"},
{"-Lbar"}, {"-Lbar"},
{"--export=my_symbol"},
{"-fpic"}, {"-fpic"},
{"-fno-pic"}, {"-fno-pic"},
{"-fPIC"}, {"-fPIC"},
-13
View File
@@ -13,14 +13,10 @@ typedef someType noType; // undefined type
#define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_1 5) // invalid const syntax
#define SOME_CONST_2 6) // const not used (so no error) #define SOME_CONST_2 6) // const not used (so no error)
#define SOME_CONST_3 1234 // const too large for byte #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 // #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" import "C"
// #warning another warning // #warning another warning
@@ -28,7 +24,6 @@ import "C"
// Make sure that errors for the following lines won't change with future // Make sure that errors for the following lines won't change with future
// additions to the CGo preamble. // additions to the CGo preamble.
//
//line errors.go:100 //line errors.go:100
var ( var (
// constant too large // constant too large
@@ -43,12 +38,4 @@ var (
_ byte = C.SOME_CONST_3 _ byte = C.SOME_CONST_3
_ = C.SOME_CONST_4 _ = 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
) )
+6 -13
View File
@@ -1,22 +1,16 @@
// CGo errors: // CGo errors:
// testdata/errors.go:4:2: warning: some warning // testdata/errors.go:4:2: warning: some warning
// testdata/errors.go:11:9: error: unknown type name 'someType' // testdata/errors.go:11:9: error: unknown type name 'someType'
// testdata/errors.go:26:5: warning: another warning // testdata/errors.go:22:5: warning: another warning
// testdata/errors.go:13:23: unexpected token ), expected end of expression // testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:21:26: unexpected token ), expected end of expression // testdata/errors.go:19:26: unexpected token ), expected end of expression
// testdata/errors.go:16:33: unexpected token ), expected end of expression
// testdata/errors.go:17:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// Type checking errors after CGo processing: // Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.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:105: unknown field z in struct literal
// testdata/errors.go:108: undefined: C.SOME_CONST_1 // testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:110: 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 // testdata/errors.go:112: undeclared name: C.SOME_CONST_4
// testdata/errors.go:114: undefined: C.SOME_CONST_b
// testdata/errors.go:116: undefined: C.SOME_CONST_startspace
// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid
package main package main
@@ -57,11 +51,10 @@ type (
C.longlong int64 C.longlong int64
C.ulonglong uint64 C.ulonglong uint64
) )
type C.struct_point_t struct { type C._Ctype_struct___0 struct {
x C.int x C.int
y C.int y C.int
} }
type C.point_t = C.struct_point_t type C.point_t = C._Ctype_struct___0
const C.SOME_CONST_3 = 1234 const C.SOME_CONST_3 = 1234
const C.SOME_PARAM_CONST_valid = 3 + 4
-2
View File
@@ -5,7 +5,6 @@ package main
int foo(int a, int b); int foo(int a, int b);
void variadic0(); void variadic0();
void variadic2(int x, int y, ...); void variadic2(int x, int y, ...);
static void staticfunc(int x);
// Global variable signatures. // Global variable signatures.
extern int someValue; extern int someValue;
@@ -17,7 +16,6 @@ func accessFunctions() {
C.foo(3, 4) C.foo(3, 4)
C.variadic0() C.variadic0()
C.variadic2(3, 5) C.variadic2(3, 5)
C.staticfunc(3)
} }
func accessGlobals() { func accessGlobals() {
-5
View File
@@ -55,10 +55,5 @@ func C.variadic2(x C.int, y C.int)
var C.variadic2$funcaddr unsafe.Pointer var C.variadic2$funcaddr unsafe.Pointer
//export _Cgo_static_173c95a79b6df1980521_staticfunc
func C.staticfunc!symbols.go(x C.int)
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
//go:extern someValue //go:extern someValue
var C.someValue C.int var C.someValue C.int
-4
View File
@@ -112,10 +112,6 @@ import "C"
import "C" import "C"
var ( var (
// aliases
_ C.float
_ C.double
// Simple typedefs. // Simple typedefs.
_ C.myint _ C.myint
+36 -34
View File
@@ -38,11 +38,11 @@ type (
C.ulonglong uint64 C.ulonglong uint64
) )
type C.myint = C.int type C.myint = C.int
type C.struct_point2d_t struct { type C._Ctype_struct___0 struct {
x C.int x C.int
y C.int y C.int
} }
type C.point2d_t = C.struct_point2d_t type C.point2d_t = C._Ctype_struct___0
type C.struct_point3d struct { type C.struct_point3d struct {
x C.int x C.int
y C.int y C.int
@@ -55,19 +55,21 @@ type C.struct_type1 struct {
___type C.int ___type C.int
} }
type C.struct_type2 struct{ _type C.int } type C.struct_type2 struct{ _type C.int }
type C.union_union1_t struct{ i C.int } type C._Ctype_union___1 struct{ i C.int }
type C.union1_t = C.union_union1_t type C.union1_t = C._Ctype_union___1
type C.union_union3_t struct{ $union uint64 } type C._Ctype_union___2 struct{ $union uint64 }
func (union *C.union_union3_t) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C._Ctype_union___2) unionfield_i() *C.int {
func (union *C.union_union3_t) unionfield_d() *float64 { return (*C.int)(unsafe.Pointer(&union.$union))
}
func (union *C._Ctype_union___2) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union)) return (*float64)(unsafe.Pointer(&union.$union))
} }
func (union *C.union_union3_t) unionfield_s() *C.short { func (union *C._Ctype_union___2) unionfield_s() *C.short {
return (*C.short)(unsafe.Pointer(&union.$union)) return (*C.short)(unsafe.Pointer(&union.$union))
} }
type C.union3_t = C.union_union3_t type C.union3_t = C._Ctype_union___2
type C.union_union2d struct{ $union [2]uint64 } type C.union_union2d struct{ $union [2]uint64 }
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) } func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
@@ -76,50 +78,50 @@ func (union *C.union_union2d) unionfield_d() *[2]float64 {
} }
type C.union2d_t = C.union_union2d type C.union2d_t = C.union_union2d
type C.union_unionarray_t struct{ arr [10]C.uchar } type C._Ctype_union___3 struct{ arr [10]C.uchar }
type C.unionarray_t = C.union_unionarray_t type C.unionarray_t = C._Ctype_union___3
type C._Ctype_union___0 struct{ $union [3]uint32 } type C._Ctype_union___5 struct{ $union [3]uint32 }
func (union *C._Ctype_union___0) unionfield_area() *C.point2d_t { func (union *C._Ctype_union___5) unionfield_area() *C.point2d_t {
return (*C.point2d_t)(unsafe.Pointer(&union.$union)) return (*C.point2d_t)(unsafe.Pointer(&union.$union))
} }
func (union *C._Ctype_union___0) unionfield_solid() *C.point3d_t { func (union *C._Ctype_union___5) unionfield_solid() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union)) return (*C.point3d_t)(unsafe.Pointer(&union.$union))
} }
type C.struct_struct_nested_t struct { type C._Ctype_struct___4 struct {
begin C.point2d_t begin C.point2d_t
end C.point2d_t end C.point2d_t
tag C.int tag C.int
coord C._Ctype_union___0 coord C._Ctype_union___5
} }
type C.struct_nested_t = C.struct_struct_nested_t type C.struct_nested_t = C._Ctype_struct___4
type C.union_union_nested_t struct{ $union [2]uint64 } type C._Ctype_union___6 struct{ $union [2]uint64 }
func (union *C.union_union_nested_t) unionfield_point() *C.point3d_t { func (union *C._Ctype_union___6) unionfield_point() *C.point3d_t {
return (*C.point3d_t)(unsafe.Pointer(&union.$union)) return (*C.point3d_t)(unsafe.Pointer(&union.$union))
} }
func (union *C.union_union_nested_t) unionfield_array() *C.unionarray_t { func (union *C._Ctype_union___6) unionfield_array() *C.unionarray_t {
return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
} }
func (union *C.union_union_nested_t) unionfield_thing() *C.union3_t { func (union *C._Ctype_union___6) unionfield_thing() *C.union3_t {
return (*C.union3_t)(unsafe.Pointer(&union.$union)) return (*C.union3_t)(unsafe.Pointer(&union.$union))
} }
type C.union_nested_t = C.union_union_nested_t type C.union_nested_t = C._Ctype_union___6
type C.enum_option = C.int type C.enum_option = C.int
type C.option_t = C.enum_option type C.option_t = C.enum_option
type C.enum_option2_t = C.uint type C._Ctype_enum___7 = C.uint
type C.option2_t = C.enum_option2_t type C.option2_t = C._Ctype_enum___7
type C.struct_types_t struct { type C._Ctype_struct___8 struct {
f float32 f float32
d float64 d float64
ptr *C.int ptr *C.int
} }
type C.types_t = C.struct_types_t type C.types_t = C._Ctype_struct___8
type C.myIntArray = [10]C.int type C.myIntArray = [10]C.int
type C.struct_bitfield_t struct { type C._Ctype_struct___9 struct {
start C.uchar start C.uchar
__bitfield_1 C.uchar __bitfield_1 C.uchar
@@ -127,21 +129,21 @@ type C.struct_bitfield_t struct {
e C.uchar e C.uchar
} }
func (s *C.struct_bitfield_t) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f } func (s *C._Ctype_struct___9) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *C.struct_bitfield_t) set_bitfield_a(value C.uchar) { func (s *C._Ctype_struct___9) set_bitfield_a(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
} }
func (s *C.struct_bitfield_t) bitfield_b() C.uchar { func (s *C._Ctype_struct___9) bitfield_b() C.uchar {
return s.__bitfield_1 >> 5 & 0x1 return s.__bitfield_1 >> 5 & 0x1
} }
func (s *C.struct_bitfield_t) set_bitfield_b(value C.uchar) { func (s *C._Ctype_struct___9) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
} }
func (s *C.struct_bitfield_t) bitfield_c() C.uchar { func (s *C._Ctype_struct___9) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6 return s.__bitfield_1 >> 6
} }
func (s *C.struct_bitfield_t) set_bitfield_c(value C.uchar, func (s *C._Ctype_struct___9) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type C.bitfield_t = C.struct_bitfield_t type C.bitfield_t = C._Ctype_struct___9
+60 -111
View File
@@ -19,6 +19,7 @@ type Config struct {
Options *Options Options *Options
Target *TargetSpec Target *TargetSpec
GoMinorVersion int GoMinorVersion int
ClangHeaders string // Clang built-in header include path
TestConfig TestConfig TestConfig TestConfig
} }
@@ -46,12 +47,6 @@ func (c *Config) Features() string {
return c.Target.Features + "," + c.Options.LLVMFeatures return c.Target.Features + "," + c.Options.LLVMFeatures
} }
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
// string is returned if the target doesn't specify an ABI.
func (c *Config) ABI() string {
return c.Target.ABI
}
// GOOS returns the GOOS of the target. This might not always be the actual OS: // GOOS returns the GOOS of the target. This might not always be the actual OS:
// for example, bare-metal targets will usually pretend to be linux to get the // for example, bare-metal targets will usually pretend to be linux to get the
// standard library to compile. // standard library to compile.
@@ -60,7 +55,7 @@ func (c *Config) GOOS() string {
} }
// GOARCH returns the GOARCH of the target. This might not always be the actual // 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. // library so such targets will usually pretend to be linux/arm.
func (c *Config) GOARCH() string { func (c *Config) GOARCH() string {
return c.Target.GOARCH return c.Target.GOARCH
@@ -72,22 +67,9 @@ func (c *Config) GOARM() string {
return c.Options.GOARM 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. // BuildTags returns the complete list of build tags used during this build.
func (c *Config) BuildTags() []string { func (c *Config) BuildTags() []string {
tags := append([]string(nil), c.Target.BuildTags...) // copy slice (avoid a race) tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
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
for i := 1; i <= c.GoMinorVersion; i++ { for i := 1; i <= c.GoMinorVersion; i++ {
tags = append(tags, fmt.Sprintf("go1.%d", i)) tags = append(tags, fmt.Sprintf("go1.%d", i))
} }
@@ -95,8 +77,14 @@ func (c *Config) BuildTags() []string {
return tags 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 // GC returns the garbage collection strategy in use on this platform. Valid
// values are "none", "leaking", "conservative" and "precise". // values are "none", "leaking", and "conservative".
func (c *Config) GC() string { func (c *Config) GC() string {
if c.Options.GC != "" { if c.Options.GC != "" {
return c.Options.GC return c.Options.GC
@@ -111,7 +99,7 @@ func (c *Config) GC() string {
// that can be traced by the garbage collector. // that can be traced by the garbage collector.
func (c *Config) NeedsStackObjects() bool { func (c *Config) NeedsStackObjects() bool {
switch c.GC() { switch c.GC() {
case "conservative", "custom", "precise": case "conservative":
for _, tag := range c.BuildTags() { for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" { if tag == "tinygo.wasm" {
return true return true
@@ -151,18 +139,18 @@ func (c *Config) Serial() string {
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner // OptLevels returns the optimization level (0-2), size level (0-2), and inliner
// threshold as used in the LLVM optimization pipeline. // 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 { switch c.Options.Opt {
case "none", "0": case "none", "0":
return "O0", 0, 0 return 0, 0, 0 // -O0
case "1": case "1":
return "O1", 1, 0 return 1, 0, 0 // -O1
case "2": case "2":
return "O2", 2, 0 return 2, 0, 225 // -O2
case "s": case "s":
return "Os", 2, 1 return 2, 1, 225 // -Os
case "z": case "z":
return "Oz", 2, 2 // default return 2, 2, 5 // -Oz, default
default: default:
// This is not shown to the user: valid choices are already checked as // This is not shown to the user: valid choices are already checked as
// part of Options.Verify(). It is here as a sanity check. // part of Options.Verify(). It is here as a sanity check.
@@ -187,22 +175,23 @@ func (c *Config) AutomaticStackSize() bool {
return false return false
} }
// StackSize returns the default stack size to be used for goroutines, if the // UseThinLTO returns whether ThinLTO should be used for the given target. Some
// stack size could not be determined automatically at compile time. // targets (such as wasm) are not yet supported.
func (c *Config) StackSize() uint64 { // We should try and remove as many exceptions as possible in the future, so
if c.Options.StackSize != 0 { // that this optimization can be applied in more places.
return c.Options.StackSize func (c *Config) UseThinLTO() bool {
parts := strings.Split(c.Triple(), "-")
if parts[0] == "wasm32" {
// wasm-ld doesn't seem to support ThinLTO yet.
return false
} }
return c.Target.DefaultStackSize if parts[0] == "avr" || parts[0] == "xtensa" {
} // These use external (GNU) linkers which might perhaps support ThinLTO
// through a plugin, but it's too much hassle to set up.
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap. return false
func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() > 32*1024 {
return 1024
} }
// Other architectures support ThinLTO.
return 256 return true
} }
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that // RP2040BootPatch returns whether the RP2040 boot patch should be applied that
@@ -214,26 +203,14 @@ func (c *Config) RP2040BootPatch() bool {
return false 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 {
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"
}
return arch
}
// MuslArchitecture returns the architecture name as used in musl libc. It is // 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. // usually the same as the first part of the LLVM triple, but not always.
func MuslArchitecture(triple string) string { func MuslArchitecture(triple string) string {
return CanonicalArchName(triple) arch := strings.Split(triple, "-")[0]
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
arch = "arm"
}
return arch
} }
// LibcPath returns the path to the libc directory. The libc path will be either // LibcPath returns the path to the libc directory. The libc path will be either
@@ -244,12 +221,6 @@ func (c *Config) LibcPath(name string) (path string, precompiled bool) {
if c.CPU() != "" { if c.CPU() != "" {
archname += "-" + c.CPU() archname += "-" + c.CPU()
} }
if c.ABI() != "" {
archname += "-" + c.ABI()
}
if c.Target.SoftFloat {
archname += "-softfloat"
}
// Try to load a precompiled library. // Try to load a precompiled library.
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name) precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
@@ -289,35 +260,24 @@ func (c *Config) DefaultBinaryExtension() string {
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo // CFlags returns the flags to pass to the C compiler. This is necessary for CGo
// preprocessing. // preprocessing.
func (c *Config) CFlags(libclang bool) []string { func (c *Config) CFlags() []string {
var cflags []string var cflags []string
for _, flag := range c.Target.CFlags { for _, flag := range c.Target.CFlags {
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT"))) 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).
cflags = append(cflags,
"-resource-dir="+resourceDir,
)
}
switch c.Target.Libc { switch c.Target.Libc {
case "darwin-libSystem": case "darwin-libSystem":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
cflags = append(cflags, cflags = append(cflags,
"-nostdlibinc", "--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"),
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
) )
case "picolibc": case "picolibc":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc") picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc") path, _ := c.LibcPath("picolibc")
cflags = append(cflags, cflags = append(cflags,
"-nostdlibinc", "--sysroot="+path,
"-isystem", filepath.Join(path, "include"), "-isystem", filepath.Join(path, "include"), // necessary for Xtensa
"-isystem", filepath.Join(picolibcDir, "include"), "-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"), "-isystem", filepath.Join(picolibcDir, "tinystdio"),
) )
@@ -333,17 +293,12 @@ func (c *Config) CFlags(libclang bool) []string {
) )
case "wasi-libc": case "wasi-libc":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
cflags = append(cflags, cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
"-nostdlibinc",
"-isystem", root+"/lib/wasi-libc/sysroot/include")
case "wasmbuiltins":
// nothing to add (library is purely for builtins)
case "mingw-w64": case "mingw-w64":
root := goenv.Get("TINYGOROOT") root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("mingw-w64") path, _ := c.LibcPath("mingw-w64")
cflags = append(cflags, cflags = append(cflags,
"-nostdlibinc", "--sysroot="+path,
"-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", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"), "-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT", "-D_UCRT",
@@ -356,7 +311,7 @@ func (c *Config) CFlags(libclang bool) []string {
panic("unknown libc: " + c.Target.Libc) panic("unknown libc: " + c.Target.Libc)
} }
// Always emit debug information. It is optionally stripped at link time. // Always emit debug information. It is optionally stripped at link time.
cflags = append(cflags, "-gdwarf-4") cflags = append(cflags, "-g")
// Use the same optimization level as TinyGo. // Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt) cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple. // Set the LLVM target triple.
@@ -374,10 +329,6 @@ func (c *Config) CFlags(libclang bool) []string {
cflags = append(cflags, "-mcpu="+c.Target.CPU) cflags = append(cflags, "-mcpu="+c.Target.CPU)
} }
} }
// Set the -mabi flag, if needed.
if c.ABI() != "" {
cflags = append(cflags, "-mabi="+c.ABI())
}
return cflags return cflags
} }
@@ -417,8 +368,8 @@ func (c *Config) VerifyIR() bool {
} }
// Debug returns whether debug (DWARF) information should be retained by the // Debug returns whether debug (DWARF) information should be retained by the
// linker. By default, debug information is retained, but it can be removed // linker. By default, debug information is retained but it can be removed with
// with the -no-debug flag. // the -no-debug flag.
func (c *Config) Debug() bool { func (c *Config) Debug() bool {
return c.Options.Debug return c.Options.Debug
} }
@@ -462,7 +413,7 @@ func (c *Config) BinaryFormat(ext string) string {
// Programmer returns the flash method and OpenOCD interface name given a // Programmer returns the flash method and OpenOCD interface name given a
// particular configuration. It may either be all configured in the target JSON // particular configuration. It may either be all configured in the target JSON
// file or be modified using the -programmer command-line option. // file or be modified using the -programmmer command-line option.
func (c *Config) Programmer() (method, openocdInterface string) { func (c *Config) Programmer() (method, openocdInterface string) {
switch c.Options.Programmer { switch c.Options.Programmer {
case "": case "":
@@ -502,6 +453,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport) return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
} }
args = []string{"-f", "interface/" + openocdInterface + ".cfg"} args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
if c.Target.OpenOCDTransport != "" { if c.Target.OpenOCDTransport != "" {
transport := c.Target.OpenOCDTransport transport := c.Target.OpenOCDTransport
if transport == "swd" { if transport == "swd" {
@@ -513,9 +467,6 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
args = append(args, "-c", "transport select "+transport) args = append(args, "-c", "transport select "+transport)
} }
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg") args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
for _, cmd := range c.Target.OpenOCDCommands {
args = append(args, "-c", cmd)
}
return args, nil return args, nil
} }
@@ -538,6 +489,15 @@ func (c *Config) RelocationModel() string {
return "static" return "static"
} }
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
// the value is overridden by `-wasm-abi` flag if it is provided
func (c *Config) WasmAbi() string {
if c.Options.WasmAbi != "" {
return c.Options.WasmAbi
}
return c.Target.WasmAbi
}
// EmulatorName is a shorthand to get the command for this emulator, something // EmulatorName is a shorthand to get the command for this emulator, something
// like qemu-system-arm or simavr. // like qemu-system-arm or simavr.
func (c *Config) EmulatorName() string { func (c *Config) EmulatorName() string {
@@ -571,8 +531,6 @@ func (c *Config) Emulator(format, binary string) ([]string, error) {
var emulator []string var emulator []string
for _, s := range parts { for _, s := range parts {
s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT")) s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT"))
// Allow replacement of what's usually /tmp except notably Windows.
s = strings.ReplaceAll(s, "{tmpDir}", os.TempDir())
s = strings.ReplaceAll(s, "{"+format+"}", binary) s = strings.ReplaceAll(s, "{"+format+"}", binary)
emulator = append(emulator, s) emulator = append(emulator, s)
} }
@@ -581,14 +539,5 @@ func (c *Config) Emulator(format, binary string) ([]string, error) {
type TestConfig struct { type TestConfig struct {
CompileTestBinary bool CompileTestBinary bool
CompileOnly bool // TODO: Filter the test functions to run, include verbose flag, etc
Verbose bool
Short bool
RunRegexp string
SkipRegexp string
Count *int
BenchRegexp string
BenchTime string
BenchMem bool
Shuffle string
} }
+4 -11
View File
@@ -8,9 +8,9 @@ import (
) )
var ( var (
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"} validGCOptions = []string{"none", "leaking", "conservative"}
validSchedulerOptions = []string{"none", "tasks", "asyncify"} validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"} validSerialOptions = []string{"none", "uart", "usb"}
validPrintSizeOptions = []string{"none", "short", "full"} validPrintSizeOptions = []string{"none", "short", "full"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
@@ -23,21 +23,17 @@ type Options struct {
GOOS string // environment variable GOOS string // environment variable
GOARCH string // environment variable GOARCH string // environment variable
GOARM string // environment variable (only used with GOARCH=arm) 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 Target string
Opt string Opt string
GC string GC string
PanicStrategy string PanicStrategy string
Scheduler string Scheduler string
StackSize uint64 // goroutine stack size (if none could be automatically determined)
Serial string Serial string
Work bool // -work flag to print temporary build directory Work bool // -work flag to print temporary build directory
InterpTimeout time.Duration InterpTimeout time.Duration
PrintIR bool PrintIR bool
DumpSSA bool DumpSSA bool
VerifyIR bool VerifyIR bool
SkipDWARF bool
PrintCommands func(cmd string, args ...string) `json:"-"` PrintCommands func(cmd string, args ...string) `json:"-"`
Semaphore chan struct{} `json:"-"` // -p flag controls cap Semaphore chan struct{} `json:"-"` // -p flag controls cap
Debug bool Debug bool
@@ -45,17 +41,14 @@ type Options struct {
PrintAllocs *regexp.Regexp // regexp string PrintAllocs *regexp.Regexp // regexp string
PrintStacks bool PrintStacks bool
Tags []string Tags []string
WasmAbi string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
TestConfig TestConfig TestConfig TestConfig
Programmer string Programmer string
OpenOCDCommands []string OpenOCDCommands []string
LLVMFeatures string LLVMFeatures string
Directory string
PrintJSON bool 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
} }
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
+1 -7
View File
@@ -9,7 +9,7 @@ import (
func TestVerifyOptions(t *testing.T) { func TestVerifyOptions(t *testing.T) {
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`) expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative`)
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`) 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`) 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`) expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
@@ -48,12 +48,6 @@ func TestVerifyOptions(t *testing.T) {
GC: "conservative", GC: "conservative",
}, },
}, },
{
name: "GCOptionCustom",
opts: compileopts.Options{
GC: "custom",
},
},
{ {
name: "InvalidSchedulerOption", name: "InvalidSchedulerOption",
opts: compileopts.Options{ opts: compileopts.Options{
+114 -297
View File
@@ -23,48 +23,45 @@ import (
// https://doc.rust-lang.org/nightly/nightly-rustc/rustc_target/spec/struct.TargetOptions.html // 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 // https://github.com/shepmaster/rust-arduino-blink-led-no-core-with-cargo/blob/master/blink/arduino.json
type TargetSpec struct { type TargetSpec struct {
Inherits []string `json:"inherits,omitempty"` Inherits []string `json:"inherits"`
Triple string `json:"llvm-target,omitempty"` Triple string `json:"llvm-target"`
CPU string `json:"cpu,omitempty"` CPU string `json:"cpu"`
ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag Features string `json:"features"`
Features string `json:"features,omitempty"` GOOS string `json:"goos"`
GOOS string `json:"goos,omitempty"` GOARCH string `json:"goarch"`
GOARCH string `json:"goarch,omitempty"` BuildTags []string `json:"build-tags"`
SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) GC string `json:"gc"`
BuildTags []string `json:"build-tags,omitempty"` Scheduler string `json:"scheduler"`
GC string `json:"gc,omitempty"` Serial string `json:"serial"` // which serial output to use (uart, usb, none)
Scheduler string `json:"scheduler,omitempty"` Linker string `json:"linker"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Linker string `json:"linker,omitempty"` Libc string `json:"libc"`
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
Libc string `json:"libc,omitempty"` DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. CFlags []string `json:"cflags"`
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. LDFlags []string `json:"ldflags"`
CFlags []string `json:"cflags,omitempty"` LinkerScript string `json:"linkerscript"`
LDFlags []string `json:"ldflags,omitempty"` ExtraFiles []string `json:"extra-files"`
LinkerScript string `json:"linkerscript,omitempty"` RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
ExtraFiles []string `json:"extra-files,omitempty"` Emulator string `json:"emulator"`
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum FlashCommand string `json:"flash-command"`
Emulator string `json:"emulator,omitempty"` GDB []string `json:"gdb"`
FlashCommand string `json:"flash-command,omitempty"` PortReset string `json:"flash-1200-bps-reset"`
GDB []string `json:"gdb,omitempty"` SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
PortReset string `json:"flash-1200-bps-reset,omitempty"` FlashMethod string `json:"flash-method"`
SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" FlashVolume string `json:"msd-volume-name"`
FlashMethod string `json:"flash-method,omitempty"` FlashFilename string `json:"msd-firmware-name"`
FlashVolume []string `json:"msd-volume-name,omitempty"` UF2FamilyID string `json:"uf2-family-id"`
FlashFilename string `json:"msd-firmware-name,omitempty"` BinaryFormat string `json:"binary-format"`
UF2FamilyID string `json:"uf2-family-id,omitempty"` OpenOCDInterface string `json:"openocd-interface"`
BinaryFormat string `json:"binary-format,omitempty"` OpenOCDTarget string `json:"openocd-target"`
OpenOCDInterface string `json:"openocd-interface,omitempty"` OpenOCDTransport string `json:"openocd-transport"`
OpenOCDTarget string `json:"openocd-target,omitempty"` OpenOCDCommands []string `json:"openocd-commands"`
OpenOCDTransport string `json:"openocd-transport,omitempty"` OpenOCDVerify *bool `json:"openocd-verify"` // enable verify when flashing with openocd
OpenOCDCommands []string `json:"openocd-commands,omitempty"` JLinkDevice string `json:"jlink-device"`
OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd CodeModel string `json:"code-model"`
JLinkDevice string `json:"jlink-device,omitempty"` RelocationModel string `json:"relocation-model"`
CodeModel string `json:"code-model,omitempty"` WasmAbi string `json:"wasm-abi"`
RelocationModel string `json:"relocation-model,omitempty"`
WITPackage string `json:"wit-package,omitempty"`
WITWorld string `json:"wit-world,omitempty"`
} }
// overrideProperties overrides all properties that are set in child into itself using reflection. // overrideProperties overrides all properties that are set in child into itself using reflection.
@@ -87,10 +84,6 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) error {
if src.Uint() != 0 { if src.Uint() != 0 {
dst.Set(src) dst.Set(src)
} }
case reflect.Bool:
if src.Bool() {
dst.Set(src)
}
case reflect.Ptr: // for pointers, copy if not nil case reflect.Ptr: // for pointers, copy if not nil
if !src.IsNil() { if !src.IsNil() {
dst.Set(src) dst.Set(src)
@@ -176,14 +169,52 @@ func (spec *TargetSpec) resolveInherits() error {
// Load a target specification. // Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) { func LoadTarget(options *Options) (*TargetSpec, error) {
switch options.Target { if options.Target == "" {
case "": // Configure based on GOOS/GOARCH environment variables (falling back to
// No target given, use GOOS/GOARCH env variables. // runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
return defaultTarget(options, options.GOOS, options.GOARCH) var llvmarch string
case "wasip1", "wasi": switch options.GOARCH {
// Special case: support both -target=wasip1 and the GOOS/GOARCH pair. case "386":
// They should both have the same effect. llvmarch = "i386"
return defaultTarget(options, "wasip1", "wasm") case "amd64":
llvmarch = "x86_64"
case "arm64":
llvmarch = "aarch64"
case "arm":
switch options.GOARM {
case "5":
llvmarch = "armv5"
case "6":
llvmarch = "armv6"
case "7":
llvmarch = "armv7"
default:
return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM)
}
default:
llvmarch = options.GOARCH
}
llvmos := options.GOOS
if llvmos == "darwin" {
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
// to iOS!
llvmos = "macosx10.12.0"
if llvmarch == "aarch64" {
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
}
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
target := llvmarch + "-unknown-" + llvmos
if options.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. // See whether there is a target specification for this target (e.g.
@@ -207,217 +238,59 @@ func LoadTarget(options *Options) (*TargetSpec, error) {
return spec, nil return spec, nil
} }
// GetTargetSpecs retrieves target specifications from the TINYGOROOT targets func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
// directory. Only valid target JSON files are considered, and the function // No target spec available. Use the default one, useful on most systems
// returns a map of target names to their respective TargetSpec. // with a regular OS.
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.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, goos, goarch string) (*TargetSpec, error) {
spec := TargetSpec{ spec := TargetSpec{
Triple: triple,
GOOS: goos, GOOS: goos,
GOARCH: goarch, GOARCH: goarch,
BuildTags: []string{goos, goarch}, BuildTags: []string{goos, goarch},
GC: "precise",
Scheduler: "tasks", Scheduler: "tasks",
Linker: "cc", Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB DefaultStackSize: 1024 * 64, // 64kB
GDB: []string{"gdb"}, GDB: []string{"gdb"},
PortReset: "false", PortReset: "false",
} }
// Configure target based on GOARCH.
var llvmarch string
switch goarch { switch goarch {
case "386": case "386":
llvmarch = "i386"
spec.CPU = "pentium4" spec.CPU = "pentium4"
spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
case "amd64": case "amd64":
llvmarch = "x86_64"
spec.CPU = "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": case "arm":
spec.CPU = "generic" spec.CPU = "generic"
spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables") spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
subarch := strings.Split(options.GOARM, ",") switch strings.Split(triple, "-")[0] {
if len(subarch) > 2 { case "armv5":
return nil, fmt.Errorf("invalid GOARM=%s, must be of form <num>,[hardfloat|softfloat]", options.GOARM) 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":
archLevel := subarch[0] 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"
var fpu string case "armv7":
if len(subarch) >= 2 { 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"
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)
} }
case "arm64": case "arm64":
spec.CPU = "generic" spec.CPU = "generic"
llvmarch = "aarch64" spec.Features = "+neon"
if goos == "darwin" {
spec.Features = "+fp-armv8,+neon"
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
} else if goos == "windows" {
spec.Features = "+fp-armv8,+neon,-fmv"
} else { // linux
spec.Features = "+fp-armv8,+neon,-fmv,-outline-atomics"
}
case "mips", "mipsle":
spec.CPU = "mips32r2"
spec.CFlags = append(spec.CFlags, "-fno-pic")
if goarch == "mips" {
llvmarch = "mips" // big endian
} else {
llvmarch = "mipsel" // little endian
}
switch options.GOMIPS {
case "hardfloat":
spec.Features = "+fpxx,+mips32r2,+nooddspreg,-noabicalls"
case "softfloat":
spec.SoftFloat = true
spec.Features = "+mips32r2,+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":
llvmarch = "wasm32"
spec.CPU = "generic"
spec.Features = "+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext"
spec.BuildTags = append(spec.BuildTags, "tinygo.wasm")
spec.CFlags = append(spec.CFlags,
"-mbulk-memory",
"-mnontrapping-fptoint",
"-msign-ext",
)
default:
return nil, fmt.Errorf("unknown GOARCH=%s", goarch)
} }
if goos == "darwin" {
// Configure target based on GOOS.
llvmos := goos
llvmvendor := "unknown"
switch goos {
case "darwin":
platformVersion := "10.12.0"
if goarch == "arm64" {
platformVersion = "11.0.0" // first macosx platform with arm64 support
}
llvmvendor = "apple"
spec.Linker = "ld.lld" spec.Linker = "ld.lld"
spec.Libc = "darwin-libSystem" spec.Libc = "darwin-libSystem"
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to arch := strings.Split(triple, "-")[0]
// iOS! platformVersion := strings.TrimPrefix(strings.Split(triple, "-")[2], "macosx")
llvmos = "macosx" + platformVersion
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"-flavor", "darwin", "-flavor", "darwin",
"-dead_strip", "-dead_strip",
"-arch", llvmarch, "-arch", arch,
"-platform_version", "macos", platformVersion, platformVersion, "-platform_version", "macos", platformVersion, platformVersion,
) )
spec.ExtraFiles = append(spec.ExtraFiles, } else if goos == "linux" {
"src/runtime/runtime_unix.c")
case "linux":
spec.Linker = "ld.lld" spec.Linker = "ld.lld"
spec.RTLib = "compiler-rt" spec.RTLib = "compiler-rt"
spec.Libc = "musl" spec.Libc = "musl"
spec.LDFlags = append(spec.LDFlags, "--gc-sections") spec.LDFlags = append(spec.LDFlags, "--gc-sections")
if goarch == "arm64" { } else if goos == "windows" {
// 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/runtime/runtime_unix.c")
case "windows":
spec.Linker = "ld.lld" spec.Linker = "ld.lld"
spec.Libc = "mingw-w64" spec.Libc = "mingw-w64"
// Note: using a medium code model, low image base and no ASLR // Note: using a medium code model, low image base and no ASLR
@@ -426,78 +299,27 @@ func defaultTarget(options *Options, goos, goarch string) (*TargetSpec, error) {
// normally present in Go (without explicitly opting in). // normally present in Go (without explicitly opting in).
// For more discussion: // For more discussion:
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1 // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
switch goarch {
case "amd64":
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"--image-base", "0x400000",
)
case "arm64":
spec.LDFlags = append(spec.LDFlags,
"-m", "arm64pe",
)
}
spec.LDFlags = append(spec.LDFlags, spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"-Bdynamic", "-Bdynamic",
"--image-base", "0x400000",
"--gc-sections", "--gc-sections",
"--no-insert-timestamp", "--no-insert-timestamp",
"--no-dynamicbase", "--no-dynamicbase",
) )
case "wasip1": } else {
spec.GC = "" // use default GC spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
spec.Scheduler = "asyncify"
spec.Linker = "wasm-ld"
spec.RTLib = "compiler-rt"
spec.Libc = "wasi-libc"
spec.DefaultStackSize = 1024 * 64 // 64kB
spec.LDFlags = append(spec.LDFlags,
"--stack-first",
"--no-demangle",
)
spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}"
spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/asm_tinygowasm.S",
"src/internal/task/task_asyncify_wasm.S",
)
llvmos = "wasi"
default:
return nil, fmt.Errorf("unknown GOOS=%s", goos)
} }
// 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 goos == "windows" {
spec.Triple += "-gnu"
} else if 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 goarch != "wasm" { if goarch != "wasm" {
suffix := "" suffix := ""
if goos == "windows" && goarch == "amd64" { if goos == "windows" {
// Windows uses a different calling convention on amd64 from other // Windows uses a different calling convention from other operating
// operating systems so we need separate assembly files. // systems so we need separate assembly files.
suffix = "_windows" suffix = "_windows"
} }
asmGoarch := goarch spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
if goarch == "mips" || goarch == "mipsle" { spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
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")
} }
// Configure the emulator.
if goarch != runtime.GOARCH { if goarch != runtime.GOARCH {
// Some educated guesses as to how to invoke helper programs. // Some educated guesses as to how to invoke helper programs.
spec.GDB = []string{"gdb-multiarch"} spec.GDB = []string{"gdb-multiarch"}
@@ -514,10 +336,6 @@ func defaultTarget(options *Options, goos, goarch string) (*TargetSpec, error) {
spec.Emulator = "qemu-arm {}" spec.Emulator = "qemu-arm {}"
case "arm64": case "arm64":
spec.Emulator = "qemu-aarch64 {}" spec.Emulator = "qemu-aarch64 {}"
case "mips":
spec.Emulator = "qemu-mips {}"
case "mipsle":
spec.Emulator = "qemu-mipsel {}"
} }
} }
} }
@@ -526,7 +344,6 @@ func defaultTarget(options *Options, goos, goarch string) (*TargetSpec, error) {
spec.Emulator = "wine {}" spec.Emulator = "wine {}"
} }
} }
return &spec, nil return &spec, nil
} }
+3 -7
View File
@@ -16,18 +16,14 @@ import "tinygo.org/x/go-llvm"
var stdlibAliases = map[string]string{ var stdlibAliases = map[string]string{
// crypto packages // crypto packages
"crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric", "crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric",
"crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric", "crypto/ed25519/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric",
"crypto/md5.block": "crypto/md5.blockGeneric", "crypto/md5.block": "crypto/md5.blockGeneric",
"crypto/sha1.block": "crypto/sha1.blockGeneric", "crypto/sha1.block": "crypto/sha1.blockGeneric",
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric", "crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
"crypto/sha256.block": "crypto/sha256.blockGeneric", "crypto/sha256.block": "crypto/sha256.blockGeneric",
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric", "crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
// AES
"crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock",
"crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock",
// math package // math package
"math.archHypot": "math.hypot", "math.archHypot": "math.hypot",
"math.archMax": "math.max", "math.archMax": "math.max",
@@ -56,7 +52,7 @@ func (b *builder) createAlias(alias llvm.Value) {
b.CreateUnreachable() b.CreateUnreachable()
return return
} }
result := b.CreateCall(alias.GlobalValueType(), alias, b.llvmFn.Params(), "") result := b.CreateCall(alias, b.llvmFn.Params(), "")
if result.Type().TypeKind() == llvm.VoidTypeKind { if result.Type().TypeKind() == llvm.VoidTypeKind {
b.CreateRetVoid() b.CreateRetVoid()
} else { } else {
+10 -11
View File
@@ -89,24 +89,23 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic") b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
} }
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice // createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
// and unsafe.String. This function must panic if the ptr/len parameters are // function must panic if the ptr/len parameters are invalid.
// invalid. func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) { // From the documentation of unsafe.Slice:
// From the documentation of unsafe.Slice and unsafe.String:
// > At run time, if len is negative, or if ptr is nil and len is not // > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs. // > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is // 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. // 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 // 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. // Make sure the len value is at least as big as a uintptr.
len = b.extendInteger(len, lenType, b.uintptrType) len = b.extendInteger(len, lenType, b.uintptrType)
// Determine the maximum slice size, and therefore the maximum value of the // Determine the maximum slice size, and therefore the maximum value of the
// len parameter. // len parameter.
maxSize := b.maxSliceSize(elementType) maxSize := b.maxSliceSize(ptr.Type().ElementType())
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false) maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
// Do the check. By using unsigned greater than for the length check, signed // Do the check. By using unsigned greater than for the length check, signed
@@ -118,7 +117,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value,
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "") lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "") assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "") assert = b.CreateOr(assert, lenOutOfBounds, "")
b.createRuntimeAssert(assert, name, "unsafeSlicePanic") b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
} }
// createChanBoundsCheck creates a bounds check before creating a new channel to // createChanBoundsCheck creates a bounds check before creating a new channel to
@@ -135,7 +134,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an // Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
// uintptr if uintptrs were signed. // 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() { if elementSize > maxBufSize.ZExtValue() {
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize)) b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
return return
@@ -146,11 +145,11 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
} }
// Make the maxBufSize actually the maximum allowed value (in number of // Make the maxBufSize actually the maximum allowed value (in number of
// elements in the channel buffer). // elements in the channel buffer).
maxBufSize = b.CreateUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false), "") maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
// Make sure maxBufSize has the same type as bufSize. // Make sure maxBufSize has the same type as bufSize.
if maxBufSize.Type() != bufSize.Type() { 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. // Do the check for a too large (or negative) buffer size.
+56 -21
View File
@@ -1,6 +1,9 @@
package compiler package compiler
import ( import (
"fmt"
"strings"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -10,42 +13,74 @@ import (
func (b *builder) createAtomicOp(name string) llvm.Value { func (b *builder) createAtomicOp(name string) llvm.Value {
switch name { switch name {
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr": case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1])
if strings.HasPrefix(b.Triple, "avr") {
// AtomicRMW does not work on AVR as intended:
// - There are some register allocation issues (fixed by https://reviews.llvm.org/D97127 which is not yet in a usable LLVM release)
// - The result is the new value instead of the old value
vType := val.Type()
name := fmt.Sprintf("__sync_fetch_and_add_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType}, false))
}
oldVal := b.createCall(fn, []llvm.Value{ptr, val}, "")
// Return the new value, not the original value returned.
return b.CreateAdd(oldVal, val, "")
}
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
// Return the new value, not the original value returned by atomicrmw. // Return the new value, not the original value returned by atomicrmw.
return b.CreateAdd(oldVal, val, "") 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": case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1], getPos(b.fn)) 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) oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
if isPointer {
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
}
return oldVal return oldVal
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer": case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0])
old := b.getValue(b.fn.Params[1], getPos(b.fn)) old := b.getValue(b.fn.Params[1])
newVal := b.getValue(b.fn.Params[2], getPos(b.fn)) newVal := b.getValue(b.fn.Params[2])
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true) tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
swapped := b.CreateExtractValue(tuple, 1, "") swapped := b.CreateExtractValue(tuple, 1, "")
return swapped return swapped
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer": 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 := b.CreateLoad(ptr, "")
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
return val return val
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer": case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
ptr := b.getValue(b.fn.Params[0], getPos(b.fn)) ptr := b.getValue(b.fn.Params[0])
val := b.getValue(b.fn.Params[1], getPos(b.fn)) val := b.getValue(b.fn.Params[1])
if strings.HasPrefix(b.Triple, "avr") {
// SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores.
vType := val.Type()
isPointer := vType.TypeKind() == llvm.PointerTypeKind
if isPointer {
// libcalls only supports integers, so cast to an integer.
vType = b.uintptrType
val = b.CreatePtrToInt(val, vType, "")
ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "")
}
name := fmt.Sprintf("__atomic_store_%d", vType.IntTypeWidth()/8)
fn := b.mod.NamedFunction(name)
if fn.IsNil() {
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType, b.uintptrType}, false))
}
b.createCall(fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, "")
return llvm.Value{}
}
store := b.CreateStore(val, ptr) store := b.CreateStore(val, ptr)
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
+44 -38
View File
@@ -20,7 +20,7 @@ const maxFieldsPerParam = 3
type paramInfo struct { type paramInfo struct {
llvmType llvm.Type llvmType llvm.Type
name string // name, possibly with suffixes for e.g. struct fields 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
} }
// paramFlags identifies parameter attributes for flags. Most importantly, it // paramFlags identifies parameter attributes for flags. Most importantly, it
@@ -36,20 +36,16 @@ const (
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or // createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
// createRuntimeInvoke instead. // createRuntimeInvoke instead.
func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value { func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value {
member := b.program.ImportedPackage("runtime").Members[fnName] fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
if member == nil { llvmFn := b.getFunction(fn)
panic("unknown runtime call: " + fnName)
}
fn := member.(*ssa.Function)
fnType, llvmFn := b.getFunction(fn)
if llvmFn.IsNil() { if llvmFn.IsNil() {
panic("trying to call non-existent function: " + fn.RelString(nil)) 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 { if isInvoke {
return b.createInvoke(fnType, llvmFn, args, name) return b.createInvoke(llvmFn, args, name)
} }
return b.createCall(fnType, llvmFn, args, name) return b.createCall(llvmFn, args, name)
} }
// createRuntimeCall creates a new call to runtime.<fnName> with the given // createRuntimeCall creates a new call to runtime.<fnName> with the given
@@ -69,22 +65,22 @@ func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name str
// createCall creates a call to the given function with the arguments possibly // createCall creates a call to the given function with the arguments possibly
// expanded. // expanded.
func (b *builder) createCall(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
expanded := make([]llvm.Value, 0, len(args)) expanded := make([]llvm.Value, 0, len(args))
for _, arg := range args { for _, arg := range args {
fragments := b.expandFormalParam(arg) fragments := b.expandFormalParam(arg)
expanded = append(expanded, fragments...) expanded = append(expanded, fragments...)
} }
return b.CreateCall(fnType, fn, expanded, name) return b.CreateCall(fn, expanded, name)
} }
// createInvoke is like createCall but continues execution at the landing pad if // createInvoke is like createCall but continues execution at the landing pad if
// the call resulted in a panic. // the call resulted in a panic.
func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value { func (b *builder) createInvoke(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
if b.hasDeferFrame() { if b.hasDeferFrame() {
b.createInvokeCheckpoint() b.createInvokeCheckpoint()
} }
return b.createCall(fnType, fn, args, name) return b.createCall(fn, args, name)
} }
// Expand an argument type to a list that can be used in a function call // Expand an argument type to a list that can be used in a function call
@@ -100,7 +96,13 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
// failed to expand this parameter: too many fields // failed to expand this parameter: too many fields
} }
// TODO: split small arrays // TODO: split small arrays
return []paramInfo{c.getParamInfo(t, name, goType)} return []paramInfo{
{
llvmType: t,
name: name,
flags: getTypeFlags(goType),
},
}
} }
// expandFormalParamOffsets returns a list of offsets from the start of an // expandFormalParamOffsets returns a list of offsets from the start of an
@@ -150,6 +152,7 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
// Try to flatten a struct type to a list of types. Returns a 1-element slice // Try to flatten a struct type to a list of types. Returns a 1-element slice
// with the passed in type if this is not possible. // with the passed in type if this is not possible.
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo { func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
typeFlags := getTypeFlags(goType)
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
var paramInfos []paramInfo var paramInfos []paramInfo
@@ -180,37 +183,40 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
} }
} }
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
for i := range subInfos {
subInfos[i].flags |= typeFlags
}
paramInfos = append(paramInfos, subInfos...) paramInfos = append(paramInfos, subInfos...)
} }
return paramInfos return paramInfos
default: default:
return []paramInfo{c.getParamInfo(t, name, goType)} return []paramInfo{
{
llvmType: t,
name: name,
flags: typeFlags,
},
}
} }
} }
// getParamInfo collects information about a parameter. For example, if this // getTypeFlags returns the type flags for a given type. It will not recurse
// parameter is pointer-like, it will also store the element type for the // into sub-types (such as in structs).
// dereferenceable_or_null attribute. func getTypeFlags(t types.Type) paramFlags {
func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Type) paramInfo { if t == nil {
info := paramInfo{ return 0
llvmType: t,
name: name,
} }
if goType != nil { switch t.Underlying().(type) {
switch underlying := goType.Underlying().(type) { case *types.Pointer:
case *types.Pointer: // Pointers in Go must either point to an object or be nil.
// Pointers in Go must either point to an object or be nil. return paramIsDeferenceableOrNull
info.elemSize = c.targetData.TypeAllocSize(c.getLLVMType(underlying.Elem())) case *types.Chan, *types.Map:
case *types.Chan: // Channels and maps are implemented as pointers pointing to some
// Channels are implemented simply as a *runtime.channel. // object, and follow the same rules as *types.Pointer.
info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("channel")) return paramIsDeferenceableOrNull
case *types.Map: default:
// Maps are similar to channels: they are implemented as a return 0
// *runtime.hashmap.
info.elemSize = c.targetData.TypeAllocSize(c.getLLVMRuntimeType("hashmap"))
}
} }
return info
} }
// extractSubfield extracts a field from a struct, or returns null if this is // extractSubfield extracts a field from a struct, or returns null if this is
@@ -233,7 +239,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 // type t if this object were flattened like in flattenAggregate. Used together
// with flattenAggregate to know the start indices of each value in the // with flattenAggregate to know the start indices of each value in the
// non-flattened object. // non-flattened object.
+42 -38
View File
@@ -14,7 +14,7 @@ import (
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value { func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem())) elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false) 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()) b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() { if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
bufSize = b.CreateZExt(bufSize, b.uintptrType, "") bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
@@ -27,33 +27,34 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
// createChanSend emits a pseudo chan send operation. It is lowered to the // createChanSend emits a pseudo chan send operation. It is lowered to the
// actual channel send operation during goroutine lowering. // actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) { func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan, getPos(instr)) ch := b.getValue(instr.Chan)
chanValue := b.getValue(instr.X, getPos(instr)) chanValue := b.getValue(instr.X)
// store value-to-send // store value-to-send
valueType := b.getLLVMType(instr.X.Type()) valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType) valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca) b.CreateStore(chanValue, valueAlloca)
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send. // Do the send.
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
// https://bugs.llvm.org/show_bug.cgi?id=41742 // https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if !isZeroSize { if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
} }
} }
@@ -61,31 +62,32 @@ func (b *builder) createChanSend(instr *ssa.Send) {
// actual channel receive operation during goroutine lowering. // actual channel receive operation during goroutine lowering.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
ch := b.getValue(unop.X, getPos(unop)) ch := b.getValue(unop.X)
// Allocate memory to receive into. // Allocate memory to receive into.
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0 isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType) valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value") valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
} }
// Allocate blockedlist buffer. // Allocate blockedlist buffer.
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
var received llvm.Value var received llvm.Value
if isZeroSize { if isZeroSize {
received = llvm.ConstNull(valueType) received = llvm.ConstNull(valueType)
} else { } else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received") received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize) b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
} }
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if unop.CommaOk { if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
@@ -138,7 +140,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
var selectStates []llvm.Value var selectStates []llvm.Value
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState") chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
for _, state := range expr.States { for _, state := range expr.States {
ch := b.getValue(state.Chan, state.Pos) ch := b.getValue(state.Chan)
selectState := llvm.ConstNull(chanSelectStateType) selectState := llvm.ConstNull(chanSelectStateType)
selectState = b.CreateInsertValue(selectState, ch, 0, "") selectState = b.CreateInsertValue(selectState, ch, 0, "")
switch state.Dir { switch state.Dir {
@@ -154,10 +156,11 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
case types.SendOnly: case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca // Store this value in an alloca and put a pointer to this alloca
// in the send state. // 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") alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca) b.CreateStore(sendValue, alloca)
selectState = b.CreateInsertValue(selectState, alloca, 1, "") ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -165,12 +168,12 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Create a receive buffer, where the received value will be stored. // Create a receive buffer, where the received value will be stored.
recvbuf := llvm.Undef(b.dataPtrType) recvbuf := llvm.Undef(b.i8ptrType)
if recvbufSize != 0 { if recvbufSize != 0 {
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize)) 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) recvbufAlloca.SetAlignment(recvbufAlign)
recvbuf = b.CreateGEP(allocaType, recvbufAlloca, []llvm.Value{ recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.recvbuf") }, "select.recvbuf")
@@ -178,16 +181,16 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Create the states slice (allocated on the stack). // Create the states slice (allocated on the stack).
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates)) 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 { for i, state := range selectStates {
// Set each slice element to the appropriate channel. // Set each slice element to the appropriate channel.
gep := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ gep := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "") }, "")
b.CreateStore(state, gep) b.CreateStore(state, gep)
} }
statesPtr := b.CreateGEP(statesAllocaType, statesAlloca, []llvm.Value{ statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.states") }, "select.states")
@@ -199,9 +202,9 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
// Stack-allocate operation structures. // Stack-allocate operation structures.
// If these were simply created as a slice, they would heap-allocate. // If these were simply created as a slice, they would heap-allocate.
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates)) chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca") chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{ chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
}, "select.block") }, "select.block")
@@ -213,7 +216,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
}, "select.result") }, "select.result")
// Terminate the lifetime of the operation structures. // Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(chBlockAlloca, chBlockSize) b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else { } else {
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{ results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
recvbuf, recvbuf,
@@ -222,7 +225,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
// Terminate the lifetime of the states alloca. // 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, // The result value does not include all the possible received values,
// because we can't load them in advance. Instead, the *ssa.Extract // because we can't load them in advance. Instead, the *ssa.Extract
@@ -244,7 +247,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value { func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
if expr.Index == 0 { if expr.Index == 0 {
// index // index
value := b.getValue(expr.Tuple, getPos(expr)) value := b.getValue(expr.Tuple)
index := b.CreateExtractValue(value, expr.Index, "") index := b.CreateExtractValue(value, expr.Index, "")
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() { if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
index = b.CreateSExt(index, b.intType, "") index = b.CreateSExt(index, b.intType, "")
@@ -252,7 +255,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
return index return index
} else if expr.Index == 1 { } else if expr.Index == 1 {
// comma-ok // comma-ok
value := b.getValue(expr.Tuple, getPos(expr)) value := b.getValue(expr.Tuple)
return b.CreateExtractValue(value, expr.Index, "") return b.CreateExtractValue(value, expr.Index, "")
} else { } else {
// Select statements are (index, ok, ...) where ... is a number of // Select statements are (index, ok, ...) where ... is a number of
@@ -261,7 +264,8 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// receive can proceed at a time) so we'll get that alloca, bitcast // receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it. // it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)] recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
typ := b.getLLVMType(expr.Type()) typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
return b.CreateLoad(typ, recvbuf, "") ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
} }
} }
+278 -475
View File
File diff suppressed because it is too large Load Diff
+51 -106
View File
@@ -9,7 +9,6 @@ import (
"testing" "testing"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -28,12 +27,6 @@ type testCase struct {
func TestCompiler(t *testing.T) { func TestCompiler(t *testing.T) {
t.Parallel() t.Parallel()
// Determine Go minor version (e.g. 16 in go1.16.3).
_, goMinor, err := goenv.GetGorootVersion()
if err != nil {
t.Fatal("could not read Go version:", err)
}
// Determine which tests to run, depending on the Go and LLVM versions. // Determine which tests to run, depending on the Go and LLVM versions.
tests := []testCase{ tests := []testCase{
{"basic.go", "", ""}, {"basic.go", "", ""},
@@ -49,13 +42,6 @@ func TestCompiler(t *testing.T) {
{"goroutine.go", "cortex-m-qemu", "tasks"}, {"goroutine.go", "cortex-m-qemu", "tasks"},
{"channel.go", "", ""}, {"channel.go", "", ""},
{"gc.go", "", ""}, {"gc.go", "", ""},
{"zeromap.go", "", ""},
}
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
}
if goMinor >= 21 {
tests = append(tests, testCase{"go1.21.go", "", ""})
} }
for _, tc := range tests { for _, tc := range tests {
@@ -73,11 +59,51 @@ func TestCompiler(t *testing.T) {
options := &compileopts.Options{ options := &compileopts.Options{
Target: targetString, Target: targetString,
} }
target, err := compileopts.LoadTarget(options)
if err != nil {
t.Fatal("failed to load target:", err)
}
if tc.scheduler != "" { if tc.scheduler != "" {
options.Scheduler = tc.scheduler options.Scheduler = tc.scheduler
} }
config := &compileopts.Config{
Options: options,
Target: target,
}
compilerConfig := &Config{
Triple: config.Triple(),
Features: config.Features(),
GOOS: config.GOOS(),
GOARCH: config.GOARCH(),
CodeModel: config.CodeModel(),
RelocationModel: config.RelocationModel(),
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.Target.DefaultStackSize,
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 { if errs != nil {
for _, err := range errs { for _, err := range errs {
t.Error(err) t.Error(err)
@@ -85,18 +111,20 @@ func TestCompiler(t *testing.T) {
return return
} }
err := llvm.VerifyModule(mod, llvm.PrintMessageAction) err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
// Optimize IR a little. // Optimize IR a little.
passOptions := llvm.NewPassBuilderOptions() funcPasses := llvm.NewFunctionPassManagerForModule(mod)
defer passOptions.Dispose() defer funcPasses.Dispose()
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions) funcPasses.AddInstructionCombiningPass()
if err != nil { funcPasses.InitializeFunc()
t.Error(err) for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
funcPasses.RunFunc(fn)
} }
funcPasses.FinalizeFunc()
outFilePrefix := tc.file[:len(tc.file)-3] outFilePrefix := tc.file[:len(tc.file)-3]
if tc.target != "" { if tc.target != "" {
@@ -167,95 +195,12 @@ func filterIrrelevantIRLines(lines []string) []string {
if strings.HasPrefix(line, "source_filename = ") { if strings.HasPrefix(line, "source_filename = ") {
continue 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. // 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 continue
} }
out = append(out, line) out = append(out, line)
} }
return out 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)
}
+54 -72
View File
@@ -16,7 +16,6 @@ package compiler
import ( import (
"go/types" "go/types"
"strconv" "strconv"
"strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
@@ -61,8 +60,9 @@ func (b *builder) deferInitFunc() {
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer. // Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr") deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr) b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
if b.hasDeferFrame() { if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer. // Set up the defer frame with the current stack pointer.
@@ -162,9 +162,9 @@ mov x0, #0
1: 1:
` `
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{vg},~{memory}" 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" { if b.GOOS != "darwin" {
// These registers cause the following warning when compiling for // These registers cause the following warning when compiling for
// MacOS and Windows: // MacOS:
// warning: inline asm clobber list contains reserved registers: // warning: inline asm clobber list contains reserved registers:
// X18, FP // X18, FP
// Reserved registers on the clobber list may not be preserved // Reserved registers on the clobber list may not be preserved
@@ -188,24 +188,6 @@ std z+5, r29
ldi r24, 0 ldi r24, 0
1:` 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}" 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": case "riscv32":
asmString = ` asmString = `
la a2, 1f la a2, 1f
@@ -219,7 +201,7 @@ li a0, 0
} }
asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false) asmType := llvm.FunctionType(resultType, []llvm.Type{b.deferFrame.Type()}, false)
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false) asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
result := b.CreateCall(asmType, asm, []llvm.Value{b.deferFrame}, "setjmp") result := b.CreateCall(asm, []llvm.Value{b.deferFrame}, "setjmp")
result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0)) result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0))
isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result") isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result")
continueBB := b.insertBasicBlock("") continueBB := b.insertBasicBlock("")
@@ -267,7 +249,7 @@ func isInLoop(start *ssa.BasicBlock) bool {
func (b *builder) createDefer(instr *ssa.Defer) { func (b *builder) createDefer(instr *ssa.Defer) {
// The pointer to the previous defer struct, which we will replace to // The pointer to the previous defer struct, which we will replace to
// make a linked list. // make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next") next := b.CreateLoad(b.deferPtr, "defer.next")
var values []llvm.Value var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()} valueTypes := []llvm.Type{b.uintptrType, next.Type()}
@@ -284,13 +266,13 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with // Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters). // runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface itf := b.getValue(instr.Call.Value) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = []llvm.Value{callback, next, typecode, receiverValue} values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
for _, arg := range instr.Call.Args { for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr)) val := b.getValue(arg)
values = append(values, val) values = append(values, val)
valueTypes = append(valueTypes, val.Type()) valueTypes = append(valueTypes, val.Type())
} }
@@ -307,7 +289,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// runtime._defer fields). // runtime._defer fields).
values = []llvm.Value{callback, next} values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args { for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr)) llvmParam := b.getValue(param)
values = append(values, llvmParam) values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type()) valueTypes = append(valueTypes, llvmParam.Type())
} }
@@ -319,7 +301,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// pointer. // pointer.
// TODO: ignore this closure entirely and put pointers to the free // TODO: ignore this closure entirely and put pointers to the free
// variables directly in the defer struct, avoiding a memory allocation. // variables directly in the defer struct, avoiding a memory allocation.
closure := b.getValue(instr.Call.Value, getPos(instr)) closure := b.getValue(instr.Call.Value)
context := b.CreateExtractValue(closure, 0, "") context := b.CreateExtractValue(closure, 0, "")
// Get the callback number. // Get the callback number.
@@ -335,7 +317,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// context pointer). // context pointer).
values = []llvm.Value{callback, next} values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args { for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr)) llvmParam := b.getValue(param)
values = append(values, llvmParam) values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type()) valueTypes = append(valueTypes, llvmParam.Type())
} }
@@ -347,7 +329,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
var argValues []llvm.Value var argValues []llvm.Value
for _, arg := range instr.Call.Args { for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type()) 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 { if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
@@ -370,7 +352,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
} }
} else { } else {
funcValue := b.getValue(instr.Call.Value, getPos(instr)) funcValue := b.getValue(instr.Call.Value)
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok { if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs) b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
@@ -385,7 +367,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
values = []llvm.Value{callback, next, funcValue} values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type()) valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args { for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr)) llvmParam := b.getValue(param)
values = append(values, llvmParam) values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type()) valueTypes = append(valueTypes, llvmParam.Type())
} }
@@ -408,8 +390,9 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation. // This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType) size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType) nilPtr := llvm.ConstNull(b.i8ptrType)
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call") allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferredCallType, 0), "defer.alloc")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(alloca) b.trackPointer(alloca)
@@ -417,13 +400,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
b.CreateStore(deferredCall, alloca) b.CreateStore(deferredCall, alloca)
// Push it on top of the linked list by replacing deferPtr. // 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. // createRunDefers emits code to run all deferred functions.
func (b *builder) createRunDefers() { func (b *builder) createRunDefers() {
deferType := b.getLLVMRuntimeType("_defer")
// Add a loop like the following: // Add a loop like the following:
// for stack != nil { // for stack != nil {
// _stack := stack // _stack := stack
@@ -449,7 +431,7 @@ func (b *builder) createRunDefers() {
// Create loop head: // Create loop head:
// for stack != nil { // for stack != nil {
b.SetInsertPointAtEnd(loophead) b.SetInsertPointAtEnd(loophead)
deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "") deferData := b.CreateLoad(b.deferPtr, "")
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil") stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
b.CreateCondBr(stackIsNil, end, loop) b.CreateCondBr(stackIsNil, end, loop)
@@ -458,17 +440,17 @@ func (b *builder) createRunDefers() {
// stack = stack.next // stack = stack.next
// switch stack.callback { // switch stack.callback {
b.SetInsertPointAtEnd(loop) b.SetInsertPointAtEnd(loop)
nextStackGEP := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
}, "stack.next.gep") }, "stack.next.gep")
nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next") nextStack := b.CreateLoad(nextStackGEP, "stack.next")
b.CreateStore(nextStack, b.deferPtr) b.CreateStore(nextStack, b.deferPtr)
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{ gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
}, "callback.gep") }, "callback.gep")
callback := b.CreateLoad(b.uintptrType, gep, "callback") callback := b.CreateLoad(gep, "callback")
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs)) sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
for i, callback := range b.allDeferFuncs { for i, callback := range b.allDeferFuncs {
@@ -483,32 +465,33 @@ func (b *builder) createRunDefers() {
// Call on an value or interface value. // Call on an value or interface value.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
if !callback.IsInvoke() { if !callback.IsInvoke() {
//Expect funcValue to be passed through the deferred call. //Expect funcValue to be passed through the deferred call.
valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
} else { } else {
//Expect typecode //Expect typecode
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType) valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
} }
for _, arg := range callback.Args { for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type())) valueTypes = append(valueTypes, 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). // Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
for i := 2; i < len(valueTypes); i++ { 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(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
var fnPtr llvm.Value var fnPtr llvm.Value
var fnType llvm.Type
if !callback.IsInvoke() { if !callback.IsInvoke() {
// Isolate the func value. // Isolate the func value.
@@ -516,9 +499,8 @@ func (b *builder) createRunDefers() {
forwardParams = forwardParams[1:] forwardParams = forwardParams[1:]
//Get function pointer and context //Get function pointer and context
var context llvm.Value fp, context := b.decodeFuncValue(funcValue, callback.Signature())
fnPtr, context = b.decodeFuncValue(funcValue) fnPtr = fp
fnType = b.getLLVMFunctionType(callback.Signature())
//Pass context //Pass context
forwardParams = append(forwardParams, context) forwardParams = append(forwardParams, context)
@@ -527,75 +509,74 @@ func (b *builder) createRunDefers() {
// parameters. // parameters.
forwardParams = append(forwardParams[1:], forwardParams[0]) forwardParams = append(forwardParams[1:], forwardParams[0])
fnPtr = b.getInvokeFunction(callback) fnPtr = b.getInvokeFunction(callback)
fnType = fnPtr.GlobalValueType()
// Add the context parameter. An interface call cannot also be a // Add the context parameter. An interface call cannot also be a
// closure but we have to supply the parameter anyway for platforms // closure but we have to supply the parameter anyway for platforms
// with a strict calling convention. // with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
} }
b.createCall(fnType, fnPtr, forwardParams, "") b.createCall(fnPtr, forwardParams, "")
case *ssa.Function: case *ssa.Function:
// Direct call. // Direct call.
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
for _, param := range getParams(callback.Signature) { for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type())) valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) { 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(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam) 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. // These parameters should not be supplied when calling into an external C/ASM function.
if !b.getFunctionInfo(callback).exported { if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving // Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway. // function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType)) forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
} }
// Call real function. // Call real function.
fnType, fn := b.getFunction(callback) b.createInvoke(b.getFunction(callback), forwardParams, "")
b.createInvoke(fnType, fn, forwardParams, "")
case *ssa.MakeClosure: case *ssa.MakeClosure:
// Get the real defer struct type and cast to it. // Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function) fn := 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() params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type())) valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
} }
valueTypes = append(valueTypes, b.dataPtrType) // closure valueTypes = append(valueTypes, b.i8ptrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
forwardParams := []llvm.Value{} forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ { 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(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param") forwardParam := b.CreateLoad(gep, "param")
forwardParams = append(forwardParams, forwardParam) forwardParams = append(forwardParams, forwardParam)
} }
// Call deferred function. // Call deferred function.
fnType, llvmFn := b.getFunction(fn) b.createCall(b.getFunction(fn), forwardParams, "")
b.createCall(fnType, llvmFn, forwardParams, "")
case *ssa.Builtin: case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback] db := b.deferBuiltinFuncs[callback]
//Get parameter types //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 //Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params() params := callback.Type().Underlying().(*types.Signature).Params()
@@ -604,13 +585,14 @@ func (b *builder) createRunDefers() {
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCallPtr := b.CreateBitCast(deferData, llvm.PointerType(deferredCallType, 0), "defercall")
// Extract the params from the struct. // Extract the params from the struct.
var argValues []llvm.Value var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false) zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ { 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(deferredCallPtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param") forwardParam := b.CreateLoad(gep, "param")
argValues = append(argValues, forwardParam) argValues = append(argValues, forwardParam)
} }
+40 -15
View File
@@ -13,14 +13,34 @@ import (
// createFuncValue creates a function value from a raw function pointer with no // createFuncValue creates a function value from a raw function pointer with no
// context. // context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value { 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} // Closure is: {context, function pointer}
funcValueType := b.getFuncType(sig) funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
funcValueType := c.getFuncType(sig)
funcValue := llvm.Undef(funcValueType) funcValue := llvm.Undef(funcValueType)
funcValue = b.CreateInsertValue(funcValue, context, 0, "") funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
funcValue = b.CreateInsertValue(funcValue, funcPtr, 1, "") funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
return funcValue 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 // extractFuncScalar returns some scalar that can be used in comparisons. It is
// a cheap operation. // a cheap operation.
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value { func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
@@ -34,20 +54,26 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
} }
// decodeFuncValue extracts the context and the function pointer from this func // decodeFuncValue extracts the context and the function pointer from this func
// value. // value. This may be an expensive operation.
func (b *builder) decodeFuncValue(funcValue llvm.Value) (funcPtr, context llvm.Value) { func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
context = b.CreateExtractValue(funcValue, 0, "") context = b.CreateExtractValue(funcValue, 0, "")
funcPtr = b.CreateExtractValue(funcValue, 1, "") bitcast := b.CreateExtractValue(funcValue, 1, "")
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
funcPtr = bitcast.Operand(0)
return
}
llvmSig := b.getRawFuncType(sig)
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
return return
} }
// getFuncType returns the type of a func value given a signature. // getFuncType returns the type of a func value given a signature.
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type { func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
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. // getRawFuncType returns a LLVM function pointer type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type { func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
// Get the return type. // Get the return type.
var returnType llvm.Type var returnType llvm.Type
switch typ.Results().Len() { switch typ.Results().Len() {
@@ -75,7 +101,7 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
if recv.StructName() == "runtime._interface" { if recv.StructName() == "runtime._interface" {
// This is a call on an interface, not a concrete type. // This is a call on an interface, not a concrete type.
// The receiver is not an interface, but a i8* type. // The receiver is not an interface, but a i8* type.
recv = c.dataPtrType recv = c.i8ptrType
} }
for _, info := range c.expandFormalParamType(recv, "", nil) { for _, info := range c.expandFormalParamType(recv, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
@@ -88,10 +114,10 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
} }
} }
// All functions take these parameters at the end. // 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. // Make a func type out of the signature.
return llvm.FunctionType(returnType, paramTypes, false) return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
} }
// parseMakeClosure makes a function value (with context) from the given // parseMakeClosure makes a function value (with context) from the given
@@ -106,7 +132,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
boundVars := make([]llvm.Value, len(expr.Bindings)) boundVars := make([]llvm.Value, len(expr.Bindings))
for i, binding := range expr.Bindings { for i, binding := range expr.Bindings {
// The context stores the bound variables. // The context stores the bound variables.
llvmBoundVar := b.getValue(binding, getPos(expr)) llvmBoundVar := b.getValue(binding)
boundVars[i] = llvmBoundVar boundVars[i] = llvmBoundVar
} }
@@ -115,6 +141,5 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
context := b.emitPointerPack(boundVars) context := b.emitPointerPack(boundVars)
// Create the closure. // Create the closure.
_, fn := b.getFunction(f) return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
return b.createFuncValue(fn, context, f.Signature), nil
} }
+5 -2
View File
@@ -78,10 +78,13 @@ 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. // first if needed. The input value must be of LLVM pointer type.
func (b *builder) trackPointer(value llvm.Value) { func (b *builder) trackPointer(value llvm.Value) {
b.createRuntimeCall("trackPointer", []llvm.Value{value, b.stackChainAlloca}, "") if value.Type() != b.i8ptrType {
value = b.CreateBitCast(value, b.i8ptrType, "")
}
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
} }
// typeHasPointers returns whether this type is a pointer or contains pointers. // typeHasPointers returns whether this type is a pointer or contains pointers.
+36 -42
View File
@@ -7,6 +7,7 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -16,12 +17,11 @@ func (b *builder) createGo(instr *ssa.Go) {
// Get all function parameters to pass to the goroutine. // Get all function parameters to pass to the goroutine.
var params []llvm.Value var params []llvm.Value
for _, param := range instr.Call.Args { for _, param := range instr.Call.Args {
params = append(params, b.getValue(param, getPos(instr))) params = append(params, b.getValue(param))
} }
var prefix string var prefix string
var funcPtr llvm.Value var funcPtr llvm.Value
var funcType llvm.Type
hasContext := false hasContext := false
if callee := instr.Call.StaticCallee(); callee != nil { if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new // Static callee is known. This makes it easier to start a new
@@ -33,7 +33,7 @@ func (b *builder) createGo(instr *ssa.Go) {
case *ssa.MakeClosure: case *ssa.MakeClosure:
// A goroutine call on a func value, but the callee is trivial to find. For // A goroutine call on a func value, but the callee is trivial to find. For
// example: immediately applied functions. // example: immediately applied functions.
funcValue := b.getValue(value, getPos(instr)) funcValue := b.getValue(value)
context = b.extractFuncContext(funcValue) context = b.extractFuncContext(funcValue)
default: default:
panic("StaticCallee returned an unexpected value") panic("StaticCallee returned an unexpected value")
@@ -42,7 +42,7 @@ func (b *builder) createGo(instr *ssa.Go) {
params = append(params, context) // context parameter params = append(params, context) // context parameter
hasContext = true hasContext = true
} }
funcType, funcPtr = b.getFunction(callee) funcPtr = b.getFunction(callee)
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok { } else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
// We cheat. None of the builtins do any long or blocking operation, so // 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 // we might as well run these builtins right away without the program
@@ -70,17 +70,16 @@ func (b *builder) createGo(instr *ssa.Go) {
var argValues []llvm.Value var argValues []llvm.Value
for _, arg := range instr.Call.Args { for _, arg := range instr.Call.Args {
argTypes = append(argTypes, arg.Type()) 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()) b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
return return
} else if instr.Call.IsInvoke() { } else if instr.Call.IsInvoke() {
// This is a method call on an interface value. // 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, "") itfTypeCode := b.CreateExtractValue(itf, 0, "")
itfValue := b.CreateExtractValue(itf, 1, "") itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call) funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType()
params = append([]llvm.Value{itfValue}, params...) // start with receiver params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode params = append(params, itfTypeCode) // end with typecode
} else { } else {
@@ -90,8 +89,7 @@ func (b *builder) createGo(instr *ssa.Go) {
// * The function context, for closures. // * The function context, for closures.
// * The function pointer (for tasks). // * The function pointer (for tasks).
var context llvm.Value var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr))) funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr) params = append(params, context, funcPtr)
hasContext = true hasContext = true
prefix = b.fn.RelString(nil) prefix = b.fn.RelString(nil)
@@ -99,14 +97,14 @@ func (b *builder) createGo(instr *ssa.Go) {
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos()) callee := b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
if b.AutomaticStackSize { if b.AutomaticStackSize {
// The stack size is not known until after linking. Call a dummy // The stack size is not known until after linking. Call a dummy
// function that will be replaced with a load from a special ELF // function that will be replaced with a load from a special ELF
// section that contains the stack size (and is modified after // section that contains the stack size (and is modified after
// linking). // linking).
stackSizeFnType, stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function)) 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(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType)}, "stacksize")
} else { } else {
// The stack size is fixed at compile time. By emitting it here as a // The stack size is fixed at compile time. By emitting it here as a
// constant, it can be optimized. // constant, it can be optimized.
@@ -115,8 +113,8 @@ func (b *builder) createGo(instr *ssa.Go) {
} }
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false) stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
} }
fnType, start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function)) 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)}, "") b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "")
} }
// createGoroutineStartWrapper creates a wrapper for the task-based // createGoroutineStartWrapper creates a wrapper for the task-based
@@ -142,19 +140,15 @@ func (b *builder) createGo(instr *ssa.Go) {
// to last parameter of the function) is used for this wrapper. If hasContext is // 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 // false, the parameter bundle is assumed to have no context parameter and undef
// is passed instead. // is passed instead.
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value { func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
var wrapper llvm.Value var wrapper llvm.Value
b := &builder{ builder := c.ctx.NewBuilder()
compilerContext: c, defer builder.Dispose()
Builder: c.ctx.NewBuilder(),
}
defer b.Dispose()
var deadlock llvm.Value var deadlock llvm.Value
var deadlockType llvm.Type
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
deadlockType, deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function)) deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
} }
if !fn.IsAFunction().IsNil() { if !fn.IsAFunction().IsNil() {
@@ -166,14 +160,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
} }
// Create the wrapper. // 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, name+"$gowrapper", wrapperType) wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name)) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug { if c.Debug {
pos := c.program.Fset.Position(pos) pos := c.program.Fset.Position(pos)
@@ -194,25 +188,25 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
Optimized: true, Optimized: true,
}) })
wrapper.SetSubprogram(difunc) wrapper.SetSubprogram(difunc)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
} }
// Create the list of params for the call. // Create the list of params for the call.
paramTypes := fnType.ParamTypes() paramTypes := fn.Type().ElementType().ParamTypes()
if !hasContext { if !hasContext {
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
} }
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
if !hasContext { if !hasContext {
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
} }
// Create the call. // Create the call.
b.CreateCall(fnType, fn, params, "") builder.CreateCall(fn, params, "")
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ builder.CreateCall(deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.i8ptrType),
}, "") }, "")
} }
@@ -235,14 +229,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// merged into one. // merged into one.
// Create the wrapper. // 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) wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", "")) wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
entry := c.ctx.AddBasicBlock(wrapper, "entry") entry := c.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(entry) builder.SetInsertPointAtEnd(entry)
if c.Debug { if c.Debug {
pos := c.program.Fset.Position(pos) pos := c.program.Fset.Position(pos)
@@ -263,37 +257,37 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
Optimized: true, Optimized: true,
}) })
wrapper.SetSubprogram(difunc) wrapper.SetSubprogram(difunc)
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
} }
// Get the list of parameters, with the extra parameters at the end. // Get the list of parameters, with the extra parameters at the end.
paramTypes := fnType.ParamTypes() paramTypes := fn.Type().ElementType().ParamTypes()
paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes) params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
// Get the function pointer. // Get the function pointer.
fnPtr := params[len(params)-1] fnPtr := params[len(params)-1]
params = params[:len(params)-1] params = params[:len(params)-1]
// Create the call. // Create the call.
b.CreateCall(fnType, fnPtr, params, "") builder.CreateCall(fnPtr, params, "")
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
b.CreateCall(deadlockType, deadlock, []llvm.Value{ builder.CreateCall(deadlock, []llvm.Value{
llvm.Undef(c.dataPtrType), llvm.Undef(c.i8ptrType),
}, "") }, "")
} }
} }
if c.Scheduler == "asyncify" { if c.Scheduler == "asyncify" {
// The goroutine was terminated via deadlock. // The goroutine was terminated via deadlock.
b.CreateUnreachable() builder.CreateUnreachable()
} else { } else {
// Finish the function. Every basic block must end in a terminator, and // Finish the function. Every basic block must end in a terminator, and
// because goroutines never return a value we can simply return void. // because goroutines never return a value we can simply return void.
b.CreateRetVoid() builder.CreateRetVoid()
} }
// Return a ptrtoint of the wrapper, not the function itself. // Return a ptrtoint of the wrapper, not the function itself.
return b.CreatePtrToInt(wrapper, c.uintptrType, "") return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
} }
+15 -16
View File
@@ -5,7 +5,6 @@ package compiler
import ( import (
"fmt" "fmt"
"go/constant" "go/constant"
"go/token"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@@ -26,7 +25,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false) fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
asm := constant.StringVal(args[0].(*ssa.Const).Value) asm := constant.StringVal(args[0].(*ssa.Const).Value)
target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false) target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false)
return b.CreateCall(fnType, target, nil, ""), nil return b.CreateCall(target, nil, ""), nil
} }
// This is a compiler builtin, which allows assembly to be called in a flexible // This is a compiler builtin, which allows assembly to be called in a flexible
@@ -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") 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) 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: case *ssa.Call:
if r.Common() == instr { if r.Common() == instr {
break break
@@ -99,8 +98,8 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
constraints = append(constraints, "r") constraints = append(constraints, "r")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
// Memory references require a type starting with LLVM 14, // Memory references require a type in LLVM 14, probably as a
// probably as a preparation for opaque pointers. // preparation for opaque pointers.
err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23") err = b.makeError(instr.Pos(), "support for pointer operands was dropped in TinyGo 0.23")
return s return s
default: default:
@@ -121,7 +120,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
} }
fnType := llvm.FunctionType(outputType, argTypes, false) fnType := llvm.FunctionType(outputType, argTypes, false)
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0, false) target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0, false)
result := b.CreateCall(fnType, target, args, "") result := b.CreateCall(target, args, "")
if hasOutput { if hasOutput {
return result, nil return result, nil
} else { } else {
@@ -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 // The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly. // value supported by LLVM inline assembly.
func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error) { func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value) num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{} llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
@@ -154,7 +153,7 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
} else { } else {
constraints += ",{r" + strconv.Itoa(i) + "}" constraints += ",{r" + strconv.Itoa(i) + "}"
} }
llvmValue := b.getValue(arg, pos) llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue) llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
@@ -164,7 +163,7 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
constraints += ",~{r1},~{r2},~{r3}" constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil return b.CreateCall(target, llvmArgs, ""), nil
} }
// This is a compiler builtin which emits an inline SVCall instruction. It can // This is a compiler builtin which emits an inline SVCall instruction. It can
@@ -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 // The num parameter must be a constant. All other parameters may be any scalar
// value supported by LLVM inline assembly. // value supported by LLVM inline assembly.
// Same as emitSVCall but for AArch64 // 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) num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
llvmArgs := []llvm.Value{} llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
@@ -192,7 +191,7 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
} else { } else {
constraints += ",{x" + strconv.Itoa(i) + "}" constraints += ",{x" + strconv.Itoa(i) + "}"
} }
llvmValue := b.getValue(arg, pos) llvmValue := b.getValue(arg)
llvmArgs = append(llvmArgs, llvmValue) llvmArgs = append(llvmArgs, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
@@ -202,7 +201,7 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}" constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil return b.CreateCall(target, llvmArgs, ""), nil
} }
// This is a compiler builtin which emits CSR instructions. It can be one of: // This is a compiler builtin which emits CSR instructions. It can be one of:
@@ -227,24 +226,24 @@ func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
fnType := llvm.FunctionType(b.uintptrType, nil, false) fnType := llvm.FunctionType(b.uintptrType, nil, false)
asm := fmt.Sprintf("csrr $0, %d", csr) asm := fmt.Sprintf("csrr $0, %d", csr)
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0, false) target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0, false)
return b.CreateCall(fnType, target, nil, ""), nil return b.CreateCall(target, nil, ""), nil
case "Set": case "Set":
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false) fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrw %d, $0", csr) asm := fmt.Sprintf("csrw %d, $0", csr)
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0, false) 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(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "SetBits": case "SetBits":
// Note: it may be possible to optimize this to csrrsi in many cases. // Note: it may be possible to optimize this to csrrsi in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrs $0, %d, $1", csr) asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) 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(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
case "ClearBits": case "ClearBits":
// Note: it may be possible to optimize this to csrrci in many cases. // Note: it may be possible to optimize this to csrrci in many cases.
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false) fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
asm := fmt.Sprintf("csrrc $0, %d, $1", csr) asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false) 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(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name) return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
} }
+210 -554
View File
@@ -6,8 +6,6 @@ package compiler
// interface-lowering.go for more details. // interface-lowering.go for more details.
import ( import (
"encoding/binary"
"fmt"
"go/token" "go/token"
"go/types" "go/types"
"strconv" "strconv"
@@ -17,58 +15,6 @@ import (
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// Type kinds for basic types.
// They must match the constants for the Kind type in src/reflect/type.go.
var basicTypes = [...]uint8{
types.Bool: 1,
types.Int: 2,
types.Int8: 3,
types.Int16: 4,
types.Int32: 5,
types.Int64: 6,
types.Uint: 7,
types.Uint8: 8,
types.Uint16: 9,
types.Uint32: 10,
types.Uint64: 11,
types.Uintptr: 12,
types.Float32: 13,
types.Float64: 14,
types.Complex64: 15,
types.Complex128: 16,
types.String: 17,
types.UnsafePointer: 18,
}
// These must also match the constants for the Kind type in src/reflect/type.go.
const (
typeKindChan = 19
typeKindInterface = 20
typeKindPointer = 21
typeKindSlice = 22
typeKindArray = 23
typeKindSignature = 24
typeKindMap = 25
typeKindStruct = 26
)
// Flags stored in the first byte of the struct field byte array. Must be kept
// up to date with src/reflect/type.go.
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. // createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
// It tries to put the type in the interface value, but if that's not possible, // It tries to put the type in the interface value, but if that's not possible,
// it will do an allocation of the right size and put that in the interface // it will do an allocation of the right size and put that in the interface
@@ -77,417 +23,136 @@ const (
// An interface value is a {typecode, value} tuple named runtime._interface. // An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val}) itfValue := b.emitPointerPack([]llvm.Value{val})
itfType := b.getTypeCode(typ) itfTypeCodeGlobal := b.getTypeCode(typ)
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfType, 0, "") itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = b.CreateInsertValue(itf, itfValue, 1, "") itf = b.CreateInsertValue(itf, itfValue, 1, "")
return itf return itf
} }
// extractValueFromInterface extract the value from an interface value // extractValueFromInterface extract the value from an interface value
// (runtime._interface) under the assumption that it is of the type given in // (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. // doesn't match the underlying type of the interface.
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0] 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. // getTypeCode returns a reference to a type code.
// A type code is a pointer to a constant global that describes the type. // It returns a pointer to an external global which should be replaced with the
// This function returns a pointer to the 'kind' field (which might not be the // real type in the interface lowering pass.
// first field in the struct).
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
ms := c.program.MethodSets.MethodSet(typ) globalName := "reflect/types.type:" + getTypeCodeName(typ)
hasMethodSet := ms.Len() != 0 global := c.mod.NamedGlobal(globalName)
_, isInterface := typ.Underlying().(*types.Interface)
if isInterface {
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)
}
if global.IsNil() { if global.IsNil() {
var typeFields []llvm.Value // Create a new typecode global.
// Define the type fields. These must match the structs in global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName)
// src/reflect/type.go (ptrType, arrayType, etc). See the comment at the // Some type classes contain more information for underlying types or
// top of src/reflect/type.go for more information on the layout of these structs. // element types. Store it directly in the typecode global to make
typeFieldTypes := []*types.Var{ // reflect lowering simpler.
types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]), var references llvm.Value
} var length int64
var methodSet llvm.Value
var ptrTo llvm.Value
var typeAssert llvm.Value
switch typ := typ.(type) { switch typ := typ.(type) {
case *types.Basic:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
case *types.Named: case *types.Named:
name := typ.Obj().Name() references = c.getTypeCode(typ.Underlying())
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]),
types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))),
)
case *types.Chan: case *types.Chan:
typeFieldTypes = append(typeFieldTypes, references = c.getTypeCode(typ.Elem())
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: case *types.Pointer:
typeFieldTypes = append(typeFieldTypes, references = c.getTypeCode(typ.Elem())
types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]),
types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]),
)
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, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))),
)
case *types.Interface:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
// TODO: methods
case *types.Signature:
typeFieldTypes = append(typeFieldTypes,
types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]),
)
// TODO: signature params and return values
}
if hasMethodSet {
// This method set is appended at the start of the struct. It is
// removed in the interface lowering pass.
// TODO: don't remove these and instead do what upstream Go is doing
// instead. See: https://research.swtch.com/interfaces. This can
// likely be optimized in LLVM using
// https://llvm.org/docs/TypeMetadata.html.
typeFieldTypes = append([]*types.Var{
types.NewVar(token.NoPos, nil, "methodSet", types.Typ[types.UnsafePointer]),
}, typeFieldTypes...)
}
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)
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Underlying()), // underlying
pkgPathPtr, // pkgpath pointer
c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name
}
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
}
case *types.Slice: case *types.Slice:
typeFields = []llvm.Value{ references = c.getTypeCode(typ.Elem())
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods
c.getTypeCode(types.NewPointer(typ)), // ptrTo
c.getTypeCode(typ.Elem()), // elementType
}
case *types.Pointer:
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods
c.getTypeCode(typ.Elem()),
}
case *types.Array: case *types.Array:
typeFields = []llvm.Value{ references = c.getTypeCode(typ.Elem())
llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods length = typ.Len()
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
}
case *types.Struct: case *types.Struct:
var pkgpath string // Take a pointer to the typecodeID of the first field (if it exists).
if typ.NumFields() > 0 { structGlobal := c.makeStructTypeFields(typ)
if pkg := typ.Field(0).Pkg(); pkg != nil { references = llvm.ConstBitCast(structGlobal, global.Type())
pkgpath = pkg.Path()
}
}
pkgPathPtr := c.pkgPathPtr(pkgpath)
llvmStructType := c.getLLVMType(typ)
size := c.targetData.TypeStoreSize(llvmStructType)
typeFields = []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), 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
}
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
}
if typ.Tag(i) != "" {
flags |= structFieldFlagHasTag
}
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"
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))))
}
data += string([]byte{byte(len(typ.Tag(i)))}) + typ.Tag(i)
}
dataInitializer := c.ctx.ConstString(data, false)
dataGlobal := llvm.AddGlobal(c.mod, dataInitializer.Type(), globalName+"."+field.Name())
dataGlobal.SetInitializer(dataInitializer)
dataGlobal.SetAlignment(1)
dataGlobal.SetUnnamedAddr(true)
dataGlobal.SetLinkage(llvm.InternalLinkage)
dataGlobal.SetGlobalConstant(true)
fieldType := c.getTypeCode(field.Type())
fields = append(fields, llvm.ConstNamedStruct(structFieldType, []llvm.Value{
fieldType,
llvm.ConstGEP(dataGlobal.GlobalValueType(), dataGlobal, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}),
}))
}
typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields))
case *types.Interface: case *types.Interface:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} methodSetGlobal := c.getInterfaceMethodSet(typ)
// TODO: methods references = llvm.ConstBitCast(methodSetGlobal, global.Type())
case *types.Signature:
typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))}
// TODO: params, return values, etc
} }
// Prepend metadata byte. if _, ok := typ.Underlying().(*types.Interface); !ok {
typeFields = append([]llvm.Value{ methodSet = c.getTypeMethodSet(typ)
llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false),
}, typeFields...)
if hasMethodSet {
typeFields = append([]llvm.Value{
c.getTypeMethodSet(typ),
}, typeFields...)
}
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 {
alignment = 4
}
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
if isLocal {
global.SetLinkage(llvm.InternalLinkage)
} else { } else {
global.SetLinkage(llvm.LinkOnceODRLinkage) typeAssert = c.getInterfaceImplementsFunc(typ)
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
} }
if _, ok := typ.Underlying().(*types.Pointer); !ok {
ptrTo = c.getTypeCode(types.NewPointer(typ))
}
globalValue := llvm.ConstNull(global.Type().ElementType())
if !references.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
}
if length != 0 {
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
}
if !methodSet.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
}
if !ptrTo.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
}
if !typeAssert.IsNil() {
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
}
global.SetInitializer(globalValue)
global.SetLinkage(llvm.LinkOnceODRLinkage)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetAlignment(int(alignment)) }
if c.Debug { return global
file := c.getDIFile("<Go type>") }
diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{
Name: "type " + typ.String(), // makeStructTypeFields creates a new global that stores all type information
File: file, // related to this struct type, and returns the resulting global. This global is
Line: 1, // actually an array of all the fields in the structs.
Type: c.getDIType(globalType), func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
LocalToUnit: false, // The global is an array of runtime.structField structs.
Expr: c.dibuilder.CreateExpression(nil), runtimeStructField := c.getLLVMRuntimeType("structField")
AlignInBits: uint32(alignment * 8), structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := llvm.ConstNull(structGlobalType)
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := llvm.ConstNull(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
}) })
global.AddMetadata(0, diglobal) fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
} }
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
} }
offset := uint64(0) structGlobal.SetInitializer(structGlobalValue)
if hasMethodSet { structGlobal.SetUnnamedAddr(true)
// The pointer to the method set is always the first element of the structGlobal.SetLinkage(llvm.PrivateLinkage)
// global (if there is a method set). However, the pointer we return return structGlobal
// should point to the 'kind' field not the method set.
offset = 1
}
return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), offset, false),
})
} }
// getTypeKind returns the type kind for the given type, as defined by var basicTypes = [...]string{
// reflect.Kind.
func getTypeKind(t types.Type) uint8 {
switch t := t.Underlying().(type) {
case *types.Basic:
return basicTypes[t.Kind()]
case *types.Chan:
return typeKindChan
case *types.Interface:
return typeKindInterface
case *types.Pointer:
return typeKindPointer
case *types.Slice:
return typeKindSlice
case *types.Array:
return typeKindArray
case *types.Signature:
return typeKindSignature
case *types.Map:
return typeKindMap
case *types.Struct:
return typeKindStruct
default:
panic("unknown type")
}
}
var basicTypeNames = [...]string{
types.Bool: "bool", types.Bool: "bool",
types.Int: "int", types.Int: "int",
types.Int8: "int8", types.Int8: "int8",
@@ -511,93 +176,57 @@ var basicTypeNames = [...]string{
// getTypeCodeName returns a name for this type that can be used in the // getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect // interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum. // package. See getTypeCodeNum.
func getTypeCodeName(t types.Type) (string, bool) { func getTypeCodeName(t types.Type) string {
switch t := t.(type) { switch t := t.(type) {
case *types.Named: case *types.Named:
if t.Obj().Parent() != t.Obj().Pkg().Scope() { return "named:" + t.String()
return "named:" + t.String() + "$local", true
}
return "named:" + t.String(), false
case *types.Array: case *types.Array:
s, isLocal := getTypeCodeName(t.Elem()) return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
case *types.Basic: case *types.Basic:
return "basic:" + basicTypeNames[t.Kind()], false return "basic:" + basicTypes[t.Kind()]
case *types.Chan: case *types.Chan:
s, isLocal := getTypeCodeName(t.Elem()) return "chan:" + 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
case *types.Interface: case *types.Interface:
isLocal := false
methods := make([]string, t.NumMethods()) methods := make([]string, t.NumMethods())
for i := 0; i < t.NumMethods(); i++ { for i := 0; i < t.NumMethods(); i++ {
name := t.Method(i).Name() name := t.Method(i).Name()
if !token.IsExported(name) { if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name name = t.Method(i).Pkg().Path() + "." + name
} }
s, local := getTypeCodeName(t.Method(i).Type()) methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
if local {
isLocal = true
}
methods[i] = name + ":" + s
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal return "interface:" + "{" + strings.Join(methods, ",") + "}"
case *types.Map: case *types.Map:
keyType, keyLocal := getTypeCodeName(t.Key()) keyType := getTypeCodeName(t.Key())
elemType, elemLocal := getTypeCodeName(t.Elem()) elemType := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal return "map:" + "{" + keyType + "," + elemType + "}"
case *types.Pointer: case *types.Pointer:
s, isLocal := getTypeCodeName(t.Elem()) return "pointer:" + getTypeCodeName(t.Elem())
return "pointer:" + s, isLocal
case *types.Signature: case *types.Signature:
isLocal := false
params := make([]string, t.Params().Len()) params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ { for i := 0; i < t.Params().Len(); i++ {
s, local := getTypeCodeName(t.Params().At(i).Type()) params[i] = getTypeCodeName(t.Params().At(i).Type())
if local {
isLocal = true
}
params[i] = s
} }
results := make([]string, t.Results().Len()) results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ { for i := 0; i < t.Results().Len(); i++ {
s, local := getTypeCodeName(t.Results().At(i).Type()) results[i] = getTypeCodeName(t.Results().At(i).Type())
if local {
isLocal = true
}
results[i] = s
} }
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
case *types.Slice: case *types.Slice:
s, isLocal := getTypeCodeName(t.Elem()) return "slice:" + getTypeCodeName(t.Elem())
return "slice:" + s, isLocal
case *types.Struct: case *types.Struct:
elems := make([]string, t.NumFields()) elems := make([]string, t.NumFields())
isLocal := false
for i := 0; i < t.NumFields(); i++ { for i := 0; i < t.NumFields(); i++ {
embedded := "" embedded := ""
if t.Field(i).Embedded() { if t.Field(i).Embedded() {
embedded = "#" embedded = "#"
} }
s, local := getTypeCodeName(t.Field(i).Type()) elems[i] = embedded + t.Field(i).Name() + ":" + getTypeCodeName(t.Field(i).Type())
if local {
isLocal = true
}
elems[i] = embedded + t.Field(i).Name() + ":" + s
if t.Tag(i) != "" { if t.Tag(i) != "" {
elems[i] += "`" + t.Tag(i) + "`" elems[i] += "`" + t.Tag(i) + "`"
} }
} }
return "struct:" + "{" + strings.Join(elems, ",") + "}", isLocal return "struct:" + "{" + strings.Join(elems, ",") + "}"
default: default:
panic("unknown type: " + t.String()) panic("unknown type: " + t.String())
} }
@@ -606,40 +235,75 @@ func getTypeCodeName(t types.Type) (string, bool) {
// getTypeMethodSet returns a reference (GEP) to a global method set. This // getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass. // method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
globalName := typ.String() + "$methodset" global := c.mod.NamedGlobal(typ.String() + "$methodset")
global := c.mod.NamedGlobal(globalName) zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if global.IsNil() { if !global.IsNil() {
ms := c.program.MethodSets.MethodSet(typ) // the method set already exists
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
// Create method set.
var signatures, wrappers []llvm.Value
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method)
llvmFnType, llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn)
wrappers = append(wrappers, wrapper)
}
// Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.dataPtrType, signatures),
c.ctx.ConstStruct(wrappers, false),
}, false)
global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName)
global.SetInitializer(globalValue)
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
} }
return global
ms := c.program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
fn := c.program.MethodValue(method)
llvmFn := c.getFunction(fn)
if llvmFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
}
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstPtrToInt(wrapper, c.uintptrType),
})
methods[i] = methodInfo
}
arrayType := llvm.ArrayType(interfaceMethodInfoType, len(methods))
value := llvm.ConstArray(interfaceMethodInfoType, methods)
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
name := typ.String()
if _, ok := typ.(*types.Named); !ok {
// Anonymous interface.
name = "reflect/types.interface:" + name
}
global := c.mod.NamedGlobal(name + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// Every method is a *i8 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method)
}
value := llvm.ConstArray(c.i8ptrType, methods)
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.LinkOnceODRLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
} }
// getMethodSignatureName returns a unique name (that can be used as the name of // getMethodSignatureName returns a unique name (that can be used as the name of
@@ -681,33 +345,26 @@ func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
// Type asserts on concrete types are trivial: just compare type numbers. Type // Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult, see the comments in the function. // asserts on interfaces are more difficult, see the comments in the function.
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { func (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) assertedType := b.getLLVMType(expr.AssertedType)
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type") actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{} 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, []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 interface type with methods.
// 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}, "")
}
} else { } else {
name, _ := getTypeCodeName(expr.AssertedType) globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
globalName := "reflect/types.typeid:" + name
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() { if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global. // Create a new typecode global.
@@ -780,14 +437,13 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string {
return strings.Join(methods, "; ") return strings.Join(methods, "; ")
} }
// getInterfaceImplementsFunc returns a declared function that works as a type // getInterfaceImplementsfunc returns a declared function that works as a type
// switch. The interface lowering pass will define this function. // switch. The interface lowering pass will define this function.
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value { func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
s, _ := getTypeCodeName(assertedType.Underlying()) fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
fnName := s + ".$typeassert"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false) llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
@@ -800,8 +456,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll
// thunk is declared, not defined: it will be defined by the interface lowering // thunk is declared, not defined: it will be defined by the interface lowering
// pass. // pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
s, _ := getTypeCodeName(instr.Value.Type().Underlying()) fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
fnName := s + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature) sig := instr.Method.Type().(*types.Signature)
@@ -809,8 +464,8 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
for i := 0; i < sig.Params().Len(); i++ { for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i)) paramTuple = append(paramTuple, sig.Params().At(i))
} }
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
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)).ElementType()
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
@@ -825,7 +480,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
// value, dereferences or unpacks it if necessary, and calls the real method. // value, dereferences or unpacks it if necessary, and calls the real method.
// If the method to wrap has a pointer receiver, no wrapping is necessary and // If the method to wrap has a pointer receiver, no wrapping is necessary and
// the function is returned directly. // the function is returned directly.
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType llvm.Type, llvmFn llvm.Value) llvm.Value { func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
wrapperName := llvmFn.Name() + "$invoke" wrapperName := llvmFn.Name() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName) wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() { if !wrapper.IsNil() {
@@ -850,8 +505,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
} }
// create wrapper function // create wrapper function
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...) fnType := llvmFn.Type().ElementType()
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper) c.addStandardAttributes(wrapper)
@@ -878,11 +534,11 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0] receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...) params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind { if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFnType, llvmFn, params, "") b.CreateCall(llvmFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
} else { } else {
ret := b.CreateCall(llvmFnType, llvmFn, params, "ret") ret := b.CreateCall(llvmFn, params, "ret")
b.CreateRet(ret) b.CreateRet(ret)
} }
@@ -946,7 +602,7 @@ func typestring(t types.Type) string {
case *types.Array: case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem()) return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic: case *types.Basic:
return basicTypeNames[t.Kind()] return basicTypes[t.Kind()]
case *types.Chan: case *types.Chan:
switch t.Dir() { switch t.Dir() {
case types.SendRecv: case types.SendRecv:
+6 -8
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 // 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 // receiver and is a global. This is rather strict but still allows for
// idiomatic Go code. // idiomatic Go code.
funcValue := b.getValue(instr.Args[1], getPos(instr)) funcValue := b.getValue(instr.Args[1])
if funcValue.IsAConstant().IsNil() { if funcValue.IsAConstant().IsNil() {
// Try to determine the cause of the non-constantness for a nice error // Try to determine the cause of the non-constantness for a nice error
// message. // message.
@@ -36,7 +36,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
// Fall back to a generic error. // Fall back to a generic error.
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant") 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) funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
// Create a new global of type runtime/interrupt.handle. Globals of this // Create a new global of type runtime/interrupt.handle. Globals of this
@@ -49,11 +49,9 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
initializer := llvm.ConstNull(globalLLVMType) initializer := llvm.ConstNull(globalLLVMType)
initializer = b.CreateInsertValue(initializer, funcContext, 0, "") initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0})
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "") initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1})
initializer = b.CreateInsertValue(initializer, llvm.ConstNamedStruct(globalLLVMType.StructElementTypes()[2], []llvm.Value{ initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
llvm.ConstInt(b.intType, uint64(id.Int64()), true),
}), 2, "")
global.SetInitializer(initializer) global.SetInitializer(initializer)
// Add debug info to the interrupt global. // Add debug info to the interrupt global.
@@ -87,7 +85,7 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false) useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType) useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
} }
b.CreateCall(useFn.GlobalValueType(), useFn, []llvm.Value{interrupt}, "") b.CreateCall(useFn, []llvm.Value{interrupt}, "")
} }
return interrupt, nil return interrupt, nil
+15 -171
View File
@@ -23,10 +23,6 @@ func (b *builder) defineIntrinsicFunction() {
b.createMemoryCopyImpl() b.createMemoryCopyImpl()
case name == "runtime.memzero": case name == "runtime.memzero":
b.createMemoryZeroImpl() b.createMemoryZeroImpl()
case name == "runtime.stacksave":
b.createStackSaveImpl()
case name == "runtime.KeepAlive":
b.createKeepAliveImpl()
case strings.HasPrefix(name, "runtime/volatile.Load"): case strings.HasPrefix(name, "runtime/volatile.Load"):
b.createVolatileLoad() b.createVolatileLoad()
case strings.HasPrefix(name, "runtime/volatile.Store"): case strings.HasPrefix(name, "runtime/volatile.Store"):
@@ -48,18 +44,18 @@ func (b *builder) defineIntrinsicFunction() {
// and will otherwise be lowered to regular libc memcpy/memmove calls. // and will otherwise be lowered to regular libc memcpy/memmove calls.
func (b *builder) createMemoryCopyImpl() { func (b *builder) createMemoryCopyImpl() {
b.createFunctionStart(true) b.createFunctionStart(true)
fnName := "llvm." + b.fn.Name() + ".p0.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) fnName := "llvm." + b.fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
llvmFn := b.mod.NamedFunction(fnName) llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() { 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) llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
} }
var params []llvm.Value var params []llvm.Value
for _, param := range b.fn.Params { for _, param := range b.fn.Params {
params = append(params, b.getValue(param, getPos(b.fn))) params = append(params, b.getValue(param))
} }
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false)) params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") b.CreateCall(llvmFn, params, "")
b.CreateRetVoid() b.CreateRetVoid()
} }
@@ -68,56 +64,19 @@ func (b *builder) createMemoryCopyImpl() {
// regular libc memset calls if they aren't optimized out in a different way. // regular libc memset calls if they aren't optimized out in a different way.
func (b *builder) createMemoryZeroImpl() { func (b *builder) createMemoryZeroImpl() {
b.createFunctionStart(true) b.createFunctionStart(true)
llvmFn := b.getMemsetFunc() 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{ 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), 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), llvm.ConstInt(b.ctx.Int1Type(), 0, false),
} }
b.CreateCall(llvmFn.GlobalValueType(), llvmFn, params, "") b.CreateCall(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))
pointerValue := b.CreateExtractValue(interfaceValue, 1, "")
// 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.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() b.CreateRetVoid()
} }
@@ -158,123 +117,8 @@ func (b *builder) defineMathOp() {
// Create a call to the intrinsic. // Create a call to the intrinsic.
args := make([]llvm.Value, len(b.fn.Params)) args := make([]llvm.Value, len(b.fn.Params))
for i, param := range 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, "") result := b.CreateCall(llvmFn, args, "")
b.CreateRet(result) b.CreateRet(result)
} }
// 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
}
}
+4 -1
View File
@@ -70,7 +70,10 @@ func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, special
return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error()) return fmt.Errorf("failed to verify element type of array type %s: %s", t.String(), err.Error())
} }
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
// Pointers can't be checked in an opaque pointer world. // check underlying type
if err := c.checkType(t.ElementType(), checked, specials); err != nil {
return fmt.Errorf("failed to verify underlying type of pointer type %s: %s", t.String(), err.Error())
}
case llvm.VectorTypeKind: case llvm.VectorTypeKind:
// check element type // check element type
if err := c.checkType(t.ElementType(), checked, specials); err != nil { if err := c.checkType(t.ElementType(), checked, specials); err != nil {
+30 -196
View File
@@ -7,7 +7,6 @@ import (
"math/big" "math/big"
"strings" "strings"
"github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/compiler/llvmutil" "github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -21,7 +20,7 @@ import (
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // 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) return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
} }
@@ -52,172 +51,29 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// emitPointerPack packs the list of values into a single pointer value using // emitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the // bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data. // pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and
// deduplicated.
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value { func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
valueTypes := make([]llvm.Type, len(values)) return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values)
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := b.ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
size := b.targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(b.dataPtrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return values[0]
} else if size <= b.targetData.TypeAllocSize(b.dataPtrType) {
// 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")
}
// 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, "")
if size < b.targetData.TypeAllocSize(b.dataPtrType) {
// 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)
}
// Store all values in the alloca.
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, "")
b.CreateStore(value, gep)
}
// Load value (the *i8) from the alloca.
result := b.CreateLoad(b.dataPtrType, packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer.
packedSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(packedAlloc.Type()), false)
b.emitLifetimeEnd(packedAlloc, packedSize)
return result
} else {
// Check if the values are all constants.
constant := true
for _, v := range values {
if !v.IsConstant() {
constant = false
break
}
}
if constant {
// The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
global := llvm.AddGlobal(b.mod, packedType, b.pkg.Path()+"$pack")
global.SetInitializer(b.ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage)
return global
}
// 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{
sizeValue,
llvm.ConstNull(b.dataPtrType),
llvm.Undef(b.dataPtrType), // unused context parameter
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
}
// Store all values in the heap pointer.
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, "")
b.CreateStore(value, gep)
}
// Return the original heap allocation pointer, which already is an *i8.
return packedAlloc
}
} }
// emitPointerUnpack extracts a list of values packed using emitPointerPack. // emitPointerUnpack extracts a list of values packed using emitPointerPack.
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value { func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
packedType := b.ctx.StructType(valueTypes, false) return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
// Get a correctly-typed pointer to the packed data.
var packedAlloc llvm.Value
needsLifetimeEnd := false
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) {
// 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
} else {
// Packed data stored on the heap.
packedAlloc = ptr
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if b.targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}
gep := b.CreateInBoundsGEP(packedType, packedAlloc, indices, "")
values[i] = b.CreateLoad(valueType, gep, "")
}
if needsLifetimeEnd {
allocSize := llvm.ConstInt(b.ctx.Int64Type(), b.targetData.TypeAllocSize(b.uintptrType), false)
b.emitLifetimeEnd(packedAlloc, allocSize)
}
return values
} }
// makeGlobalArray creates a new LLVM global with the given name and integers as // makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global and initializer type. // contents, and returns the global.
// Note that it is left with the default linkage etc., you should set // Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself. // linkage/constant/etc properties yourself.
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) (llvm.Type, llvm.Value) { func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(elementType, len(buf)) globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name) global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType) value := llvm.Undef(globalType)
for i := 0; i < len(buf); i++ { for i := 0; i < len(buf); i++ {
ch := uint64(buf[i]) ch := uint64(buf[i])
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "") value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
} }
global.SetInitializer(value) global.SetInitializer(value)
return globalType, global return global
} }
// createObjectLayout returns a LLVM value (of type i8*) that describes where // createObjectLayout returns a LLVM value (of type i8*) that describes where
@@ -228,8 +84,6 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
// which words contain a pointer (indicated by setting the given bit to 1). For // which words contain a pointer (indicated by setting the given bit to 1). For
// arrays, only the element is stored. This works because the GC knows the // arrays, only the element is stored. This works because the GC knows the
// object size and can therefore know how this value is repeated in the object. // object size and can therefore know how this value is repeated in the object.
//
// 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 { func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
// Use the element type for arrays. This works even for nested arrays. // Use the element type for arrays. This works even for nested arrays.
for { for {
@@ -251,12 +105,12 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// Do a few checks to see whether we need to generate any object layout // Do a few checks to see whether we need to generate any object layout
// information at all. // information at all.
objectSizeBytes := c.targetData.TypeAllocSize(t) objectSizeBytes := c.targetData.TypeAllocSize(t)
pointerSize := c.targetData.TypeAllocSize(c.dataPtrType) pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
pointerAlignment := c.targetData.PrefTypeAlignment(c.dataPtrType) pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
if objectSizeBytes < pointerSize { if objectSizeBytes < pointerSize {
// Too small to contain a pointer. // Too small to contain a pointer.
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
bitmap := c.getPointerBitmap(t, pos) bitmap := c.getPointerBitmap(t, pos)
if bitmap.BitLen() == 0 { if bitmap.BitLen() == 0 {
@@ -264,13 +118,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// TODO: this can be done in many other cases, e.g. when allocating an // TODO: this can be done in many other cases, e.g. when allocating an
// array (like [4][]byte, which repeats a slice 4 times). // array (like [4][]byte, which repeats a slice 4 times).
layout := (uint64(1) << 1) | 1 layout := (uint64(1) << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
if objectSizeBytes%uint64(pointerAlignment) != 0 { if objectSizeBytes%uint64(pointerAlignment) != 0 {
// This shouldn't happen except for packed structs, which aren't // This shouldn't happen except for packed structs, which aren't
// currently used. // currently used.
c.addError(pos, "internal error: unexpected object size for object with pointer field") c.addError(pos, "internal error: unexpected object size for object with pointer field")
return llvm.ConstNull(c.dataPtrType) return llvm.ConstNull(c.i8ptrType)
} }
objectSizeWords := objectSizeBytes / uint64(pointerAlignment) objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
@@ -295,7 +149,7 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
// The runtime knows that if the least significant bit of the pointer is // The runtime knows that if the least significant bit of the pointer is
// set, the pointer contains the value itself. // set, the pointer contains the value itself.
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1 layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.dataPtrType) return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
} }
// Unfortunately, the object layout is too big to fit in a pointer-sized // Unfortunately, the object layout is too big to fit in a pointer-sized
@@ -306,13 +160,12 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap) globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if !global.IsNil() { if !global.IsNil() {
return global return llvm.ConstBitCast(global, c.i8ptrType)
} }
// Create the global initializer. // Create the global initializer.
bitmapBytes := make([]byte, int(objectSizeWords+7)/8) bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
bitmap.FillBytes(bitmapBytes) bitmap.FillBytes(bitmapBytes)
reverseBytes(bitmapBytes) // big-endian to little-endian
var bitmapByteValues []llvm.Value var bitmapByteValues []llvm.Value
for _, b := range bitmapBytes { for _, b := range bitmapBytes {
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false)) bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
@@ -357,13 +210,13 @@ func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Va
global.AddMetadata(0, diglobal) global.AddMetadata(0, diglobal)
} }
return global return llvm.ConstBitCast(global, c.i8ptrType)
} }
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a // 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. // 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 { func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
alignment := c.targetData.PrefTypeAlignment(c.dataPtrType) alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
switch typ.TypeKind() { switch typ.TypeKind() {
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind: case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
return big.NewInt(0) return big.NewInt(0)
@@ -376,7 +229,7 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In
// of type uintptr, but before the LowerFuncValues pass it actually // of type uintptr, but before the LowerFuncValues pass it actually
// contains a pointer (ptrtoint) to a global. This trips up the // contains a pointer (ptrtoint) to a global. This trips up the
// interp package. Therefore, make the id field a pointer for now. // interp package. Therefore, make the id field a pointer for now.
typ = c.ctx.StructType([]llvm.Type{c.dataPtrType, c.dataPtrType}, false) typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
} }
for i, subtyp := range typ.StructElementTypes() { for i, subtyp := range typ.StructElementTypes() {
subptrs := c.getPointerBitmap(subtyp, pos) subptrs := c.getPointerBitmap(subtyp, pos)
@@ -419,11 +272,18 @@ func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.In
} }
} }
// 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 names ("armv6", "thumbv7m", etc) merged into a single
// architecture name ("arm"). // architecture name ("arm").
func (c *compilerContext) archFamily() string { 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 // isThumb returns whether we're in ARM or in Thumb mode. It panics if the
@@ -447,36 +307,10 @@ func (c *compilerContext) isThumb() bool {
// readStackPointer emits a LLVM intrinsic call that returns the current stack // readStackPointer emits a LLVM intrinsic call that returns the current stack
// pointer as an *i8. // pointer as an *i8.
func (b *builder) readStackPointer() llvm.Value { func (b *builder) readStackPointer() llvm.Value {
name := "llvm.stacksave.p0" stacksave := b.mod.NamedFunction("llvm.stacksave")
if llvmutil.Version() < 18 {
name = "llvm.stacksave" // backwards compatibility with LLVM 17 and below
}
stacksave := b.mod.NamedFunction(name)
if stacksave.IsNil() { if stacksave.IsNil() {
fnType := llvm.FunctionType(b.dataPtrType, nil, false) fnType := llvm.FunctionType(b.i8ptrType, nil, false)
stacksave = llvm.AddFunction(b.mod, name, fnType) stacksave = llvm.AddFunction(b.mod, "llvm.stacksave", fnType)
}
return b.CreateCall(stacksave.GlobalValueType(), stacksave, nil, "")
}
// 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) {
for i := len(buf)/2 - 1; i >= 0; i-- {
opp := len(buf) - 1 - i
buf[i], buf[opp] = buf[opp], buf[i]
} }
return b.CreateCall(stacksave, nil, "")
} }
+24 -72
View File
@@ -1,5 +1,5 @@
// Package llvmutil contains utility functions used across multiple compiler // 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. // transformation packages.
// //
// Normally, utility packages are avoided. However, in this case, the utility // Normally, utility packages are avoided. However, in this case, the utility
@@ -7,12 +7,7 @@
// places would be a big risk if only one of them is updated. // places would be a big risk if only one of them is updated.
package llvmutil package llvmutil
import ( import "tinygo.org/x/go-llvm"
"strconv"
"strings"
"tinygo.org/x/go-llvm"
)
// CreateEntryBlockAlloca creates a new alloca in the entry block, even though // CreateEntryBlockAlloca creates a new alloca in the entry block, even though
// the IR builder is located elsewhere. It assumes that the insert point is // the IR builder is located elsewhere. It assumes that the insert point is
@@ -31,19 +26,20 @@ func CreateEntryBlockAlloca(builder llvm.Builder, t llvm.Type, name string) llvm
} }
// CreateTemporaryAlloca creates a new alloca in the entry block and adds // 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. // before this point.
// //
// This is useful for creating temporary allocas for intrinsics. Don't forget to // This is useful for creating temporary allocas for intrinsics. Don't forget to
// end the lifetime using emitLifetimeEnd after you're done with it. // 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() ctx := t.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca = CreateEntryBlockAlloca(builder, t, name) alloca = CreateEntryBlockAlloca(builder, t, name)
bitcast = builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return return
} }
@@ -52,19 +48,19 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
ctx := mod.Context() ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout()) targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose() defer targetData.Dispose()
i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
alloca := CreateEntryBlockAlloca(builder, t, name) alloca := CreateEntryBlockAlloca(builder, t, name)
builder.SetInsertPointBefore(inst) builder.SetInsertPointBefore(inst)
bitcast := builder.CreateBitCast(alloca, i8ptrType, name+".bitcast")
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false) size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod) builder.CreateCall(getLifetimeStartFunc(mod), []llvm.Value{size, bitcast}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() { if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next) builder.SetInsertPointBefore(next)
} else { } else {
builder.SetInsertPointAtEnd(inst.InstructionParent()) builder.SetInsertPointAtEnd(inst.InstructionParent())
} }
fnType, fn = getLifetimeEndFunc(mod) builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, bitcast}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return alloca return alloca
} }
@@ -72,36 +68,33 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
// llvm.lifetime.end intrinsic. It is commonly used together with // llvm.lifetime.end intrinsic. It is commonly used together with
// createTemporaryAlloca. // createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) { func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
fnType, fn := getLifetimeEndFunc(mod) builder.CreateCall(getLifetimeEndFunc(mod), []llvm.Value{size, ptr}, "")
builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
} }
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it // getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeStartFunc(mod llvm.Module) llvm.Value {
fnName := "llvm.lifetime.start.p0" fn := mod.NamedFunction("llvm.lifetime.start.p0i8")
fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.start.p0i8", fnType)
} }
return fnType, fn return fn
} }
// getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it // getLifetimeEndFunc returns the llvm.lifetime.end intrinsic and creates it
// first if it doesn't exist yet. // first if it doesn't exist yet.
func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) { func getLifetimeEndFunc(mod llvm.Module) llvm.Value {
fnName := "llvm.lifetime.end.p0" fn := mod.NamedFunction("llvm.lifetime.end.p0i8")
fn := mod.NamedFunction(fnName)
ctx := mod.Context() ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0) i8ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() { if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType) fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), i8ptrType}, false)
fn = llvm.AddFunction(mod, "llvm.lifetime.end.p0i8", fnType)
} }
return fnType, fn return fn
} }
// SplitBasicBlock splits a LLVM basic block into two parts. All instructions // SplitBasicBlock splits a LLVM basic block into two parts. All instructions
@@ -175,44 +168,3 @@ func SplitBasicBlock(builder llvm.Builder, afterInst llvm.Value, insertAfter llv
return newBlock return newBlock
} }
// AppendToGlobal appends 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).
var usedValues []llvm.Value
if used := mod.NamedGlobal(globalName); !used.IsNil() {
builder := mod.Context().NewBuilder()
defer builder.Dispose()
usedInitializer := used.Initializer()
num := usedInitializer.Type().ArrayLength()
for i := 0; i < num; i++ {
usedValues = append(usedValues, builder.CreateExtractValue(usedInitializer, i, ""))
}
used.EraseFromParentAsGlobal()
}
// Add the new values.
ptrType := 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))
}
// Create a new array (with the old and new values).
usedInitializer := llvm.ConstArray(ptrType, 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
}
+182
View File
@@ -0,0 +1,182 @@
package llvmutil
// This file contains utility functions to pack and unpack sets of values. It
// can take in a list of values and tries to store it efficiently in the pointer
// itself if possible and legal.
import (
"tinygo.org/x/go-llvm"
)
// EmitPointerPack packs the list of values into a single pointer value using
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and deduplicated.
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
}
packedType := ctx.StructType(valueTypes, false)
// Allocate memory for the packed data.
size := targetData.TypeAllocSize(packedType)
if size == 0 {
return llvm.ConstPointerNull(i8ptrType)
} else if len(values) == 1 && values[0].Type().TypeKind() == llvm.PointerTypeKind {
return builder.CreateBitCast(values[0], i8ptrType, "pack.ptr")
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data fits in a pointer, so store it directly inside the
// pointer.
if len(values) == 1 && values[0].Type().TypeKind() == llvm.IntegerTypeKind {
// Try to keep this cast in SSA form.
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
}
// Because packedType is a struct and we have to cast it to a *i8, store
// it in a *i8 alloca first and load the *i8 value from there. This is
// effectively a bitcast.
packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "")
if size < targetData.TypeAllocSize(i8ptrType) {
// The alloca is bigger than the value that will be stored in it.
// To avoid having some bits undefined, zero the alloca first.
// Hopefully this will get optimized away.
builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc)
}
// Store all values in the alloca.
packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAllocCast, indices, "")
builder.CreateStore(value, gep)
}
// Load value (the *i8) from the alloca.
result := builder.CreateLoad(packedAlloc, "")
// End the lifetime of the alloca, to help the optimizer.
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
return result
} else {
// Check if the values are all constants.
constant := true
for _, v := range values {
if !v.IsConstant() {
constant = false
break
}
}
if constant {
// The data is known at compile time, so store it in a constant global.
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
global := llvm.AddGlobal(mod, packedType, prefix+"$pack")
global.SetInitializer(ctx.ConstStruct(values, false))
global.SetGlobalConstant(true)
global.SetUnnamedAddr(true)
global.SetLinkage(llvm.InternalLinkage)
return llvm.ConstBitCast(global, i8ptrType)
}
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(uintptrType, size, false)
alloc := mod.NamedFunction("runtime.alloc")
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
sizeValue,
llvm.ConstNull(i8ptrType),
llvm.Undef(i8ptrType), // unused context parameter
}, "")
if needsStackObjects {
trackPointer := mod.NamedFunction("runtime.trackPointer")
builder.CreateCall(trackPointer, []llvm.Value{
packedHeapAlloc,
llvm.Undef(i8ptrType), // unused context parameter
}, "")
}
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
// Store all values in the heap pointer.
for i, value := range values {
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
builder.CreateStore(value, gep)
}
// Return the original heap allocation pointer, which already is an *i8.
return packedHeapAlloc
}
}
// EmitPointerUnpack extracts a list of values packed using EmitPointerPack.
func EmitPointerUnpack(builder llvm.Builder, mod llvm.Module, ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
ctx := mod.Context()
targetData := llvm.NewTargetData(mod.DataLayout())
defer targetData.Dispose()
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
uintptrType := ctx.IntType(targetData.PointerSize() * 8)
packedType := ctx.StructType(valueTypes, false)
// Get a correctly-typed pointer to the packed data.
var packedAlloc, packedRawAlloc llvm.Value
size := targetData.TypeAllocSize(packedType)
if size == 0 {
// No data to unpack.
} else if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.PointerTypeKind {
// A single pointer is always stored directly.
return []llvm.Value{builder.CreateBitCast(ptr, valueTypes[0], "unpack.ptr")}
} else if size <= targetData.TypeAllocSize(i8ptrType) {
// Packed data stored directly in pointer.
if len(valueTypes) == 1 && valueTypes[0].TypeKind() == llvm.IntegerTypeKind {
// Keep this cast in SSA form.
return []llvm.Value{builder.CreatePtrToInt(ptr, valueTypes[0], "unpack.int")}
}
// Fallback: load it using an alloca.
packedRawAlloc, _, _ = CreateTemporaryAlloca(builder, mod, llvm.PointerType(i8ptrType, 0), "unpack.raw.alloc")
packedRawValue := builder.CreateBitCast(ptr, llvm.PointerType(i8ptrType, 0), "unpack.raw.value")
builder.CreateStore(packedRawValue, packedRawAlloc)
packedAlloc = builder.CreateBitCast(packedRawAlloc, llvm.PointerType(packedType, 0), "unpack.alloc")
} else {
// Packed data stored on the heap. Bitcast the passed-in pointer to the
// correct pointer type.
packedAlloc = builder.CreateBitCast(ptr, llvm.PointerType(packedType, 0), "unpack.raw.ptr")
}
// Load each value from the packed data.
values := make([]llvm.Value, len(valueTypes))
for i, valueType := range valueTypes {
if targetData.TypeAllocSize(valueType) == 0 {
// This value has length zero, so there's nothing to load.
values[i] = llvm.ConstNull(valueType)
continue
}
indices := []llvm.Value{
llvm.ConstInt(ctx.Int32Type(), 0, false),
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
}
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
values[i] = builder.CreateLoad(gep, "")
}
if !packedRawAlloc.IsNil() {
allocPtr := builder.CreateBitCast(packedRawAlloc, i8ptrType, "")
allocSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(uintptrType), false)
EmitLifetimeEnd(builder, mod, allocPtr, allocSize)
}
return values
}
+32 -110
View File
@@ -41,12 +41,12 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
} }
keySize := b.targetData.TypeAllocSize(llvmKeyType) keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType) valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.uintptrType, keySize, false) llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(b.uintptrType, valueSize, false) llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false) sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false) algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
if expr.Reserve != nil { if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve, getPos(expr)) sizeHint = b.getValue(expr.Reserve)
var err error var err error
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos()) sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
if err != nil { if err != nil {
@@ -65,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 // 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 // will be zeroed by the hashmap get implementation if the key is not
// present in the map. // 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 // 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 // functions. This is necessary because those *Get functions are valid on
@@ -78,38 +78,36 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// Do the lookup. How it is done depends on the key type. // Do the lookup. How it is done depends on the key type.
var commaOkValue llvm.Value var commaOkValue llvm.Value
origKeyType := keyType
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize} params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "") commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
// Store the key in an alloca, in the entry block to avoid dynamic stack // Store the key in an alloca, in the entry block to avoid dynamic stack
// growth. // growth.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca) b.CreateStore(key, mapKeyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), mapKeyAlloca)
// Fetch the value from the hashmap. // 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, "") commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
} else { } else {
// Not trivially comparable using memcmp. Make it an interface instead. // Not trivially comparable using memcmp. Make it an interface instead.
itfKey := key itfKey := key
if _, ok := keyType.(*types.Interface); !ok { if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface now. // 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, "") commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
} }
// Load the resulting value from the hashmap. The value is set to the zero // Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap. // value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(mapValueAlloca, "")
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize) b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
if commaOk { if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
@@ -124,39 +122,36 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
// createMapUpdate updates a map key to a given value, by creating an // createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call. // appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) { 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) b.CreateStore(value, valueAlloca)
origKeyType := keyType
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valueAlloca} params := []llvm.Value{m, key, valuePtr}
b.createRuntimeCall("hashmapStringSet", params, "") b.createRuntimeCall("hashmapStringSet", params, "")
} else if hashmapIsBinaryKey(keyType) { } else if hashmapIsBinaryKey(keyType) {
// key can be compared with runtime.memequal // key can be compared with runtime.memequal
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca) b.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) params := []llvm.Value{m, keyPtr, valuePtr}
params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeCall("hashmapBinarySet", params, "") b.createRuntimeCall("hashmapBinarySet", params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyPtr, keySize)
} else { } else {
// Key is not trivially comparable, so compare it as an interface instead. // Key is not trivially comparable, so compare it as an interface instead.
itfKey := key itfKey := key
if _, ok := keyType.(*types.Interface); !ok { if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first. // 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.createRuntimeCall("hashmapInterfaceSet", params, "")
} }
b.emitLifetimeEnd(valueAlloca, valueSize) b.emitLifetimeEnd(valuePtr, valueSize)
} }
// createMapDelete deletes a key from a map by calling the appropriate runtime // createMapDelete deletes a key from a map by calling the appropriate runtime
// function. It is the implementation of the Go delete() builtin. // function. It is the implementation of the Go delete() builtin.
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error { func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
origKeyType := keyType
keyType = keyType.Underlying() keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
@@ -164,12 +159,11 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
b.createRuntimeCall("hashmapStringDelete", params, "") b.createRuntimeCall("hashmapStringDelete", params, "")
return nil return nil
} else if hashmapIsBinaryKey(keyType) { } 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.CreateStore(key, keyAlloca)
b.zeroUndefBytes(b.getLLVMType(keyType), keyAlloca) params := []llvm.Value{m, keyPtr}
params := []llvm.Value{m, keyAlloca}
b.createRuntimeCall("hashmapBinaryDelete", params, "") b.createRuntimeCall("hashmapBinaryDelete", params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyPtr, keySize)
return nil return nil
} else { } else {
// Key is not trivially comparable, so compare it as an interface // Key is not trivially comparable, so compare it as an interface
@@ -177,7 +171,7 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
itfKey := key itfKey := key
if _, ok := keyType.(*types.Interface); !ok { if _, ok := keyType.(*types.Interface); !ok {
// Not already an interface, so convert it to an interface first. // 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} params := []llvm.Value{m, itfKey}
b.createRuntimeCall("hashmapInterfaceDelete", params, "") b.createRuntimeCall("hashmapInterfaceDelete", params, "")
@@ -185,11 +179,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 // createMapIteratorNext lowers the *ssa.Next instruction for iterating over a
// map. It returns a tuple of {bool, key, value} with the result of the // map. It returns a tuple of {bool, key, value} with the result of the
// iteration. // iteration.
@@ -225,11 +214,11 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// Extract the key and value from the map. // Extract the key and value from the map.
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key") mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
mapValueAlloca, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value") mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyAlloca, mapValueAlloca}, "range.next") ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
mapKey := b.CreateLoad(llvmStoredKeyType, mapKeyAlloca, "") mapKey := b.CreateLoad(mapKeyAlloca, "")
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "") mapValue := b.CreateLoad(mapValueAlloca, "")
if isKeyStoredAsInterface { if isKeyStoredAsInterface {
// The key is stored as an interface but it isn't of interface type. // The key is stored as an interface but it isn't of interface type.
@@ -238,8 +227,8 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// End the lifetimes of the allocas, because we're done with them. // End the lifetimes of the allocas, because we're done with them.
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize) b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
b.emitLifetimeEnd(mapValueAlloca, mapValueSize) b.emitLifetimeEnd(mapValuePtr, mapValueSize)
// Construct the *ssa.Next return value: {ok, mapKey, mapValue} // Construct the *ssa.Next return value: {ok, mapKey, mapValue}
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false)) tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
@@ -251,8 +240,7 @@ func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llv
} }
// Returns true if this key type does not contain strings, interfaces etc., so // Returns true if this key type does not contain strings, interfaces etc., so
// can be compared with runtime.memequal. Note that padding bytes are undef // can be compared with runtime.memequal.
// and can alter two "equal" structs being equal when compared with memequal.
func hashmapIsBinaryKey(keyType types.Type) bool { func hashmapIsBinaryKey(keyType types.Type) bool {
switch keyType := keyType.(type) { switch keyType := keyType.(type) {
case *types.Basic: case *types.Basic:
@@ -275,69 +263,3 @@ func hashmapIsBinaryKey(keyType types.Type) bool {
return false return false
} }
} }
func (b *builder) zeroUndefBytes(llvmType llvm.Type, ptr llvm.Value) error {
// We know that hashmapIsBinaryKey is true, so we only have to handle those types that can show up there.
// To zero all undefined bytes, we iterate over all the fields in the type. For each element, compute the
// offset of that element. If it's Basic type, there are no internal padding bytes. For compound types, we recurse to ensure
// we handle nested types. Next, we determine if there are any padding bytes before the next
// element and zero those as well.
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
switch llvmType.TypeKind() {
case llvm.IntegerTypeKind:
// no padding bytes
return nil
case llvm.PointerTypeKind:
// mo padding bytes
return nil
case llvm.ArrayTypeKind:
llvmArrayType := llvmType
llvmElemType := llvmType.ElementType()
for i := 0; i < llvmArrayType.ArrayLength(); i++ {
idx := llvm.ConstInt(b.uintptrType, uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmArrayType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this element
b.zeroUndefBytes(llvmElemType, elemPtr)
}
case llvm.StructTypeKind:
llvmStructType := llvmType
numFields := llvmStructType.StructElementTypesCount()
llvmElementTypes := llvmStructType.StructElementTypes()
for i := 0; i < numFields; i++ {
idx := llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)
elemPtr := b.CreateInBoundsGEP(llvmStructType, ptr, []llvm.Value{zero, idx}, "")
// zero any padding bytes in this field
llvmElemType := llvmElementTypes[i]
b.zeroUndefBytes(llvmElemType, elemPtr)
// zero any padding bytes before the next field, if any
offset := b.targetData.ElementOffset(llvmStructType, i)
storeSize := b.targetData.TypeStoreSize(llvmElemType)
fieldEndOffset := offset + storeSize
var nextOffset uint64
if i < numFields-1 {
nextOffset = b.targetData.ElementOffset(llvmStructType, i+1)
} else {
// Last field? Next offset is the total size of the allocate 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}, "")
b.createRuntimeCall("memzero", []llvm.Value{paddingStart, n}, "")
}
}
}
return nil
}
+46 -170
View File
@@ -4,14 +4,12 @@ package compiler
// pragmas, determines the link name, etc. // pragmas, determines the link name, etc.
import ( import (
"fmt"
"go/ast" "go/ast"
"go/token" "go/token"
"go/types" "go/types"
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"github.com/tinygo-org/tinygo/loader" "github.com/tinygo-org/tinygo/loader"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
@@ -23,9 +21,9 @@ import (
// The linkName value contains a valid link name, even if //go:linkname is not // The linkName value contains a valid link name, even if //go:linkname is not
// present. // present.
type functionInfo struct { type functionInfo struct {
wasmModule string // go:wasm-module module string // go:wasm-module
wasmName string // wasm-export-name or wasm-import-name in the IR importName string // go:linkname, go:export - The name the developer assigns
linkName string // go:linkname, go:export - the IR function name linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
section string // go:section - object file section name section string // go:section - object file section name
exported bool // go:export, CGo exported bool // go:export, CGo
interrupt bool // go:interrupt interrupt bool // go:interrupt
@@ -53,24 +51,13 @@ const (
inlineNone 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 // getFunction returns the LLVM function for the given *ssa.Function, creating
// it if needed. It can later be filled with compilerContext.createFunction(). // it if needed. It can later be filled with compilerContext.createFunction().
func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) { func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
info := c.getFunctionInfo(fn) info := c.getFunctionInfo(fn)
llvmFn := c.mod.NamedFunction(info.linkName) llvmFn := c.mod.NamedFunction(info.linkName)
if !llvmFn.IsNil() { if !llvmFn.IsNil() {
return llvmFn.GlobalValueType(), llvmFn return llvmFn
} }
var retType llvm.Type var retType llvm.Type
@@ -96,7 +83,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 // 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. // closures and bound methods, but should be optimized away when not used.
if !info.exported { if !info.exported {
paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0}) paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
} }
var paramTypes []llvm.Type var paramTypes []llvm.Type
@@ -125,8 +112,17 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null") dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
for i, info := range paramInfos { for i, info := range paramInfos {
if info.elemSize != 0 { if info.flags&paramIsDeferenceableOrNull == 0 {
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize) continue
}
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
el := info.llvmType.ElementType()
size := c.targetData.TypeAllocSize(el)
if size == 0 {
// dereferenceable_or_null(0) appears to be illegal in LLVM.
continue
}
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull) llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
} }
} }
@@ -139,26 +135,12 @@ 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. // On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
// Mark it as noreturn so LLVM can optimize away code. // Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.alloc": case "runtime.alloc":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it // 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. // returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0)) 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": case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't // Appending a slice will only read the to-be-appended slice, it won't
// be modified. // be modified.
@@ -176,32 +158,25 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// that the only thing we'll do is read the pointer. // that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "__mulsi3", "__divmodsi4", "__udivmodsi4":
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
// 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
// to source languages:
// > This is a rare construct that should only be used in rare
// > circumstances, and should not be exposed to source languages.
llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
}
} }
// External/exported functions may not retain pointer values. // External/exported functions may not retain pointer values.
// https://golang.org/cmd/cgo/#hdr-Passing_pointers // https://golang.org/cmd/cgo/#hdr-Passing_pointers
if info.exported { 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 // We need to add the wasm-import-module and the wasm-import-name
// attributes. // attributes.
if info.wasmModule != "" { module := info.module
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule)) 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") nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0) nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
@@ -218,7 +193,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// should be created right away. // should be created right away.
// The exception is the package initializer, which does appear in the // The exception is the package initializer, which does appear in the
// *ssa.Package members and so shouldn't be created here. // *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 fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" {
irbuilder := c.ctx.NewBuilder() irbuilder := c.ctx.NewBuilder()
b := newBuilder(c, irbuilder, fn) b := newBuilder(c, irbuilder, fn)
b.createFunction() b.createFunction()
@@ -227,29 +202,26 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.SetUnnamedAddr(true) llvmFn.SetUnnamedAddr(true)
} }
return fnType, llvmFn return llvmFn
} }
// getFunctionInfo returns information about a function that is not directly // getFunctionInfo returns information about a function that is not directly
// present in *ssa.Function, such as the link name and whether it should be // present in *ssa.Function, such as the link name and whether it should be
// exported. // exported.
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo { func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
if info, ok := c.functionInfos[f]; ok {
return info
}
info := functionInfo{ info := functionInfo{
// Pick the default linkName. module: "env",
linkName: f.RelString(nil), importName: f.Name(),
linkName: f.RelString(nil), // pick the default linkName
} }
// Check for //go: pragmas, which may change the link name (among others). // Check for //go: pragmas, which may change the link name (among others).
c.parsePragmas(&info, f) info.parsePragmas(f)
c.functionInfos[f] = info
return info return info
} }
// parsePragmas is used by getFunctionInfo to parse function pragmas such as // parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline. // //export or //go:noinline.
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { func (info *functionInfo) parsePragmas(f *ssa.Function) {
if f.Syntax() == nil { if f.Syntax() == nil {
return return
} }
@@ -271,8 +243,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
continue continue
} }
info.importName = parts[1]
info.linkName = parts[1] info.linkName = parts[1]
info.wasmName = info.linkName
info.exported = true info.exported = true
case "//go:interrupt": case "//go:interrupt":
if hasUnsafeImport(f.Pkg.Pkg) { if hasUnsafeImport(f.Pkg.Pkg) {
@@ -280,22 +252,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
} }
case "//go:wasm-module": case "//go:wasm-module":
// Alternative comment for setting the import module. // Alternative comment for setting the import module.
// This is deprecated, use //go:wasmimport instead. if len(parts) == 1 {
if len(parts) != 2 { // Function must not be exported outside of the WebAssembly
continue // module (but only be made available for linking).
info.module = ""
} else if len(parts) == 2 {
info.module = parts[1]
} }
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
}
c.checkWasmImport(f, comment.Text)
info.exported = true
info.wasmModule = parts[1]
info.wasmName = parts[2]
case "//go:inline": case "//go:inline":
info.inline = inlineHint info.inline = inlineHint
case "//go:noinline": case "//go:noinline":
@@ -312,12 +275,8 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.linkName = parts[2] info.linkName = parts[2]
} }
case "//go:section": 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) { if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1] info.section = parts[1]
info.inline = inlineNone
} }
case "//go:nobounds": case "//go:nobounds":
// Skip bounds checking in this function. Useful for some // Skip bounds checking in this function. Useful for some
@@ -342,88 +301,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
} }
} }
// Check whether this function cannot be used in //go:wasmimport. It will add an
// error if this is the case.
//
// The list of allowed types is based on this proposal:
// https://github.com/golang/go/issues/59149
func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) {
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" {
// The runtime is a special case. Allow all kinds of parameters
// (importantly, including pointers).
return
}
if f.Blocks != nil {
// Defined functions cannot be exported.
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
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 !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 !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 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.Int, types.Uint, types.Int8, types.Uint8, types.Int16, types.Uint16, 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 && isValidWasmType(typ.Elem(), siteIndirect)
case *types.Struct:
if site != siteIndirect {
return false
}
for i := 0; i < typ.NumFields(); i++ {
if !isValidWasmType(typ.Field(i).Type(), siteIndirect) {
return false
}
}
return true
case *types.Pointer:
return 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 // getParams returns the function parameters, including the receiver at the
// start. This is an alternative to the Params member of *ssa.Function, which is // 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. // not yet populated when the package has not yet been built.
@@ -475,14 +352,11 @@ func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0)) llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
if strings.Split(c.Triple, "-")[0] == "x86_64" { if strings.Split(c.Triple, "-")[0] == "x86_64" {
// Required by the ABI. // Required by the ABI.
// The uwtable has two possible values: sync (1) or async (2). We use llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
// 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) { func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
c.addStandardDeclaredAttributes(llvmFn) c.addStandardDeclaredAttributes(llvmFn)
c.addStandardDefinedAttributes(llvmFn) c.addStandardDefinedAttributes(llvmFn)
@@ -536,6 +410,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
// Set alignment from the //go:align comment. // Set alignment from the //go:align comment.
var alignInBits uint32
alignment := c.targetData.ABITypeAlignment(llvmType) alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment { if info.align > alignment {
alignment = info.align alignment = info.align
@@ -546,6 +421,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
c.addError(g.Pos(), "global variable alignment must be a positive power of two") c.addError(g.Pos(), "global variable alignment must be a positive power of two")
} else { } else {
// Set the alignment only when it is a power of two. // Set the alignment only when it is a power of two.
alignInBits = uint32(alignment) ^ uint32(alignment-1)
llvmGlobal.SetAlignment(alignment) llvmGlobal.SetAlignment(alignment)
} }
@@ -560,7 +436,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
Type: c.getDIType(typ), Type: c.getDIType(typ),
LocalToUnit: false, LocalToUnit: false,
Expr: c.dibuilder.CreateExpression(nil), Expr: c.dibuilder.CreateExpression(nil),
AlignInBits: uint32(alignment) * 8, AlignInBits: alignInBits,
}) })
llvmGlobal.AddMetadata(0, diglobal) llvmGlobal.AddMetadata(0, diglobal)
} }
+19 -114
View File
@@ -12,10 +12,9 @@ import (
// createRawSyscall creates a system call with the provided system call number // createRawSyscall creates a system call with the provided system call number
// and returns the result as a single integer (the system call result). The // 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 // result is not further interpreted.
// return value everywhere).
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) { 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 { switch {
case b.GOARCH == "amd64" && b.GOOS == "linux": case b.GOARCH == "amd64" && b.GOOS == "linux":
// Sources: // Sources:
@@ -34,17 +33,18 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r10}", "{r10}",
"{r8}", "{r8}",
"{r9}", "{r9}",
"{r11}",
"{r12}",
"{r13}",
}[i] }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg)
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
// rcx and r11 are clobbered by the syscall, so make sure they are not used
constraints += ",~{rcx},~{r11}" constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux": case b.GOARCH == "386" && b.GOOS == "linux":
// Sources: // Sources:
// syscall(2) man page // syscall(2) man page
@@ -64,14 +64,13 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{edi}", "{edi}",
"{ebp}", "{ebp}",
}[i] }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg)
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH == "arm" && b.GOOS == "linux":
// Implement the EABI system call convention for Linux. // Implement the EABI system call convention for Linux.
// Source: syscall(2) man page. // Source: syscall(2) man page.
@@ -90,7 +89,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r5}", "{r5}",
"{r6}", "{r6}",
}[i] }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg)
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
@@ -103,8 +102,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux": case b.GOARCH == "arm64" && b.GOOS == "linux":
// Source: syscall(2) man page. // Source: syscall(2) man page.
args := []llvm.Value{} args := []llvm.Value{}
@@ -121,7 +119,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{x4}", "{x4}",
"{x5}", "{x5}",
}[i] }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg)
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
@@ -136,99 +134,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
constraints += ",~{x16},~{x17}" // scratch registers constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(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: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
@@ -271,13 +177,13 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
var paramTypes []llvm.Type var paramTypes []llvm.Type
var params []llvm.Value var params []llvm.Value
for _, val := range call.Args[2:] { for _, val := range call.Args[2:] {
param := b.getValue(val, getPos(call)) param := b.getValue(val)
params = append(params, param) params = append(params, param)
paramTypes = append(paramTypes, param.Type()) paramTypes = append(paramTypes, param.Type())
} }
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false) llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
fn := b.getValue(call.Args[0], getPos(call)) fn := b.getValue(call.Args[0])
fnPtr := b.CreateIntToPtr(fn, b.dataPtrType, "") fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
// Prepare some functions that will be called later. // Prepare some functions that will be called later.
setLastError := b.mod.NamedFunction("SetLastError") setLastError := b.mod.NamedFunction("SetLastError")
@@ -299,9 +205,9 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// Note that SetLastError/GetLastError could be replaced with direct // Note that SetLastError/GetLastError could be replaced with direct
// access to the thread control block, which is probably smaller and // access to the thread control block, which is probably smaller and
// faster. The Go runtime does this in assembly. // faster. The Go runtime does this in assembly.
b.CreateCall(setLastError.GlobalValueType(), setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "") b.CreateCall(setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
syscallResult := b.CreateCall(llvmType, fnPtr, params, "") syscallResult := b.CreateCall(fnPtr, params, "")
errResult := b.CreateCall(getLastError.GlobalValueType(), getLastError, nil, "err") errResult := b.CreateCall(getLastError, nil, "err")
if b.uintptrType != b.ctx.Int32Type() { if b.uintptrType != b.ctx.Int32Type() {
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr") errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
} }
@@ -311,7 +217,6 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
retval = b.CreateInsertValue(retval, syscallResult, 0, "") retval = b.CreateInsertValue(retval, syscallResult, 0, "")
retval = b.CreateInsertValue(retval, errResult, 2, "") retval = b.CreateInsertValue(retval, errResult, 2, "")
return retval, nil return retval, nil
default: default:
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH) return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
} }
+4 -14
View File
@@ -75,24 +75,14 @@ func complexMul(x, y complex64) complex64 {
// A type 'kv' also exists in function foo. Test that these two types don't // A type 'kv' also exists in function foo. Test that these two types don't
// conflict with each other. // conflict with each other.
type kv struct { type kv struct {
v float32 v float32
x, y, z int
} }
var kvGlobal kv func foo(a *kv) {
func foo() {
// Define a new 'kv' type. // Define a new 'kv' type.
type kv struct { type kv struct {
v byte v byte
x, y, z int
} }
// Use this type. // Use this type.
func(b kv) {}(kv{}) func(b *kv) {}(nil)
} }
type T1 []T1
type T2 [2]*T2
var a T1
var b T2
+33 -39
View File
@@ -3,40 +3,35 @@ source_filename = "basic.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
%main.kv = type { float, i32, i32, i32 } %main.kv = type { float }
%main.kv.0 = type { i8, i32, i32, i32 } %main.kv.0 = type { i8 }
@main.kvGlobal = hidden global %main.kv zeroinitializer, align 4 declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
@main.a = hidden global { ptr, i32, i32 } zeroinitializer, align 4
@main.b = hidden global [2 x ptr] zeroinitializer, align 4
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.addInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = add i32 %x, %y %0 = add i32 %x, %y
ret i32 %0 ret i32 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.equalInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq i32 %x, %y %0 = icmp eq i32 %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.divInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i32 @main.divInt(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq i32 %y, 0 %0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next br i1 %0, label %divbyzero.throw, label %divbyzero.next
@@ -50,14 +45,14 @@ divbyzero.next: ; preds = %entry
ret i32 %5 ret i32 %5
divbyzero.throw: ; preds = %entry divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(ptr undef) #3 call void @runtime.divideByZeroPanic(i8* undef) #2
unreachable unreachable
} }
declare void @runtime.divideByZeroPanic(ptr) #1 declare void @runtime.divideByZeroPanic(i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.divUint(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i32 @main.divUint(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq i32 %y, 0 %0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next br i1 %0, label %divbyzero.throw, label %divbyzero.next
@@ -67,12 +62,12 @@ divbyzero.next: ; preds = %entry
ret i32 %1 ret i32 %1
divbyzero.throw: ; preds = %entry divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(ptr undef) #3 call void @runtime.divideByZeroPanic(i8* undef) #2
unreachable unreachable
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.remInt(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i32 @main.remInt(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq i32 %y, 0 %0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next br i1 %0, label %divbyzero.throw, label %divbyzero.next
@@ -86,12 +81,12 @@ divbyzero.next: ; preds = %entry
ret i32 %5 ret i32 %5
divbyzero.throw: ; preds = %entry divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(ptr undef) #3 call void @runtime.divideByZeroPanic(i8* undef) #2
unreachable unreachable
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.remUint(i32 %x, i32 %y, ptr %context) unnamed_addr #2 { define hidden i32 @main.remUint(i32 %x, i32 %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq i32 %y, 0 %0 = icmp eq i32 %y, 0
br i1 %0, label %divbyzero.throw, label %divbyzero.next br i1 %0, label %divbyzero.throw, label %divbyzero.next
@@ -101,66 +96,66 @@ divbyzero.next: ; preds = %entry
ret i32 %1 ret i32 %1
divbyzero.throw: ; preds = %entry divbyzero.throw: ; preds = %entry
call void @runtime.divideByZeroPanic(ptr undef) #3 call void @runtime.divideByZeroPanic(i8* undef) #2
unreachable unreachable
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatEQ(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatEQ(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp oeq float %x, %y %0 = fcmp oeq float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatNE(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatNE(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp une float %x, %y %0 = fcmp une float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatLower(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatLower(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp olt float %x, %y %0 = fcmp olt float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatLowerEqual(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp ole float %x, %y %0 = fcmp ole float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatGreater(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatGreater(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp ogt float %x, %y %0 = fcmp ogt float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i1 @main.floatGreaterEqual(float %x, float %y, ptr %context) unnamed_addr #2 { define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fcmp oge float %x, %y %0 = fcmp oge float %x, %y
ret i1 %0 ret i1 %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden float @main.complexReal(float %x.r, float %x.i, ptr %context) unnamed_addr #2 { define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context) unnamed_addr #1 {
entry: entry:
ret float %x.r ret float %x.r
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden float @main.complexImag(float %x.r, float %x.i, ptr %context) unnamed_addr #2 { define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context) unnamed_addr #1 {
entry: entry:
ret float %x.i ret float %x.i
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fadd float %x.r, %y.r %0 = fadd float %x.r, %y.r
%1 = fadd float %x.i, %y.i %1 = fadd float %x.i, %y.i
@@ -170,7 +165,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fsub float %x.r, %y.r %0 = fsub float %x.r, %y.r
%1 = fsub float %x.i, %y.i %1 = fsub float %x.i, %y.i
@@ -180,7 +175,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #2 { define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = fmul float %x.r, %y.r %0 = fmul float %x.r, %y.r
%1 = fmul float %x.i, %y.i %1 = fmul float %x.i, %y.i
@@ -194,19 +189,18 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.foo(ptr %context) unnamed_addr #2 { define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context) unnamed_addr #1 {
entry: entry:
call void @"main.foo$1"(%main.kv.0 zeroinitializer, ptr undef) call void @"main.foo$1"(%main.kv.0* null, i8* undef)
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.foo$1"(%main.kv.0 %b, ptr %context) unnamed_addr #2 { define internal void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind }
attributes #3 = { nounwind }
+73 -57
View File
@@ -3,93 +3,110 @@ source_filename = "channel.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
%runtime.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } } %runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
%runtime.chanSelectState = type { ptr, ptr } %runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state", i8* }
%"internal/task.gcData" = type { i8* }
%"internal/task.state" = type { i32, i8*, %"internal/task.stackState", i1 }
%"internal/task.stackState" = type { i32, i32 }
%runtime.chanSelectState = type { %runtime.channel*, i8* }
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #1 {
entry: entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8 %chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) %chan.value.bitcast = bitcast i32* %chan.value to i8*
store i32 3, ptr %chan.value, align 4 call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) store i32 3, i32* %chan.value, align 4
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #3
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
ret void ret void
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: argmemonly nofree nosync nounwind willreturn
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #2
declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: argmemonly nofree nosync nounwind willreturn
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #1 {
entry: entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8 %chan.blockedList = alloca %runtime.channelBlockedList, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) %chan.value.bitcast = bitcast i32* %chan.value to i8*
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4 %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) %0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #3
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void ret void
} }
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1 declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #1 {
entry:
%complit = alloca {}, align 8
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
%0 = bitcast {}* %complit to i8*
call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #3
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #3
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #1 {
entry: entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8 %chan.blockedList = alloca %runtime.channelBlockedList, align 8
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList) %chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4 call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList) %0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #3
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 { define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context) unnamed_addr #1 {
entry:
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
ret void
}
; Function Attrs: nounwind
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 {
entry: entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4 %select.send.value = alloca i32, align 4
store i32 1, ptr %select.send.value, align 4 store i32 1, i32* %select.send.value, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) %select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
store ptr %ch1, ptr %select.states.alloca, align 4 call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%select.states.alloca.repack1 = getelementptr inbounds %runtime.chanSelectState, ptr %select.states.alloca, i32 0, i32 1 %.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
%0 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1 %.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
store ptr %ch2, ptr %0, align 4 %0 = bitcast i8** %.repack1 to i32**
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1 store i32* %select.send.value, i32** %0, align 4
store ptr null, ptr %.repack3, align 4 %.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4 store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) %.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
store i8* null, i8** %.repack4, align 4
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef) #3
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
%1 = extractvalue { i32, i1 } %select.result, 0 %1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0 %2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next br i1 %2, label %select.done, label %select.next
@@ -105,10 +122,9 @@ select.body: ; preds = %select.next
br label %select.done br label %select.done
} }
declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1 declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*) #0
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #2 = { argmemonly nofree nosync nounwind willreturn }
attributes #3 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } attributes #3 = { nounwind }
attributes #4 = { nounwind }
+145 -148
View File
@@ -3,106 +3,103 @@ source_filename = "defer.go"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi" target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface } %runtime._defer = type { i32, %runtime._defer* }
%runtime._interface = type { ptr, ptr } %runtime.deferFrame = type { i8*, i8*, [0 x i8*], %runtime.deferFrame*, i1, %runtime._interface }
%runtime._defer = type { i32, ptr } %runtime._interface = type { i32, i8* }
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
declare void @main.external(ptr) #2 declare void @main.external(i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.deferSimple(ptr %context) unnamed_addr #1 { define hidden void @main.deferSimple(i8* %context) unnamed_addr #1 {
entry: entry:
%defer.alloca = alloca { i32, ptr }, align 4 %defer.alloca = alloca { i32, %runtime._defer* }, align 4
%deferPtr = alloca ptr, align 4 %deferPtr = alloca %runtime._defer*, align 4
store ptr null, ptr %deferPtr, align 4 store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%0 = call ptr @llvm.stacksave.p0() %0 = call i8* @llvm.stacksave()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* %0, i8* undef) #3
store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
%defer.alloca.repack15 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 store i32 0, i32* %defer.alloca.repack, align 4
store ptr null, ptr %defer.alloca.repack15, align 4 %defer.alloca.repack16 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
store ptr %defer.alloca, ptr %deferPtr, align 4 store %runtime._defer* null, %runtime._defer** %defer.alloca.repack16, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %1 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %1, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result = icmp eq i32 %setjmp, 0 %setjmp.result = icmp eq i32 %setjmp, 0
br i1 %setjmp.result, label %1, label %lpad br i1 %setjmp.result, label %2, label %lpad
1: ; preds = %entry 2: ; preds = %entry
call void @main.external(ptr undef) #4 call void @main.external(i8* undef) #3
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %1
br label %rundefers.loophead br label %rundefers.loophead
rundefers.loophead: ; preds = %3, %rundefers.block rundefers.loophead: ; preds = %4, %2
%2 = load ptr, ptr %deferPtr, align 4 %3 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stackIsNil = icmp eq ptr %2, null %stackIsNil = icmp eq %runtime._defer* %3, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 %stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 1
%stack.next = load ptr, ptr %stack.next.gep, align 4 %stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4 store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
%callback = load i32, ptr %2, align 4 %callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %3, i32 0, i32 0
%callback = load i32, i32* %callback.gep, align 4
switch i32 %callback, label %rundefers.default [ switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0 i32 0, label %rundefers.callback0
] ]
rundefers.callback0: ; preds = %rundefers.loop rundefers.callback0: ; preds = %rundefers.loop
%setjmp1 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp1 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result2 = icmp eq i32 %setjmp1, 0 %setjmp.result2 = icmp eq i32 %setjmp1, 0
br i1 %setjmp.result2, label %3, label %lpad br i1 %setjmp.result2, label %4, label %lpad
3: ; preds = %rundefers.callback0 4: ; preds = %rundefers.callback0
call void @"main.deferSimple$1"(ptr undef) call void @"main.deferSimple$1"(i8* undef)
br label %rundefers.loophead br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop rundefers.default: ; preds = %rundefers.loop
unreachable unreachable
rundefers.end: ; preds = %rundefers.loophead rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after call void @runtime.destroyDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* undef) #3
ret void
recover: ; preds = %rundefers.end3 recover: ; preds = %rundefers.end3
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4 call void @runtime.destroyDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* undef) #3
ret void ret void
lpad: ; preds = %rundefers.callback012, %rundefers.callback0, %entry lpad: ; preds = %rundefers.callback012, %rundefers.callback0, %entry
br label %rundefers.loophead6 br label %rundefers.loophead6
rundefers.loophead6: ; preds = %5, %lpad rundefers.loophead6: ; preds = %6, %lpad
%4 = load ptr, ptr %deferPtr, align 4 %5 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stackIsNil7 = icmp eq ptr %4, null %stackIsNil7 = icmp eq %runtime._defer* %5, null
br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5 br i1 %stackIsNil7, label %rundefers.end3, label %rundefers.loop5
rundefers.loop5: ; preds = %rundefers.loophead6 rundefers.loop5: ; preds = %rundefers.loophead6
%stack.next.gep8 = getelementptr inbounds %runtime._defer, ptr %4, i32 0, i32 1 %stack.next.gep8 = getelementptr inbounds %runtime._defer, %runtime._defer* %5, i32 0, i32 1
%stack.next9 = load ptr, ptr %stack.next.gep8, align 4 %stack.next9 = load %runtime._defer*, %runtime._defer** %stack.next.gep8, align 4
store ptr %stack.next9, ptr %deferPtr, align 4 store %runtime._defer* %stack.next9, %runtime._defer** %deferPtr, align 4
%callback11 = load i32, ptr %4, align 4 %callback.gep10 = getelementptr inbounds %runtime._defer, %runtime._defer* %5, i32 0, i32 0
%callback11 = load i32, i32* %callback.gep10, align 4
switch i32 %callback11, label %rundefers.default4 [ switch i32 %callback11, label %rundefers.default4 [
i32 0, label %rundefers.callback012 i32 0, label %rundefers.callback012
] ]
rundefers.callback012: ; preds = %rundefers.loop5 rundefers.callback012: ; preds = %rundefers.loop5
%setjmp13 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp14 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result14 = icmp eq i32 %setjmp13, 0 %setjmp.result15 = icmp eq i32 %setjmp14, 0
br i1 %setjmp.result14, label %5, label %lpad br i1 %setjmp.result15, label %6, label %lpad
5: ; preds = %rundefers.callback012 6: ; preds = %rundefers.callback012
call void @"main.deferSimple$1"(ptr undef) call void @"main.deferSimple$1"(i8* undef)
br label %rundefers.loophead6 br label %rundefers.loophead6
rundefers.default4: ; preds = %rundefers.loop5 rundefers.default4: ; preds = %rundefers.loop5
@@ -112,158 +109,158 @@ rundefers.end3: ; preds = %rundefers.loophead6
br label %recover br label %recover
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn ; Function Attrs: nofree nosync nounwind willreturn
declare ptr @llvm.stacksave.p0() #3 declare i8* @llvm.stacksave() #2
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #2 declare void @runtime.setupDeferFrame(%runtime.deferFrame* dereferenceable_or_null(24), i8*, i8*) #0
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 { define internal void @"main.deferSimple$1"(i8* %context) unnamed_addr #1 {
entry: entry:
call void @runtime.printint32(i32 3, ptr undef) #4 call void @runtime.printint32(i32 3, i8* undef) #3
ret void ret void
} }
declare void @runtime.printint32(i32, ptr) #2 declare void @runtime.destroyDeferFrame(%runtime.deferFrame* dereferenceable_or_null(24), i8*) #0
declare void @runtime.printint32(i32, i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 { define hidden void @main.deferMultiple(i8* %context) unnamed_addr #1 {
entry: entry:
%defer.alloca2 = alloca { i32, ptr }, align 4 %defer.alloca2 = alloca { i32, %runtime._defer* }, align 4
%defer.alloca = alloca { i32, ptr }, align 4 %defer.alloca = alloca { i32, %runtime._defer* }, align 4
%deferPtr = alloca ptr, align 4 %deferPtr = alloca %runtime._defer*, align 4
store ptr null, ptr %deferPtr, align 4 store %runtime._defer* null, %runtime._defer** %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%0 = call ptr @llvm.stacksave.p0() %0 = call i8* @llvm.stacksave()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* %0, i8* undef) #3
store i32 0, ptr %defer.alloca, align 4 %defer.alloca.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 0
%defer.alloca.repack22 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca, i32 0, i32 1 store i32 0, i32* %defer.alloca.repack, align 4
store ptr null, ptr %defer.alloca.repack22, align 4 %defer.alloca.repack26 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca, i32 0, i32 1
store ptr %defer.alloca, ptr %deferPtr, align 4 store %runtime._defer* null, %runtime._defer** %defer.alloca.repack26, align 4
store i32 1, ptr %defer.alloca2, align 4 %1 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
%defer.alloca2.repack23 = getelementptr inbounds { i32, ptr }, ptr %defer.alloca2, i32 0, i32 1 store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %1, align 4
store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4 %defer.alloca2.repack = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 0
store ptr %defer.alloca2, ptr %deferPtr, align 4 store i32 1, i32* %defer.alloca2.repack, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %defer.alloca2.repack27 = getelementptr inbounds { i32, %runtime._defer* }, { i32, %runtime._defer* }* %defer.alloca2, i32 0, i32 1
%2 = bitcast %runtime._defer** %defer.alloca2.repack27 to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca, { i32, %runtime._defer* }** %2, align 4
%3 = bitcast %runtime._defer** %deferPtr to { i32, %runtime._defer* }**
store { i32, %runtime._defer* }* %defer.alloca2, { i32, %runtime._defer* }** %3, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result = icmp eq i32 %setjmp, 0 %setjmp.result = icmp eq i32 %setjmp, 0
br i1 %setjmp.result, label %1, label %lpad br i1 %setjmp.result, label %4, label %lpad
1: ; preds = %entry 4: ; preds = %entry
call void @main.external(ptr undef) #4 call void @main.external(i8* undef) #3
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void
rundefers.block: ; preds = %1
br label %rundefers.loophead br label %rundefers.loophead
rundefers.loophead: ; preds = %4, %3, %rundefers.block rundefers.loophead: ; preds = %7, %6, %4
%2 = load ptr, ptr %deferPtr, align 4 %5 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stackIsNil = icmp eq ptr %2, null %stackIsNil = icmp eq %runtime._defer* %5, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds %runtime._defer, ptr %2, i32 0, i32 1 %stack.next.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %5, i32 0, i32 1
%stack.next = load ptr, ptr %stack.next.gep, align 4 %stack.next = load %runtime._defer*, %runtime._defer** %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4 store %runtime._defer* %stack.next, %runtime._defer** %deferPtr, align 4
%callback = load i32, ptr %2, align 4 %callback.gep = getelementptr inbounds %runtime._defer, %runtime._defer* %5, i32 0, i32 0
%callback = load i32, i32* %callback.gep, align 4
switch i32 %callback, label %rundefers.default [ switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0 i32 0, label %rundefers.callback0
i32 1, label %rundefers.callback1 i32 1, label %rundefers.callback1
] ]
rundefers.callback0: ; preds = %rundefers.loop rundefers.callback0: ; preds = %rundefers.loop
%setjmp3 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp4 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result4 = icmp eq i32 %setjmp3, 0 %setjmp.result5 = icmp eq i32 %setjmp4, 0
br i1 %setjmp.result4, label %3, label %lpad br i1 %setjmp.result5, label %6, label %lpad
3: ; preds = %rundefers.callback0 6: ; preds = %rundefers.callback0
call void @"main.deferMultiple$1"(ptr undef) call void @"main.deferMultiple$1"(i8* undef)
br label %rundefers.loophead br label %rundefers.loophead
rundefers.callback1: ; preds = %rundefers.loop rundefers.callback1: ; preds = %rundefers.loop
%setjmp5 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp7 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result6 = icmp eq i32 %setjmp5, 0 %setjmp.result8 = icmp eq i32 %setjmp7, 0
br i1 %setjmp.result6, label %4, label %lpad br i1 %setjmp.result8, label %7, label %lpad
4: ; preds = %rundefers.callback1 7: ; preds = %rundefers.callback1
call void @"main.deferMultiple$2"(ptr undef) call void @"main.deferMultiple$2"(i8* undef)
br label %rundefers.loophead br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop rundefers.default: ; preds = %rundefers.loop
unreachable unreachable
rundefers.end: ; preds = %rundefers.loophead rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after call void @runtime.destroyDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* undef) #3
recover: ; preds = %rundefers.end7
call void @runtime.destroyDeferFrame(ptr nonnull %deferframe.buf, ptr undef) #4
ret void ret void
lpad: ; preds = %rundefers.callback119, %rundefers.callback016, %rundefers.callback1, %rundefers.callback0, %entry recover: ; preds = %rundefers.end9
br label %rundefers.loophead10 call void @runtime.destroyDeferFrame(%runtime.deferFrame* nonnull %deferframe.buf, i8* undef) #3
ret void
rundefers.loophead10: ; preds = %7, %6, %lpad lpad: ; preds = %rundefers.callback122, %rundefers.callback018, %rundefers.callback1, %rundefers.callback0, %entry
%5 = load ptr, ptr %deferPtr, align 4 br label %rundefers.loophead12
%stackIsNil11 = icmp eq ptr %5, null
br i1 %stackIsNil11, label %rundefers.end7, label %rundefers.loop9
rundefers.loop9: ; preds = %rundefers.loophead10 rundefers.loophead12: ; preds = %10, %9, %lpad
%stack.next.gep12 = getelementptr inbounds %runtime._defer, ptr %5, i32 0, i32 1 %8 = load %runtime._defer*, %runtime._defer** %deferPtr, align 4
%stack.next13 = load ptr, ptr %stack.next.gep12, align 4 %stackIsNil13 = icmp eq %runtime._defer* %8, null
store ptr %stack.next13, ptr %deferPtr, align 4 br i1 %stackIsNil13, label %rundefers.end9, label %rundefers.loop11
%callback15 = load i32, ptr %5, align 4
switch i32 %callback15, label %rundefers.default8 [ rundefers.loop11: ; preds = %rundefers.loophead12
i32 0, label %rundefers.callback016 %stack.next.gep14 = getelementptr inbounds %runtime._defer, %runtime._defer* %8, i32 0, i32 1
i32 1, label %rundefers.callback119 %stack.next15 = load %runtime._defer*, %runtime._defer** %stack.next.gep14, align 4
store %runtime._defer* %stack.next15, %runtime._defer** %deferPtr, align 4
%callback.gep16 = getelementptr inbounds %runtime._defer, %runtime._defer* %8, i32 0, i32 0
%callback17 = load i32, i32* %callback.gep16, align 4
switch i32 %callback17, label %rundefers.default10 [
i32 0, label %rundefers.callback018
i32 1, label %rundefers.callback122
] ]
rundefers.callback016: ; preds = %rundefers.loop9 rundefers.callback018: ; preds = %rundefers.loop11
%setjmp17 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp20 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result18 = icmp eq i32 %setjmp17, 0
br i1 %setjmp.result18, label %6, label %lpad
6: ; preds = %rundefers.callback016
call void @"main.deferMultiple$1"(ptr undef)
br label %rundefers.loophead10
rundefers.callback119: ; preds = %rundefers.loop9
%setjmp20 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
%setjmp.result21 = icmp eq i32 %setjmp20, 0 %setjmp.result21 = icmp eq i32 %setjmp20, 0
br i1 %setjmp.result21, label %7, label %lpad br i1 %setjmp.result21, label %9, label %lpad
7: ; preds = %rundefers.callback119 9: ; preds = %rundefers.callback018
call void @"main.deferMultiple$2"(ptr undef) call void @"main.deferMultiple$1"(i8* undef)
br label %rundefers.loophead10 br label %rundefers.loophead12
rundefers.default8: ; preds = %rundefers.loop9 rundefers.callback122: ; preds = %rundefers.loop11
%setjmp24 = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(%runtime.deferFrame* nonnull %deferframe.buf) #4
%setjmp.result25 = icmp eq i32 %setjmp24, 0
br i1 %setjmp.result25, label %10, label %lpad
10: ; preds = %rundefers.callback122
call void @"main.deferMultiple$2"(i8* undef)
br label %rundefers.loophead12
rundefers.default10: ; preds = %rundefers.loop11
unreachable unreachable
rundefers.end7: ; preds = %rundefers.loophead10 rundefers.end9: ; preds = %rundefers.loophead12
br label %recover br label %recover
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 { define internal void @"main.deferMultiple$1"(i8* %context) unnamed_addr #1 {
entry: entry:
call void @runtime.printint32(i32 3, ptr undef) #4 call void @runtime.printint32(i32 3, i8* undef) #3
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 { define internal void @"main.deferMultiple$2"(i8* %context) unnamed_addr #1 {
entry: entry:
call void @runtime.printint32(i32 5, ptr undef) #4 call void @runtime.printint32(i32 5, i8* undef) #3
ret void ret void
} }
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #0 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" }
attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #1 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" }
attributes #2 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+strict-align,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" } attributes #2 = { nofree nosync nounwind willreturn }
attributes #3 = { nocallback nofree nosync nounwind willreturn } attributes #3 = { nounwind }
attributes #4 = { nounwind } attributes #4 = { nounwind returns_twice }
attributes #5 = { nounwind returns_twice }
-77
View File
@@ -1,77 +0,0 @@
package main
import "unsafe"
//go:wasmimport modulename empty
func empty()
// ERROR: can only use //go:wasmimport on declarations
//
//go:wasmimport modulename implementation
func implementation() {
}
type Uint uint32
type S struct {
a [4]uint32
b uintptr
c int
d float32
e float64
}
//go:wasmimport modulename validparam
func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S)
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int}
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{}
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func()
//
//go:wasmimport modulename invalidparam
func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func())
//go:wasmimport modulename validreturn_int32
func validreturn_int32() int32
//go:wasmimport modulename validreturn_int
func validreturn_int() int
//go:wasmimport modulename validreturn_ptr_int32
func validreturn_ptr_int32() *int32
//go:wasmimport modulename validreturn_ptr_string
func validreturn_ptr_string() *string
//go:wasmimport modulename validreturn_ptr_struct
func validreturn_ptr_struct() *S
//go:wasmimport modulename validreturn_unsafe_pointer
func validreturn_unsafe_pointer() unsafe.Pointer
// ERROR: //go:wasmimport modulename manyreturns: too many return values
//
//go:wasmimport modulename manyreturns
func manyreturns() (int32, int32)
// ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func()
//
//go:wasmimport modulename invalidreturn_func
func invalidreturn_func() func()
// ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte
//
//go:wasmimport modulename invalidreturn_slice_byte
func invalidreturn_slice_byte() []byte
// ERROR: //go:wasmimport modulename invalidreturn_chan_int: unsupported result type chan int
//
//go:wasmimport modulename invalidreturn_chan_int
func invalidreturn_chan_int() chan int
// ERROR: //go:wasmimport modulename invalidreturn_string: unsupported result type string
//
//go:wasmimport modulename invalidreturn_string
func invalidreturn_string() string
+13 -15
View File
@@ -3,19 +3,18 @@ source_filename = "float.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.f32tou32(float %v, ptr %context) unnamed_addr #2 { define hidden i32 @main.f32tou32(float %v, i8* %context) unnamed_addr #1 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -27,25 +26,25 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden float @main.maxu32f(ptr %context) unnamed_addr #2 { define hidden float @main.maxu32f(i8* %context) unnamed_addr #1 {
entry: entry:
ret float 0x41F0000000000000 ret float 0x41F0000000000000
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.maxu32tof32(ptr %context) unnamed_addr #2 { define hidden i32 @main.maxu32tof32(i8* %context) unnamed_addr #1 {
entry: entry:
ret i32 -1 ret i32 -1
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden { i32, i32, i32, i32 } @main.inftoi32(ptr %context) unnamed_addr #2 { define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context) unnamed_addr #1 {
entry: entry:
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 } ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i32 @main.u32tof32tou32(i32 %v, ptr %context) unnamed_addr #2 { define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = uitofp i32 %v to float %0 = uitofp i32 %v to float
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
@@ -55,7 +54,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden float @main.f32tou32tof32(float %v, ptr %context) unnamed_addr #2 { define hidden float @main.f32tou32tof32(float %v, i8* %context) unnamed_addr #1 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000 %withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
@@ -68,7 +67,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i8 @main.f32tou8(float %v, ptr %context) unnamed_addr #2 { define hidden i8 @main.f32tou8(float %v, i8* %context) unnamed_addr #1 {
entry: entry:
%positive = fcmp oge float %v, 0.000000e+00 %positive = fcmp oge float %v, 0.000000e+00
%withinmax = fcmp ole float %v, 2.550000e+02 %withinmax = fcmp ole float %v, 2.550000e+02
@@ -80,7 +79,7 @@ entry:
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden i8 @main.f32toi8(float %v, ptr %context) unnamed_addr #2 { define hidden i8 @main.f32toi8(float %v, i8* %context) unnamed_addr #1 {
entry: entry:
%abovemin = fcmp oge float %v, -1.280000e+02 %abovemin = fcmp oge float %v, -1.280000e+02
%belowmax = fcmp ole float %v, 1.270000e+02 %belowmax = fcmp ole float %v, 1.270000e+02
@@ -93,6 +92,5 @@ entry:
ret i8 %0 ret i8 %0
} }
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
+15 -16
View File
@@ -3,48 +3,47 @@ source_filename = "func.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.foo(ptr %callback.context, ptr %callback.funcptr, ptr %context) unnamed_addr #2 { define hidden void @main.foo(i8* %callback.context, void ()* %callback.funcptr, i8* %context) unnamed_addr #1 {
entry: entry:
%0 = icmp eq ptr %callback.funcptr, null %0 = icmp eq void ()* %callback.funcptr, null
br i1 %0, label %fpcall.throw, label %fpcall.next br i1 %0, label %fpcall.throw, label %fpcall.next
fpcall.next: ; preds = %entry fpcall.next: ; preds = %entry
call void %callback.funcptr(i32 3, ptr %callback.context) #3 %1 = bitcast void ()* %callback.funcptr to void (i32, i8*)*
call void %1(i32 3, i8* %callback.context) #2
ret void ret void
fpcall.throw: ; preds = %entry fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(ptr undef) #3 call void @runtime.nilPanic(i8* undef) #2
unreachable unreachable
} }
declare void @runtime.nilPanic(ptr) #1 declare void @runtime.nilPanic(i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.bar(ptr %context) unnamed_addr #2 { define hidden void @main.bar(i8* %context) unnamed_addr #1 {
entry: entry:
call void @main.foo(ptr undef, ptr nonnull @main.someFunc, ptr undef) call void @main.foo(i8* undef, void ()* bitcast (void (i32, i8*)* @main.someFunc to void ()*), i8* undef)
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.someFunc(i32 %arg0, ptr %context) unnamed_addr #2 { define hidden void @main.someFunc(i32 %arg0, i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind }
attributes #3 = { nounwind }
+95 -99
View File
@@ -3,139 +3,135 @@ source_filename = "gc.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi" target triple = "wasm32-unknown-wasi"
%runtime._interface = type { ptr, ptr } %runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
%runtime.interfaceMethodInfo = type { i8*, i32 }
%runtime._interface = type { i32, i8* }
@main.scalar1 = hidden global ptr null, align 4 @main.scalar1 = hidden global i8* null, align 4
@main.scalar2 = hidden global ptr null, align 4 @main.scalar2 = hidden global i32* null, align 4
@main.scalar3 = hidden global ptr null, align 4 @main.scalar3 = hidden global i64* null, align 4
@main.scalar4 = hidden global ptr null, align 4 @main.scalar4 = hidden global float* null, align 4
@main.array1 = hidden global ptr null, align 4 @main.array1 = hidden global [3 x i8]* null, align 4
@main.array2 = hidden global ptr null, align 4 @main.array2 = hidden global [71 x i8]* null, align 4
@main.array3 = hidden global ptr null, align 4 @main.array3 = hidden global [3 x i8*]* null, align 4
@main.struct1 = hidden global ptr null, align 4 @main.struct1 = hidden global {}* null, align 4
@main.struct2 = hidden global ptr null, align 4 @main.struct2 = hidden global { i32, i32 }* null, align 4
@main.struct3 = hidden global ptr null, align 4 @main.struct3 = hidden global { i8*, [60 x i32], i8* }* null, align 4
@main.struct4 = hidden global ptr null, align 4 @main.struct4 = hidden global { i8*, [61 x i32] }* null, align 4
@main.slice1 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice1 = hidden global { i8*, i32, i32 } zeroinitializer, align 8
@main.slice2 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice2 = hidden global { i32**, i32, i32 } zeroinitializer, align 8
@main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 4 @main.slice3 = hidden global { { i8*, i32, i32 }*, i32, i32 } zeroinitializer, align 8
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c" \00\00\00\00\00\00\01" }
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } @"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\00\00\00\00\00\00\00\01" }
@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 80, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 @"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:complex128", i32 0 }
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:complex128" }, align 4 @"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:complex128", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
; Function Attrs: allockind("alloc,zeroed") allocsize(0) declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) #0
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1 declare void @runtime.trackPointer(i8* nocapture readonly, i8*) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 { define hidden void @main.init(i8* %context) unnamed_addr #1 {
entry: entry:
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.newScalar(ptr %context) unnamed_addr #2 { define hidden void @main.newScalar(i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %new = call i8* @runtime.alloc(i32 1, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new = call align 1 dereferenceable(1) ptr @runtime.alloc(i32 1, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new, i8** @main.scalar1, align 4
store ptr %new, ptr @main.scalar1, align 4 %new1 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new1 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new1, i8** bitcast (i32** @main.scalar2 to i8**), align 4
store ptr %new1, ptr @main.scalar2, align 4 %new2 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new2 = call align 8 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new2, i8** bitcast (i64** @main.scalar3 to i8**), align 4
store ptr %new2, ptr @main.scalar3, align 4 %new3 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new3 = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new3, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new3, i8** bitcast (float** @main.scalar4 to i8**), align 4
store ptr %new3, ptr @main.scalar4, align 4
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.newArray(ptr %context) unnamed_addr #2 { define hidden void @main.newArray(i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %new = call i8* @runtime.alloc(i32 3, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new = call align 1 dereferenceable(3) ptr @runtime.alloc(i32 3, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new, i8** bitcast ([3 x i8]** @main.array1 to i8**), align 4
store ptr %new, ptr @main.array1, align 4 %new1 = call i8* @runtime.alloc(i32 71, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new1 = call align 1 dereferenceable(71) ptr @runtime.alloc(i32 71, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new1, i8** bitcast ([71 x i8]** @main.array2 to i8**), align 4
store ptr %new1, ptr @main.array2, align 4 %new2 = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 67 to i8*), i8* undef) #2
%new2 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new2, i8** bitcast ([3 x i8*]** @main.array3 to i8**), align 4
store ptr %new2, ptr @main.array3, align 4
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.newStruct(ptr %context) unnamed_addr #2 { define hidden void @main.newStruct(i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %new = call i8* @runtime.alloc(i32 0, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new, i8** bitcast ({}** @main.struct1 to i8**), align 4
store ptr %new, ptr @main.struct1, align 4 %new1 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new1, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new1, i8** bitcast ({ i32, i32 }** @main.struct2 to i8**), align 4
store ptr %new1, ptr @main.struct2, align 4 %new2 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*), i8* undef) #2
%new2 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-2000000000000001", ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new2, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new2, i8** bitcast ({ i8*, [60 x i32], i8* }** @main.struct3 to i8**), align 4
store ptr %new2, ptr @main.struct3, align 4 %new3 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-0001" to i8*), i8* undef) #2
%new3 = call align 4 dereferenceable(248) ptr @runtime.alloc(i32 248, ptr nonnull @"runtime/gc.layout:62-0001", ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new3, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %new3, ptr nonnull %stackalloc, ptr undef) #3 store i8* %new3, i8** bitcast ({ i8*, [61 x i32] }** @main.struct4 to i8**), align 4
store ptr %new3, ptr @main.struct4, align 4
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #2 { define hidden { i8*, void ()* }* @main.newFuncValue(i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %new = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 197 to i8*), i8* undef) #2
%new = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 197 to ptr), ptr undef) #3 %0 = bitcast i8* %new to { i8*, void ()* }*
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #2
ret ptr %new ret { i8*, void ()* }* %0
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.makeSlice(ptr %context) unnamed_addr #2 { define hidden void @main.makeSlice(i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %makeslice = call i8* @runtime.alloc(i32 5, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #2
%makeslice = call align 1 dereferenceable(5) ptr @runtime.alloc(i32 5, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %makeslice, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %makeslice, ptr nonnull %stackalloc, ptr undef) #3 store i8* %makeslice, i8** getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 0), align 8
store ptr %makeslice, ptr @main.slice1, align 4 store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 1), align 4
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 1), align 4 store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 2), align 8
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice1, i32 0, i32 2), align 4 %makeslice1 = call i8* @runtime.alloc(i32 20, i8* nonnull inttoptr (i32 67 to i8*), i8* undef) #2
%makeslice1 = call align 4 dereferenceable(20) ptr @runtime.alloc(i32 20, ptr nonnull inttoptr (i32 67 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %makeslice1, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %makeslice1, ptr nonnull %stackalloc, ptr undef) #3 store i8* %makeslice1, i8** bitcast ({ i32**, i32, i32 }* @main.slice2 to i8**), align 8
store ptr %makeslice1, ptr @main.slice2, align 4 store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 1), align 4
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 1), align 4 store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 2), align 8
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice2, i32 0, i32 2), align 4 %makeslice3 = call i8* @runtime.alloc(i32 60, i8* nonnull inttoptr (i32 71 to i8*), i8* undef) #2
%makeslice3 = call align 4 dereferenceable(60) ptr @runtime.alloc(i32 60, ptr nonnull inttoptr (i32 71 to ptr), ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %makeslice3, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %makeslice3, ptr nonnull %stackalloc, ptr undef) #3 store i8* %makeslice3, i8** bitcast ({ { i8*, i32, i32 }*, i32, i32 }* @main.slice3 to i8**), align 8
store ptr %makeslice3, ptr @main.slice3, align 4 store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 1), align 4
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 1), align 4 store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 2), align 8
store i32 5, ptr getelementptr inbounds ({ ptr, i32, i32 }, ptr @main.slice3, i32 0, i32 2), align 4
ret void ret void
} }
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #2 { define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, i8* %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef) #2
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #2
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 %.repack = bitcast i8* %0 to double*
store double %v.r, ptr %0, align 8 store double %v.r, double* %.repack, align 8
%.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1 %.repack1 = getelementptr inbounds i8, i8* %0, i32 8
store double %v.i, ptr %.repack1, align 8 %1 = bitcast i8* %.repack1 to double*
%1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1 store double %v.i, double* %1, align 8
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #3 %2 = insertvalue %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:complex128" to i32), i8* undef }, i8* %0, 1
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #2
ret %runtime._interface %1 ret %runtime._interface %2
} }
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind }
attributes #3 = { nounwind }
-15
View File
@@ -1,15 +0,0 @@
package main
import "unsafe"
func unsafeSliceData(s []int) *int {
return unsafe.SliceData(s)
}
func unsafeString(ptr *byte, len int16) string {
return unsafe.String(ptr, len)
}
func unsafeStringData(s string) *byte {
return unsafe.StringData(s)
}
-63
View File
@@ -1,63 +0,0 @@
; ModuleID = 'go1.20.go'
source_filename = "go1.20.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime._string = type { ptr, i32 }
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #1
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #2 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3
ret ptr %s.data
}
; Function Attrs: nounwind
define hidden %runtime._string @main.unsafeString(ptr dereferenceable_or_null(1) %ptr, i16 %len, ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
%0 = icmp slt i16 %len, 0
%1 = icmp eq ptr %ptr, null
%2 = icmp ne i16 %len, 0
%3 = and i1 %1, %2
%4 = or i1 %3, %0
br i1 %4, label %unsafe.String.throw, label %unsafe.String.next
unsafe.String.next: ; preds = %entry
%5 = zext i16 %len to i32
%6 = insertvalue %runtime._string undef, ptr %ptr, 0
%7 = insertvalue %runtime._string %6, i32 %5, 1
call void @runtime.trackPointer(ptr %ptr, ptr nonnull %stackalloc, ptr undef) #3
ret %runtime._string %7
unsafe.String.throw: ; preds = %entry
call void @runtime.unsafeSlicePanic(ptr undef) #3
unreachable
}
declare void @runtime.unsafeSlicePanic(ptr) #1
; Function Attrs: nounwind
define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3
ret ptr %s.data
}
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
attributes #3 = { nounwind }

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