Compare commits

..

1 Commits

Author SHA1 Message Date
Ayke van Laethem df8add7b60 machine: add tool to check consistency with documentation
This tool checks whether the machine package matches the documentation
in https://tinygo.org/docs/reference/machine/.
2022-09-26 16:50:14 +02:00
1659 changed files with 23729 additions and 114478 deletions
+128
View File
@@ -0,0 +1,128 @@
version: 2.1
commands:
submodules:
steps:
- run:
name: "Pull submodules"
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:
steps:
- restore_cache:
keys:
- llvm-source-14-v3
- run:
name: "Fetch LLVM source"
command: make llvm-source
- save_cache:
key: llvm-source-14-v3
paths:
- llvm-project/clang/lib/Headers
- llvm-project/clang/include
- llvm-project/compiler-rt
- llvm-project/lld/include
- llvm-project/llvm/include
hack-ninja-jobs:
steps:
- run:
name: "Hack Ninja to use less jobs"
command: |
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
chmod +x /go/bin/ninja
build-binaryen-linux:
steps:
- restore_cache:
keys:
- binaryen-linux-v2
- run:
name: "Build Binaryen"
command: |
make binaryen
- save_cache:
key: binaryen-linux-v2
paths:
- build/wasm-opt
test-linux:
parameters:
llvm:
type: string
fmt-check:
type: boolean
default: true
steps:
- checkout
- submodules
- run:
name: "Install apt dependencies"
command: |
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' > /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
apt-get update
apt-get install --no-install-recommends -y \
llvm-<<parameters.llvm>>-dev \
clang-<<parameters.llvm>> \
libclang-<<parameters.llvm>>-dev \
lld-<<parameters.llvm>> \
gcc-avr \
avr-libc \
cmake \
ninja-build
- hack-ninja-jobs
- build-binaryen-linux
- restore_cache:
keys:
- go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
- go-cache-v3-{{ checksum "go.mod" }}
- llvm-source-linux
- run: go install -tags=llvm<<parameters.llvm>> .
- restore_cache:
keys:
- wasi-libc-sysroot-systemclang-v6
- run: make wasi-libc
- save_cache:
key: wasi-libc-sysroot-systemclang-v6
paths:
- lib/wasi-libc/sysroot
- when:
condition: <<parameters.fmt-check>>
steps:
- run:
# Do this before gen-device so that it doesn't check the
# formatting of generated files.
name: Check Go code formatting
command: make fmt-check
- run: make gen-device -j4
- run: make smoketest XTENSA=0
- save_cache:
key: go-cache-v3-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
paths:
- ~/.cache/go-build
- /go/pkg/mod
jobs:
test-llvm14-go118:
docker:
- image: golang:1.18-buster
steps:
- test-linux:
llvm: "14"
resource_class: large
workflows:
test-all:
jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm14-go118
-1
View File
@@ -1,4 +1,3 @@
build/ build/
llvm-*/ llvm-*/
.github
-3
View File
@@ -1,3 +0,0 @@
# These are supported funding model platforms
open_collective: tinygo
+49 -72
View File
@@ -14,39 +14,33 @@ concurrency:
jobs: jobs:
build-macos: build-macos:
name: build-macos name: build-macos
strategy: runs-on: macos-11
matrix:
# macos-14: arm64 (oldest supported version as of 18-11-2025)
# macos-15-intel: amd64 (last intel version to be supported by github runners)
# See https://github.com/actions/runner-images/issues/13046
os: [macos-14, macos-15-intel]
include:
- os: macos-14
goarch: arm64
- os: macos-15-intel
goarch: amd64
runs-on: ${{ matrix.os }}
steps: steps:
- name: Install Dependencies - name: Install Dependencies
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@v6 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
cache: true cache: true
- name: Restore LLVM source cache - name: Cache LLVM source
uses: actions/cache/restore@v5 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-22-${{ matrix.os }}-v1 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
@@ -56,90 +50,73 @@ 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@v5 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@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-22-${{ matrix.os }}-v1 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'
shell: bash
run: | run: |
# fetch LLVM source # fetch LLVM source
rm -rf llvm-project rm -rf llvm-project
make llvm-source make llvm-source
# install dependencies # install dependencies
HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
# 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 - name: Cache wasi-libc sysroot
uses: actions/cache/save@v5 uses: actions/cache@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true' id: cache-wasi-libc
with: with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} key: wasi-libc-sysroot-v4
path: llvm-build path: lib/wasi-libc/sysroot
- name: make gen-device - name: Build wasi-libc
run: make -j3 gen-device if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Test TinyGo - name: Test TinyGo
run: make test GOTESTFLAGS="-only-current-os" shell: bash
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
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz shell: bash
run: cp -p build/release.tar.gz build/tinygo.darwin-amd64.tar.gz
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v7 # Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a double-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v2
with: with:
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }} name: release-double-zipped
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz path: build/tinygo.darwin-amd64.tar.gz
archive: false
- name: Smoke tests - name: Smoke tests
run: make smoketest TINYGO=$(PWD)/build/tinygo shell: bash
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, 19, 20]
steps: steps:
- name: Set up Homebrew
uses: Homebrew/actions/setup-homebrew@main
- name: Fix Python symlinks
run: |
# Github runners have broken symlinks, so relink
# see: https://github.com/actions/setup-python/issues/577
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
- name: Install LLVM - name: Install LLVM
shell: bash
run: | run: |
brew install llvm@${{ matrix.version }} HOMEBREW_NO_AUTO_UPDATE=1 brew install llvm@14
brew link llvm@${{ matrix.version }}
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
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 == 20
run: go install run: go install
- name: Check binary - name: Check binary
if: matrix.version == 20
run: tinygo version run: tinygo version
-72
View File
@@ -1,72 +0,0 @@
# This CI job checks whether at least the smoke tests pass for the oldest
# Go/LLVM version we claim to support.
name: Version compatibility test
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-compat:
runs-on: ubuntu-22.04 # this must be a specific version for the apt install below
env:
# Oldest versions currently supported by TinyGo
LLVM: "15"
Go: "1.25" # when updating this, also update minorMin in builder/config.go
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.Go }}
cache: true
- name: Install LLVM
run: |
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ env.LLVM }} main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-${{ env.LLVM }}-dev \
clang-${{ env.LLVM }} \
libclang-${{ env.LLVM }}-dev \
lld-${{ env.LLVM }} \
binaryen
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-compat
path: llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: llvm-project/compiler-rt
- name: Go cache
uses: actions/cache@v5
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-build-${{ env.Go }}-llvm${{ env.LLVM }}-${{ hashFiles('go.sum') }}
- name: Build TinyGo
run: go install -tags=llvm${{ env.LLVM }}
- run: tinygo version
- run: make gen-device -j4
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
- run: make smoketest XTENSA=0
+39 -18
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@v6 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@v4 uses: docker/setup-buildx-action@v1
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v6 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@v4 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@v4 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@v7 uses: docker/build-push-action@v2
with: with:
context: . context: .
push: true push: true
@@ -66,3 +55,35 @@ jobs:
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
- name: Trigger Drivers repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/drivers/actions/workflows/build.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger Bluetooth repo build on Github Actions
run: |
curl -X POST \
-H "Authorization: Bearer ${{secrets.GHA_ACCESS_TOKEN}}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/tinygo-org/bluetooth/actions/workflows/linux.yml/dispatches \
-d '{"ref": "dev"}'
- name: Trigger TinyFS repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfs/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyFont repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinyfont/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
- name: Trigger TinyDraw repo build on CircleCI
run: |
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/tinydraw/pipeline' \
--header 'Content-Type: application/json' \
-d '{"branch": "dev"}' \
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
+214 -189
View File
@@ -12,61 +12,38 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
go-mod-tidy:
# Check that go.sum is up to date.
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
cache: true
- name: Run go mod tidy
run: go mod tidy
- name: Check go.mod and go.sum are up to date
run: git diff --exit-code
build-linux: build-linux:
# Build Linux binaries, ready for release. # Build Linux binaries, ready for release.
# This runs inside an Alpine Linux container so we can more easily create a # This runs inside an Alpine Linux container so we can more easily create a
# statically linked binary. # statically linked binary.
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: golang:1.27rc2-alpine image: golang:1.19-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps: steps:
- name: Install apk dependencies - name: Install apk dependencies
# tar: needed for actions/cache@v5 # 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 mold 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@v6 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Extract TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Cache Go - name: Cache Go
uses: actions/cache@v5 uses: actions/cache@v3
with: with:
key: go-cache-linux-alpine-v2-${{ 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@v5 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-22-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
@@ -76,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@v5 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@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-22-linux-alpine-v1 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'
@@ -105,81 +71,80 @@ 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@v5
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@v5 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-alpine-v3 key: binaryen-linux-alpine-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: |
apk add cmake samurai python3 apk add cmake samurai python3
make binaryen STATIC=1 make binaryen STATIC=1
- name: Cache wasi-libc
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-alpine-v1
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- name: Install fpm - name: Install fpm
run: | run: |
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${{ steps.version.outputs.version }}.linux-amd64.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb cp -p build/release.deb /tmp/tinygo_amd64.deb
- name: "Publish release artifact: tarball" - name: Publish release artifact
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v2
with: with:
name: tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz name: linux-amd64-double-zipped
path: /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz path: |
archive: false /tmp/tinygo.linux-amd64.tar.gz
- name: "Publish release artifact: Debian package" /tmp/tinygo_amd64.deb
uses: actions/upload-artifact@v7
with:
name: tinygo_${{ steps.version.outputs.version }}_amd64.deb
path: /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
archive: false
test-linux-build: test-linux-build:
# Test the binaries built in the build-linux job by running the smoke tests. # Test the binaries built in the build-linux job by running the smoke tests.
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
with:
submodules: true
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
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: "29.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@v8 uses: actions/download-artifact@v2
with: with:
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz name: linux-amd64-double-zipped
- name: Extract release tarball - name: Extract release tarball
run: | run: |
mkdir -p ~/lib mkdir -p ~/lib
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo 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: |
- run: make tinygo-test-wasm 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
@@ -187,7 +152,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Install apt dependencies - name: Install apt dependencies
@@ -199,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@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v2
with: with:
node-version: '22' 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: "29.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@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-22-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
@@ -230,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@v5 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@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-22-linux-asserts-v1 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'
@@ -257,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@v5
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@v5 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-asserts-v1 key: binaryen-linux-asserts-v1
@@ -272,6 +220,15 @@ jobs:
- name: Build Binaryen - name: Build Binaryen
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
uses: actions/cache@v3
id: cache-wasi-libc
with:
key: wasi-libc-sysroot-linux-asserts-v5
path: lib/wasi-libc/sysroot
- name: Build wasi-libc
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
run: make wasi-libc
- run: make gen-device -j4 - run: make gen-device -j4
- name: Test TinyGo - name: Test TinyGo
run: make ASSERT=1 test run: make ASSERT=1 test
@@ -279,14 +236,20 @@ jobs:
run: | run: |
make ASSERT=1 make ASSERT=1
echo "$(pwd)/build" >> $GITHUB_PATH echo "$(pwd)/build" >> $GITHUB_PATH
- name: Test machine package
run: make check-machine
- 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-test-baremetal - run: make tinygo-baremetal
- name: Check Go code formatting build-linux-arm:
run: make fmt-check lint
build-linux-cross:
# 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
@@ -295,41 +258,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-22.04 # note: use the oldest image available! (see above)
needs: build-linux needs: build-linux
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
- name: Get TinyGo version
id: version
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- 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@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
cache: true cache: true
- name: Restore LLVM source cache - name: Cache LLVM source
uses: actions/cache/restore@v5 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-22-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
@@ -339,22 +289,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@v5 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@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-22-linux-${{ matrix.goarch }}-v1 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'
@@ -365,27 +304,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@v5
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@v5 uses: actions/cache@v3
id: cache-binaryen id: cache-binaryen
with: with:
key: binaryen-linux-${{ matrix.goarch }}-v5 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
@@ -393,33 +326,125 @@ 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@v8 uses: actions/download-artifact@v2
with: with:
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz name: linux-amd64-double-zipped
- name: Extract amd64 release - name: Extract amd64 release
run: | run: |
mkdir -p build/release mkdir -p build/release
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
- name: Modify release - name: Modify release
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${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz cp -p build/release.tar.gz /tmp/tinygo.linux-arm.tar.gz
cp -p build/release.deb /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb cp -p build/release.deb /tmp/tinygo_armhf.deb
- name: "Publish release artifact: tarball" - name: Publish release artifact
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v2
with: with:
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }} name: linux-arm-double-zipped
path: /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz path: |
archive: false /tmp/tinygo.linux-arm.tar.gz
- name: "Publish release artifact: Debian package" /tmp/tinygo_armhf.deb
uses: actions/upload-artifact@v7 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: with:
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }} go-version: '1.19'
path: /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb cache: true
archive: false - 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@v6
with:
submodules: recursive
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Docker meta
id: meta
uses: docker/metadata-action@v6
with:
images: |
tinygo/llvm-20
ghcr.io/${{ github.repository_owner }}/llvm-20
tags: |
type=sha,format=long
type=raw,value=latest
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Log in to Github Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v7
with:
target: tinygo-llvm-build
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
-48
View File
@@ -1,48 +0,0 @@
name: Nix
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
nix-test:
runs-on: ubuntu-latest
steps:
- name: Uninstall system LLVM
# Hack to work around issue where we still include system headers for
# some reason.
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
run: sudo apt-get remove llvm-18
- name: Checkout
uses: actions/checkout@v6
- name: Pull musl, bdwgc
run: |
git submodule update --init lib/musl lib/bdwgc
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-nix-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: |
llvm-project/compiler-rt
- uses: cachix/install-nix-action@v31
- name: Test
run: |
nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo"
@@ -1,12 +0,0 @@
# Command that's part of sizediff.yml. This is put in a separate file so that it
# still works after checking out the dev branch (that is, when going from LLVM
# 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed).
echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-20-dev \
clang-20 \
libclang-20-dev \
lld-20
-97
View File
@@ -1,97 +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: Install Go
uses: actions/setup-go@v6
with:
go-version: '~1.25'
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0 # fetch all history (no sparse checkout)
submodules: true
- name: Install apt dependencies
run: ./.github/workflows/sizediff-install-pkgs.sh
- name: Restore LLVM source cache
uses: actions/cache@v5
id: cache-llvm-source
with:
key: llvm-source-22-sizediff-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Cache Go
uses: actions/cache@v5
with:
key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }}
path: |
~/.cache/go-build
~/go/pkg/mod
- run: make gen-device -j4
- name: Download drivers repo
run: git clone https://github.com/tinygo-org/drivers.git
- name: Save HEAD
run: git branch github-actions-saved-HEAD HEAD
# Compute sizes for the PR branch
- name: Build tinygo binary for the PR branch
run: go install
- name: Determine binary sizes on the PR branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt)
# Compute sizes for the dev branch
- name: Checkout dev branch
run: |
git reset --hard origin/dev
git checkout --no-recurse-submodules `git merge-base HEAD origin/dev`
- name: Install apt dependencies on the dev branch
# this is only needed on a PR that changes the LLVM version
run: ./.github/workflows/sizediff-install-pkgs.sh
- name: Build tinygo binary for the dev branch
run: go install
- name: Determine binary sizes on the dev branch
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt)
# Create comment
# TODO: add a summary, something like:
# - overall size difference (percent)
# - number of binaries that grew / shrank / remained the same
# - don't show the full diff when no binaries changed
- name: Calculate size diff
run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt
- name: Create comment
run: |
echo "Size difference with the dev branch:" > comment.txt
echo "<details><summary>Binary size difference</summary>" >> comment.txt
echo "<pre>" >> comment.txt
cat sizediff.txt >> comment.txt
echo "</pre></details>" >> comment.txt
- name: Comment contents
run: cat comment.txt
- name: Add comment
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
uses: thollander/actions-comment-pull-request@v2.3.1
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
filePath: comment.txt
comment_tag: sizediff
@@ -1,4 +0,0 @@
#!/bin/sh
# Extract the version string from the source code, to be stored in a variable.
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
+36 -120
View File
@@ -14,33 +14,28 @@ concurrency:
jobs: jobs:
build-windows: build-windows:
runs-on: windows-2022 runs-on: windows-2022
outputs:
version: ${{ steps.version.outputs.version }}
steps: steps:
- uses: MinoruSekine/setup-scoop@v4 - uses: brechtm/setup-scoop@v2
with:
scoop_update: 'false'
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
run: | run: |
scoop config use_external_7zip true
scoop install ninja binaryen scoop install ninja binaryen
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v2
with: with:
submodules: true submodules: true
- name: Extract TinyGo version
id: version
shell: bash
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v3
with: with:
go-version: '1.27.0-rc.2' go-version: '1.19'
cache: true cache: true
- name: Restore cached LLVM source - name: Cache LLVM source
uses: actions/cache/restore@v5 uses: actions/cache@v3
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-22-windows-v3 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
@@ -50,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@v5 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@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-22-windows-v1 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'
@@ -78,111 +62,43 @@ 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 - name: Cache wasi-libc sysroot
uses: actions/cache/save@v5 uses: actions/cache@v3
if: steps.cache-llvm-build.outputs.cache-hit != 'true' id: cache-wasi-libc
with: with:
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }} key: wasi-libc-sysroot-v4
path: llvm-build path: lib/wasi-libc/sysroot
- name: Cache Go cache - name: Build wasi-libc
uses: actions/cache@v5 if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
with: run: make wasi-libc
key: go-cache-windows-v3-${{ hashFiles('go.mod') }}
path: |
C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod
- name: Install wasmtime - name: Install wasmtime
run: | run: |
scoop config use_external_7zip true scoop install wasmtime
scoop install wasmtime@29.0.1
- name: make gen-device
run: make -j3 gen-device
- name: Test TinyGo - name: Test TinyGo
shell: bash shell: bash
run: make test GOTESTFLAGS="-only-current-os" 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
- name: Make release artifact - name: Make release artifact
shell: bash shell: bash
working-directory: build/release working-directory: build/release
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo run: 7z -tzip a release.zip tinygo
- name: Publish release artifact - name: Publish release artifact
uses: actions/upload-artifact@v7 # Note: this release artifact is double-zipped, see:
# https://github.com/actions/upload-artifact/issues/39
# We can essentially pick one of these:
# - have a dobule-zipped artifact when downloaded from the UI
# - have a very slow artifact upload
# We're doing the former here, to keep artifact uploads fast.
uses: actions/upload-artifact@v2
with: with:
name: tinygo${{ steps.version.outputs.version }}.windows-amd64.zip name: release-double-zipped
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip path: build/release/release.zip
archive: false
smoke-test-windows:
runs-on: windows-2022
needs: build-windows
steps:
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install binaryen
- name: Checkout
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
# This build is already unzipped.
- 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: Checkout
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
# This build is already unzipped.
- 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:
- uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies
shell: bash
run: |
scoop config use_external_7zip true
scoop install binaryen wasmtime@29.0.1
- name: Checkout
uses: actions/checkout@v6
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
with:
name: tinygo${{ needs.build-windows.outputs.version }}.windows-amd64.zip
path: build/
# This build is already unzipped.
- name: Test stdlib packages on wasip1
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
+1 -12
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
@@ -20,11 +15,9 @@ src/device/stm32/*.go
src/device/stm32/*.s src/device/stm32/*.s
src/device/kendryte/*.go src/device/kendryte/*.go
src/device/kendryte/*.s src/device/kendryte/*.s
src/device/renesas/*.go
src/device/renesas/*.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/*
@@ -37,9 +30,5 @@ test.exe
test.gba test.gba
test.hex test.hex
test.nro test.nro
test.uf2
test.wasm test.wasm
wasm.wasm wasm.wasm
*.uf2
*.elf
+4 -14
View File
@@ -9,20 +9,19 @@
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/picolibc/picolibc.git url = https://github.com/keith-packard/picolibc.git
[submodule "lib/stm32-svd"] [submodule "lib/stm32-svd"]
path = lib/stm32-svd path = lib/stm32-svd
url = https://github.com/tinygo-org/stm32-svd url = https://github.com/tinygo-org/stm32-svd
[submodule "lib/musl"] [submodule "lib/musl"]
path = lib/musl path = lib/musl
url = https://github.com/tinygo-org/musl-libc.git url = git://git.musl-libc.org/musl
[submodule "lib/binaryen"] [submodule "lib/binaryen"]
path = lib/binaryen path = lib/binaryen
url = https://github.com/WebAssembly/binaryen.git url = https://github.com/WebAssembly/binaryen.git
@@ -32,12 +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 "src/net"]
path = src/net
url = https://github.com/tinygo-org/net.git
[submodule "lib/wasi-cli"]
path = lib/wasi-cli
url = https://github.com/WebAssembly/wasi-cli
[submodule "lib/bdwgc"]
path = lib/bdwgc
url = https://github.com/ivmai/bdwgc.git
+3 -25
View File
@@ -18,8 +18,7 @@ tarball. If you want to help with development of TinyGo itself, you should follo
LLVM, Clang and LLD are quite light on dependencies, requiring only standard 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
@@ -85,17 +69,11 @@ Try running TinyGo:
./build/tinygo help ./build/tinygo help
Also, make sure the `tinygo` binary really is statically linked. The command to check for Also, make sure the `tinygo` binary really is statically linked. Check this
dynamic dependencies differs depending on your operating system. using `ldd` (not to be confused with `lld`):
On Linux, use `ldd` (not to be confused with `lld`):
ldd ./build/tinygo ldd ./build/tinygo
On macOS, use otool -L:
otool -L ./build/tinygo
The result should not contain libclang or libLLVM. The result should not contain libclang or libLLVM.
## Make a release tarball ## Make a release tarball
+4 -1280
View File
File diff suppressed because it is too large Load Diff
+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.27rc2 AS tinygo-llvm FROM golang:1.19 AS tinygo-llvm
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-17 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.27rc2 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"]
-32
View File
@@ -1,32 +0,0 @@
TinyGo Team Members
===================
The team of humans who maintain TinyGo.
* **Purpose**: To maintain the community, code, documentation, and tools for the TinyGo compiler.
* **Board**: The group of people who share responsibility for key decisions for the TinyGo organization.
* **Majority Voting**: The board makes decisions by majority vote.
* **Membership**: The board elects its own members.
* **Do-ocracy**: Those who step forward to do a given task propose how it should be done. Then other interested people can make comments.
* **Proof of Work**: Power in decision-making is slightly weighted based on a participant's labor for the community.
* **Initiation**: We need to establish a procedure for how people join the team of maintainers.
* **Transparency**: Important information should be made publicly available, ideally in a way that allows for public comment.
* **Code of Conduct**: Participants agree to abide by the current project Code of Conduct.
## Members
* Ayke van Laethem (@aykevl)
* Daniel Esteban (@conejoninja)
* Ron Evans (@deadprogram)
* Damian Gryski (@dgryski)
* Masaaki Takasago (@sago35)
* Patricio Whittingslow (@soypat)
* Yurii Soldak (@ysoldak)
## Experimental
* **Monthly Meeting**: A monthly meeting for the team and any other interested participants.
Duration: 1 hour
Facilitation: @deadprogram
Schedule: See https://github.com/tinygo-org/tinygo/wiki/Meetings for more information
+2 -3
View File
@@ -1,8 +1,7 @@
Copyright (c) 2018-2026 The TinyGo Authors. All rights reserved. Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
TinyGo includes portions of the Go standard library. TinyGo includes portions of the Go standard library.
Copyright 2009 The Go Authors. All rights reserved. Copyright (c) 2009-2022 The Go Authors. All rights reserved.
See https://github.com/golang/go/blob/master/LICENSE for license information.
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.
+138 -566
View File
File diff suppressed because it is too large Load Diff
+102 -60
View File
@@ -1,16 +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) [![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.
> [!IMPORTANT]
> You can help TinyGo with a financial contribution using OpenCollective. Please see https://opencollective.com/tinygo for more information. Thank you!
## Embedded
Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED: Here is an example program that blinks the built-in LED when run directly on any supported board with onboard LED:
```go ```go
@@ -34,70 +29,117 @@ func main() {
} }
``` ```
The above program can be compiled and run without modification on an [Arduino Uno](https://tinygo.org/docs/reference/microcontrollers/boards/arduino-uno), an [Adafruit Circuit Playground Express](https://tinygo.org/docs/reference/microcontrollers/featured/circuitplay-express), a [Seeed Studio XIAO-ESP32S3](https://tinygo.org/docs/reference/microcontrollers/featured/xiao-esp32s3) or any of the many supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno: The above program can be compiled and run without modification on an Arduino Uno, an Adafruit ItsyBitsy M0, or any of the supported boards that have a built-in LED, just by setting the correct TinyGo compiler target. For example, this compiles and flashes an Arduino Uno:
```shell ```shell
tinygo flash -target arduino-uno 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:wasmexport add
func add(x, y uint32) uint32 {
return x + y
}
```
This compiles the above TinyGo program for use on any WASI Preview 1 runtime:
```shell
tinygo build -buildmode=c-shared -o add.wasm -target=wasip1 add.go
```
You can also use the same syntax as Go 1.24+:
```shell
GOOS=wasip1 GOARCH=wasm tinygo build -buildmode=c-shared -o add.wasm add.go
``` ```
## 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 150 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:
@@ -142,7 +184,7 @@ Non-goals:
## Why this project exists ## Why this project exists
> We never expected Go to be an embedded language, and so its got serious problems... > We never expected Go to be an embedded language and so its got serious problems...
-- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799) -- Rob Pike, [GopherCon 2014 Opening Keynote](https://www.youtube.com/watch?v=VoS7DsT1rdM&feature=youtu.be&t=2799)
+4 -47
View File
@@ -3,7 +3,6 @@ package builder
import ( import (
"bytes" "bytes"
"debug/elf" "debug/elf"
"debug/macho"
"debug/pe" "debug/pe"
"encoding/binary" "encoding/binary"
"errors" "errors"
@@ -13,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...>
@@ -63,20 +61,6 @@ func makeArchive(arfile *os.File, objs []string) error {
fileIndex int fileIndex int
}{symbol.Name, i}) }{symbol.Name, i})
} }
} else if dbg, err := macho.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symtab.Syms {
// See mach-o/nlist.h
if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT
continue // undefined symbol
}
if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0
continue // internal symbol (static, etc)
}
symbolTable = append(symbolTable, struct {
name string
fileIndex int
}{symbol.Name, i})
}
} else if dbg, err := pe.NewFile(objfile); err == nil { } else if dbg, err := pe.NewFile(objfile); err == nil {
for _, symbol := range dbg.Symbols { for _, symbol := range dbg.Symbols {
if symbol.StorageClass != 2 { if symbol.StorageClass != 2 {
@@ -90,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
@@ -165,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
} }
@@ -187,7 +144,7 @@ func makeArchive(arfile *os.File, objs []string) error {
// Store the start index, for when we'll update the symbol table with // 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
} }
-91
View File
@@ -1,91 +0,0 @@
package builder
// The well-known conservative Boehm-Demers-Weiser GC.
// This file provides a way to compile this GC for use with TinyGo.
import (
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var BoehmGC = Library{
name: "bdwgc",
cflags: func(target, headerPath string) []string {
libdir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
flags := []string{
// use a modern environment
"-DUSE_MMAP", // mmap is available
"-DUSE_MUNMAP", // return memory to the OS using munmap
"-DGC_BUILTIN_ATOMIC", // use compiler intrinsics for atomic operations
"-DNO_EXECUTE_PERMISSION", // don't make the heap executable
// specific flags for TinyGo
"-DALL_INTERIOR_POINTERS", // scan interior pointers (needed for Go)
"-DIGNORE_DYNAMIC_LOADING", // we don't support dynamic loading at the moment
"-DNO_GETCONTEXT", // musl doesn't support getcontext()
"-DGC_DISABLE_INCREMENTAL", // don't mess with SIGSEGV and such
// Use a minimal environment.
"-DNO_MSGBOX_ON_ERROR", // don't call MessageBoxA on Windows
"-DDONT_USE_ATEXIT",
"-DNO_GETENV", // smaller binary, more predictable configuration
"-DNO_CLOCK", // don't use system clock
"-DNO_DEBUGGING", // reduce code size
"-DGC_NO_FINALIZATION", // finalization is not used at the moment
// Special flag to work around the lack of __data_start in ld.lld.
// TODO: try to fix this in LLVM/lld directly so we don't have to
// work around it anymore.
"-DGC_DONT_REGISTER_MAIN_STATIC_DATA",
// Do not scan the stack. We have our own mechanism to do this.
"-DSTACK_NOT_SCANNED",
"-DNO_PROC_STAT", // we scan the stack manually (don't read /proc/self/stat on Linux)
"-DSTACKBOTTOM=0", // dummy value, we scan the stack manually
// Assertions can be enabled while debugging GC issues.
//"-DGC_ASSERTIONS",
// We use our own way of dealing with threads (that is a bit hacky).
// See src/runtime/gc_boehm.go.
//"-DGC_THREADS",
//"-DTHREAD_LOCAL_ALLOC",
"-I" + libdir + "/include",
}
return flags
},
needsLibc: true,
sourceDir: func() string {
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/bdwgc")
},
librarySources: func(target string, _ bool) ([]string, error) {
sources := []string{
"allchblk.c",
"alloc.c",
"blacklst.c",
"dbg_mlc.c",
"dyn_load.c",
"headers.c",
"mach_dep.c",
"malloc.c",
"mark.c",
"mark_rts.c",
"misc.c",
"new_hblk.c",
"os_dep.c",
"reclaim.c",
}
if strings.Split(target, "-")[2] == "windows" {
// Due to how the linker on Windows works (that doesn't allow
// undefined functions), we need to include these extra files.
sources = append(sources,
"mallocx.c",
"ptr_chck.c",
)
}
return sources, nil
},
}
+276 -433
View File
File diff suppressed because it is too large Load Diff
+14 -85
View File
@@ -5,10 +5,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strings"
"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"
) )
@@ -29,19 +29,13 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4", "cortex-m4",
"cortex-m7", "cortex-m7",
"esp32c3", "esp32c3",
"esp32c6",
"esp32s3",
"fe310", "fe310",
"gameboy-advance", "gameboy-advance",
"k210", "k210",
"nintendoswitch", "nintendoswitch",
"riscv-qemu", "riscv-qemu",
"tkey", "wasi",
"uefi-amd64",
"wasip1",
"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,
@@ -49,6 +43,7 @@ func TestClangAttributes(t *testing.T) {
targetNames = append(targetNames, "esp32", "esp8266") targetNames = append(targetNames, "esp32", "esp8266")
} }
for _, targetName := range targetNames { for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) { t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName}) testClangAttributes(t, &compileopts.Options{Target: targetName})
}) })
@@ -57,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: "386"},
{GOOS: "windows", GOARCH: "amd64"}, {GOOS: "windows", GOARCH: "amd64"},
{GOOS: "windows", GOARCH: "arm64"},
} { } {
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)
}) })
@@ -89,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()
@@ -98,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.
@@ -111,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.
@@ -130,10 +115,8 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
defer mod.Dispose() defer mod.Dispose()
// Check whether the LLVM target matches. // Check whether the LLVM target matches.
// Use ClangTriple since LLVM 22 normalizes wasm32-unknown-wasi to wasip1. if mod.Target() != config.Triple() {
expectedTriple := compileopts.ClangTriple(config.Triple()) t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
if mod.Target() != expectedTriple {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", expectedTriple, mod.Target())
} }
// Check the "target-cpu" and "target-features" string attribute of the add // Check the "target-cpu" and "target-features" string attribute of the add
@@ -157,65 +140,11 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// The reason is that Debian has patched Clang in a way that // The reason is that Debian has patched Clang in a way that
// modifies the LLVM features string, changing lots of FPU/float // modifies the LLVM features string, changing lots of FPU/float
// related flags. We want to test vanilla Clang, not Debian Clang. // related flags. We want to test vanilla Clang, not Debian Clang.
// t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
// Rather than requiring an exact match (which breaks across LLVM
// versions as new features are added), check that every feature
// TinyGo specifies is consistent with what Clang produces:
// - A "+feature" in TinyGo must appear in Clang's output.
// - A "-feature" in TinyGo must not be "+feature" in Clang's output.
checkFeatureFlags(t, config.Features(), features)
} }
} }
} }
// checkFeatureFlags verifies that all features specified by TinyGo's target
// configuration are consistent with Clang's output. This allows Clang to add
// new features across LLVM versions without breaking the test.
func checkFeatureFlags(t *testing.T, targetFeatures, clangFeatures string) {
t.Helper()
// Build a set of Clang's features for fast lookup.
clangSet := make(map[string]bool) // feature name -> enabled
for _, f := range strings.Split(clangFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
enabled := f[0] == '+'
name := f[1:]
clangSet[name] = enabled
}
// Check each feature that TinyGo specifies.
var missing, conflicts []string
for _, f := range strings.Split(targetFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
wantEnabled := f[0] == '+'
name := f[1:]
clangEnabled, inClang := clangSet[name]
if wantEnabled && (!inClang || !clangEnabled) {
// TinyGo requires +feature but Clang doesn't enable it.
missing = append(missing, f)
} else if !wantEnabled && inClang && clangEnabled {
// TinyGo requires -feature but Clang enables it.
conflicts = append(conflicts, fmt.Sprintf("target has %q but Clang has %q", f, "+"+name))
}
}
if len(missing) > 0 {
t.Errorf("target specifies features not present in Clang output: %s\n\ttarget features: %s\n\tclang features: %s",
strings.Join(missing, ", "), targetFeatures, clangFeatures)
}
if len(conflicts) > 0 {
t.Errorf("target disables features that Clang enables: %s",
strings.Join(conflicts, "; "))
}
}
// This TestMain is necessary because TinyGo may also be invoked to run certain // This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead // LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests. // to recursive tests.
+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
} }
+13 -93
View File
@@ -5,18 +5,17 @@ import (
"path/filepath" "path/filepath"
"strings" "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",
@@ -41,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",
@@ -92,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",
@@ -111,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",
@@ -124,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",
@@ -133,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",
@@ -193,37 +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",
}
// Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{
"i386/chkstk.S", // __chkstk_ms
"i386/chkstk2.S", // _alloca (__alloca)
}
// Builtins needed specifically for windows/amd64.
var windowsAMD64Builtins = []string{
"x86_64/chkstk.S",
}
// Builtins needed specifically for windows/arm64.
var windowsARM64Builtins = []string{
"aarch64/chkstk.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"}
@@ -237,35 +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, _ bool) ([]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":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsAMD64Builtins...)
}
case "aarch64":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsARM64Builtins...)
}
case "riscv64":
builtins = append(builtins, genericBuiltins128...)
case "i386":
if isWindowsTriple(target) {
builtins = append(builtins, windowsI386Builtins...)
}
} }
return builtins, nil return builtins
}, },
} }
func isWindowsTriple(target string) bool {
parts := strings.Split(target, "-")
return len(parts) > 2 && parts[2] == "windows"
}
+17 -9
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
} }
@@ -281,7 +289,7 @@ func parseDepFile(s string) ([]string, error) {
s = strings.ReplaceAll(s, "\\\n", " ") s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:". // Only use the first line, which is expected to begin with "deps:".
line, _, _ := strings.Cut(s, "\n") line := strings.SplitN(s, "\n", 2)[0]
if !strings.HasPrefix(line, "deps:") { if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix") return nil, errors.New("readDepFile: expected 'deps:' prefix")
} }
+48 -97
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 --------------------------------------===//
// //
@@ -23,19 +16,18 @@
#include "clang/Basic/Diagnostic.h" #include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h" #include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h" #include "clang/Driver/DriverDiagnostic.h"
#include "clang/Options/Options.h" #include "clang/Driver/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h" #include "clang/Frontend/FrontendDiagnostic.h"
#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"
#include "llvm/MC/MCCodeEmitter.h" #include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h" #include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h" #include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h" #include "llvm/MC/MCObjectWriter.h"
@@ -54,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"
@@ -62,13 +55,11 @@
#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::options; using namespace clang::driver;
using namespace clang::driver::options;
using namespace llvm; using namespace llvm;
using namespace llvm::opt; using namespace llvm::opt;
@@ -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)
@@ -109,24 +100,13 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Construct the invocation. // Construct the invocation.
// Target Options // Target Options
Opts.Triple = llvm::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);
// Use the default target triple if unspecified. // Use the default target triple if unspecified.
if (Opts.Triple.getTriple().empty()) if (Opts.Triple.empty())
Opts.Triple = llvm::Triple(llvm::sys::getDefaultTargetTriple()); Opts.Triple = llvm::sys::getDefaultTargetTriple();
// Language Options // Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I); Opts.IncludePaths = Args.getAllArgValues(OPT_I);
@@ -139,11 +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);
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);
@@ -158,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
@@ -205,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));
@@ -223,23 +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.Crel = Args.hasArg(OPT_crel);
Opts.ImplicitMapsyms = Args.hasArg(OPT_mmapsyms_implicit);
Opts.X86RelaxRelocations = !Args.hasArg(OPT_mrelax_relocations_no);
Opts.X86Sse2Avx = Args.hasArg(OPT_msse2avx);
Opts.AsSecureLogFile = Args.getLastArgValue(OPT_as_secure_log_file);
return Success; return Success;
} }
@@ -267,14 +230,14 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::string Error; std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error); const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget) if (!TheTarget)
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str(); return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer = ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
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;
@@ -290,24 +253,15 @@ 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.MCRelaxAll = Opts.RelaxAll;
MCOptions.EmitDwarfUnwind = Opts.EmitDwarfUnwind;
MCOptions.EmitCompactUnwindNonCanonical = Opts.EmitCompactUnwindNonCanonical;
MCOptions.MCSaveTempLabels = Opts.SaveTemporaryLabels;
MCOptions.Crel = Opts.Crel;
MCOptions.ImplicitMapSyms = Opts.ImplicitMapsyms;
MCOptions.X86RelaxRelocations = Opts.X86RelaxRelocations;
MCOptions.X86Sse2Avx = Opts.X86Sse2Avx;
MCOptions.CompressDebugSections = Opts.CompressDebugSections;
MCOptions.AsSecureLogFile = Opts.AsSecureLogFile;
std::unique_ptr<MCAsmInfo> MAI( 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!");
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections // Ensure MCAsmInfo initialization occurs before any use, otherwise sections
// may be created with a combination of default and explicit settings. // may be created with a combination of default and explicit settings.
MAI->setCompressDebugSections(Opts.CompressDebugSections);
MAI->setRelaxELFRelocations(Opts.RelaxELFRelocations);
bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj; bool IsBinary = Opts.OutputType == AssemblerInvocation::FT_Obj;
if (Opts.OutputPath.empty()) if (Opts.OutputPath.empty())
@@ -327,7 +281,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS)); TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
assert(STI && "Unable to create subtarget info!"); assert(STI && "Unable to create subtarget info!");
MCContext Ctx(Opts.Triple, MAI.get(), MRI.get(), STI.get(), &SrcMgr, MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
&MCOptions); &MCOptions);
bool PIC = false; bool PIC = false;
@@ -347,6 +301,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCObjectFileInfo(Ctx, PIC)); TheTarget->createMCObjectFileInfo(Ctx, PIC));
Ctx.setObjectFileInfo(MOFI.get()); Ctx.setObjectFileInfo(MOFI.get());
if (Opts.SaveTemporaryLabels)
Ctx.setAllowTemporaryLabels(false);
if (Opts.GenDwarfForAssembly) if (Opts.GenDwarfForAssembly)
Ctx.setGenDwarfForAssembly(true); Ctx.setGenDwarfForAssembly(true);
if (!Opts.DwarfDebugFlags.empty()) if (!Opts.DwarfDebugFlags.empty())
@@ -382,26 +338,24 @@ 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.ShowMCInst = Opts.ShowInst;
MCOptions.AsmVerbose = true;
MCOptions.MCUseDwarfDirectory = MCTargetOptions::EnableDwarfDirectory;
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.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) { if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter( MCInstPrinter *IP = TheTarget->createMCInstPrinter(
Opts.Triple, Opts.OutputAsmVariant, *MAI, *MCII, *MRI)); llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
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));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out); auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(IP), Str.reset(TheTarget->createAsmStreamer(
std::move(CE), std::move(MAB))); Ctx, std::move(FOut), /*asmverbose*/ true,
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
Opts.ShowInst));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) { } else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx)); Str.reset(createNullStreamer(Ctx));
} else { } else {
@@ -413,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!");
@@ -422,17 +376,12 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS) DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out); : MAB->createObjectWriter(*Out);
Triple T = Opts.Triple; Triple T(Opts.Triple);
Str.reset(TheTarget->createMCObjectStreamer( Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI)); T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI,
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
/*DWARFMustBeAtTheEnd*/ true));
Str.get()->initSections(Opts.NoExecStack, *STI); Str.get()->initSections(Opts.NoExecStack, *STI);
if (T.isOSBinFormatMachO() && T.isOSDarwin()) {
Triple *TVT = Opts.DarwinTargetVariantTriple
? &*Opts.DarwinTargetVariantTriple
: nullptr;
Str->emitVersionForTarget(T, VersionTuple(), TVT,
Opts.DarwinTargetVariantSDKVersion);
}
} }
// When -fembed-bitcode is passed to clang_as, a 1-byte marker // When -fembed-bitcode is passed to clang_as, a 1-byte marker
@@ -440,10 +389,13 @@ 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);
} }
// Assembly to object compilation should leverage assembly info.
Str->setUseAssemblerInfoForParsing(true);
bool Failed = false; bool Failed = false;
std::unique_ptr<MCAsmParser> Parser( std::unique_ptr<MCAsmParser> Parser(
@@ -453,7 +405,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCTargetAsmParser> TAP( std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions)); TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP) if (!TAP)
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str(); Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
// Set values for symbols, if any. // Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) { for (auto &S : Opts.SymbolDefs) {
@@ -506,12 +458,12 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
InitializeAllAsmParsers(); InitializeAllAsmParsers();
// Construct our diagnostic client. // Construct our diagnostic client.
DiagnosticOptions DiagOpts; IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), DiagOpts); = new TextDiagnosticPrinter(errs(), &*DiagOpts);
DiagClient->setPrefix("clang -cc1as"); DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs()); IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
DiagnosticsEngine Diags(DiagID, DiagOpts, DiagClient); DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
// Set an error handler, so that any LLVM backend diagnostics go through our // Set an error handler, so that any LLVM backend diagnostics go through our
// error handler. // error handler.
@@ -526,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(clang::options::CC1AsOption)); /*ShowAllAliases=*/false);
return 0; return 0;
} }
+4 -57
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.
@@ -20,7 +17,7 @@ struct AssemblerInvocation {
/// @{ /// @{
/// The name of the target triple to assemble for. /// The name of the target triple to assemble for.
llvm::Triple Triple; std::string Triple;
/// If given, the name of the target CPU to determine which instructions /// If given, the name of the target CPU to determine which instructions
/// are legal. /// are legal.
@@ -38,19 +35,16 @@ struct AssemblerInvocation {
/// @{ /// @{
std::vector<std::string> IncludePaths; std::vector<std::string> IncludePaths;
LLVM_PREFERRED_TYPE(bool)
unsigned NoInitialTextSection : 1; unsigned NoInitialTextSection : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned SaveTemporaryLabels : 1; unsigned SaveTemporaryLabels : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned GenDwarfForAssembly : 1; unsigned GenDwarfForAssembly : 1;
LLVM_PREFERRED_TYPE(bool) unsigned RelaxELFRelocations : 1;
unsigned Dwarf64 : 1; unsigned Dwarf64 : 1;
unsigned DwarfVersion; unsigned DwarfVersion;
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;
@@ -69,9 +63,7 @@ struct AssemblerInvocation {
FT_Obj ///< Object file output. FT_Obj ///< Object file output.
}; };
FileType OutputType; FileType OutputType;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowHelp : 1; unsigned ShowHelp : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowVersion : 1; unsigned ShowVersion : 1;
/// @} /// @}
@@ -79,48 +71,20 @@ struct AssemblerInvocation {
/// @{ /// @{
unsigned OutputAsmVariant; unsigned OutputAsmVariant;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowEncoding : 1; unsigned ShowEncoding : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ShowInst : 1; unsigned ShowInst : 1;
/// @} /// @}
/// @name Assembler Options /// @name Assembler Options
/// @{ /// @{
LLVM_PREFERRED_TYPE(bool)
unsigned RelaxAll : 1; unsigned RelaxAll : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoExecStack : 1; unsigned NoExecStack : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned FatalWarnings : 1; unsigned FatalWarnings : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoWarn : 1; unsigned NoWarn : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned NoTypeCheck : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned IncrementalLinkerCompatible : 1; unsigned IncrementalLinkerCompatible : 1;
LLVM_PREFERRED_TYPE(bool)
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.
LLVM_PREFERRED_TYPE(bool)
unsigned EmitCompactUnwindNonCanonical : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned Crel : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned ImplicitMapsyms : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86RelaxRelocations : 1;
LLVM_PREFERRED_TYPE(bool)
unsigned X86Sse2Avx : 1;
/// The name of the relocation model to use. /// The name of the relocation model to use.
std::string RelocationModel; std::string RelocationModel;
@@ -128,21 +92,11 @@ 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:
AssemblerInvocation() { AssemblerInvocation() {
Triple = llvm::Triple(); Triple = "";
NoInitialTextSection = 0; NoInitialTextSection = 0;
InputFile = "-"; InputFile = "-";
OutputPath = "-"; OutputPath = "-";
@@ -154,17 +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;
Crel = false;
ImplicitMapsyms = 0;
X86RelaxRelocations = 0;
X86Sse2Avx = 0;
} }
static bool CreateFromArgs(AssemblerInvocation &Res, static bool CreateFromArgs(AssemblerInvocation &Res,
+5 -5
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;
@@ -27,9 +27,9 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc); std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems // The compiler invocation needs a DiagnosticsEngine so it can report problems
clang::DiagnosticOptions DiagOpts; llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), DiagOpts); clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), DiagOpts, &DiagnosticPrinter, false); clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver // Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags); clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
+13 -1
View File
@@ -3,6 +3,7 @@ package builder
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"runtime" "runtime"
"strings" "strings"
@@ -17,7 +18,7 @@ import (
var commands = map[string][]string{} var commands = map[string][]string{}
func init() { func init() {
llvmMajor, _, _ := strings.Cut(llvm.Version, ".") llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor} commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
@@ -75,3 +76,14 @@ func LookupCommand(name string) (string, error) {
} }
return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " ")) return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
} }
func execCommand(name string, args ...string) error {
name, err := LookupCommand(name)
if err != nil {
return err
}
cmd := exec.Command(name, args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
+13 -27
View File
@@ -1,8 +1,8 @@
package builder package builder
import ( import (
"errors"
"fmt" "fmt"
"runtime"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
@@ -24,40 +24,26 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
spec.OpenOCDCommands = options.OpenOCDCommands spec.OpenOCDCommands = options.OpenOCDCommands
} }
// Version range supported by TinyGo. goroot := goenv.Get("GOROOT")
const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml if goroot == "" {
const minorMax = 27 return nil, errors.New("cannot locate $GOROOT, please set it manually")
}
// Check that we support this Go toolchain version. major, minor, err := goenv.GetGorootVersion(goroot)
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
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 < 18 || minor > 19 {
return nil, fmt.Errorf("requires go version 1.18 through 1.19, got go%d.%d", major, minor)
} }
if options.GoCompatibility { clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
// Note: when this gets updated, also update the Go compatibility matrix:
// https://github.com/tinygo-org/tinygo-site/blob/dev/content/docs/reference/go-compat-matrix.md
return nil, fmt.Errorf("requires go version 1.%d through 1.%d, got go%d.%d", minorMin, minorMax, gorootMajor, gorootMinor)
}
}
// Check that the Go toolchain version isn't too new, if we haven't been
// compiled with the latest Go version.
// This may be a bit too aggressive: if the newer version doesn't change the
// Go language we will most likely be able to compile it.
buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version())
if err != nil {
return nil, err
}
if buildMajor != 1 || buildMinor < gorootMinor {
return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version())
}
return &compileopts.Config{ return &compileopts.Config{
Options: options, Options: options,
Target: spec, Target: spec,
GoMinorVersion: gorootMinor, GoMinorVersion: minor,
ClangHeaders: clangHeaderPath,
TestConfig: options.TestConfig, TestConfig: options.TestConfig,
}, nil }, nil
} }
+1 -1
View File
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
return &compileJob{ return &compileJob{
description: "compile Darwin libSystem.dylib", description: "compile Darwin libSystem.dylib",
run: func(job *compileJob) (err error) { run: func(job *compileJob) (err error) {
arch, _, _ := strings.Cut(config.Triple(), "-") arch := strings.Split(config.Triple(), "-")[0]
job.result = filepath.Join(tmpdir, "libSystem.dylib") job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o") objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s") inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
+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}
} }
} }
+14 -150
View File
@@ -23,7 +23,7 @@ type espImageSegment struct {
data []byte data []byte
} }
// makeESPFirmwareImage converts an input ELF file to an image file for an ESP32 or // makeESPFirmare converts an input ELF file to an image file for an ESP32 or
// ESP8266 chip. This is a special purpose image format just for the ESP chip // 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.
// //
@@ -31,7 +31,7 @@ type espImageSegment struct {
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format // https://github.com/espressif/esptool/wiki/Firmware-Image-Format
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58 // https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
// https://github.com/espressif/esptool/blob/master/esptool.py // https://github.com/espressif/esptool/blob/master/esptool.py
func makeESPFirmwareImage(infile, outfile, format string) error { func makeESPFirmareImage(infile, outfile, format string) error {
inf, err := elf.Open(infile) inf, err := elf.Open(infile)
if err != nil { if err != nil {
return err return err
@@ -65,6 +65,15 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
// Sort the segments by address. This is what esptool does too. // Sort the segments by address. This is what esptool does too.
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr }) sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
// Write first to an in-memory buffer, primarily so that we can easily // Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image. // calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time. // An added benefit is that we don't need to check for errors all the time.
@@ -79,86 +88,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip = format[:len(format)-len("-img")] chip = format[:len(format)-len("-img")]
} }
// For ESP32 (original): separate RAM segments (loadable by ROM bootloader)
// from flash-mapped segments (DROM/IROM, require MMU setup by startup code).
// The ROM bootloader on ESP32 does NOT handle flash-mapped segments —
// it tries to memcpy to the virtual address, which crashes.
var flashSegments []*espImageSegment
if chip == "esp32" {
var ramSegments []*espImageSegment
for _, seg := range segments {
if (seg.addr >= 0x3F400000 && seg.addr < 0x3F800000) || // DROM
(seg.addr >= 0x400D0000 && seg.addr < 0x40400000) { // IROM
flashSegments = append(flashSegments, seg)
} else {
ramSegments = append(ramSegments, seg)
}
}
segments = ramSegments
}
// ESP32 flash XIP: compute where the DROM segment will be placed in flash
// (page-aligned, right after the RAM segments) and patch the
// _drom_flash_addr variable so the startup code can program the cache MMU.
// This must happen before the checksum/hash are computed so the patched
// value is covered by both.
const esp32FlashBase = 0x1000 // esptool flashes the image at 0x1000
// The ESP32 flash cache MMU supports configurable page sizes down to 256 B. 64 KiB is the reset/default size.
// If the startup code ever changes the MMU page size, this constant must change too.
const esp32PageSize = 0x10000 // 64KB MMU pages
var esp32DromFlashAddr uint32
if chip == "esp32" && len(flashSegments) > 0 {
// Compute the size of the RAM portion of the image (everything the ROM
// bootloader loads, up to and including the appended SHA256 hash).
ramImageSize := 0
if makeImage {
ramImageSize += 4096
}
ramImageSize += 24 // image header (8) + trailer fields (16)
for _, seg := range segments {
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
}
ramImageSize += 16 - ramImageSize%16 // footer padding + checksum byte
ramImageSize += 32 // appended SHA256 hash
// DROM flash address must be 64KB page-aligned.
esp32DromFlashAddr = uint32(esp32FlashBase+ramImageSize+esp32PageSize-1) &^ (esp32PageSize - 1)
// Patch _drom_flash_addr in whichever RAM segment contains it.
syms, _ := inf.Symbols()
var dromSymAddr uint64
for _, s := range syms {
if s.Name == "_drom_flash_addr" {
dromSymAddr = s.Value
break
}
}
if dromSymAddr == 0 {
return fmt.Errorf("ESP32: _drom_flash_addr symbol not found")
}
patched := false
for _, seg := range segments {
if dromSymAddr >= uint64(seg.addr) && dromSymAddr+4 <= uint64(seg.addr)+uint64(len(seg.data)) {
off := int(dromSymAddr - uint64(seg.addr))
binary.LittleEndian.PutUint32(seg.data[off:], esp32DromFlashAddr)
patched = true
break
}
}
if !patched {
return fmt.Errorf("ESP32: _drom_flash_addr (0x%x) not in any RAM segment", dromSymAddr)
}
}
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
if makeImage { if makeImage {
// The bootloader starts at 0x1000, or 4096. // The bootloader starts at 0x1000, or 4096.
// TinyGo doesn't use a separate bootloader and runs the entire // TinyGo doesn't use a separate bootloader and runs the entire
@@ -171,24 +100,11 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{ chip_id := map[string]uint16{
"esp32": 0x0000, "esp32": 0x0000,
"esp32c3": 0x0005, "esp32c3": 0x0005,
"esp32c6": 0x000d,
"esp32s3": 0x0009,
}[chip]
// SPI flash speed/size byte (byte 3 of header):
// Upper nibble = flash size, lower nibble = flash frequency.
// The espflasher auto-detects and patches the flash size (upper nibble),
// but the frequency (lower nibble) must be correct per chip.
spiSpeedSize := map[string]uint8{
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
}[chip] }[chip]
// Image header. // Image header.
switch chip { switch chip {
case "esp32", "esp32c3", "esp32s3", "esp32c6": case "esp32", "esp32c3":
// Header format: // Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by // Note: not adding a SHA256 hash as the binary is modified by
@@ -209,8 +125,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
}{ }{
magic: 0xE9, magic: 0xE9,
segment_count: byte(len(segments)), segment_count: byte(len(segments)),
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: spiSpeedSize, spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
entry_addr: uint32(inf.Entry), entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin wp_pin: 0xEE, // disable WP pin
chip_id: chip_id, chip_id: chip_id,
@@ -262,58 +178,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
outf.Write(hash[:]) outf.Write(hash[:])
} }
// For ESP32: append flash-mapped segments (DROM/IROM) at page-aligned flash
// offsets after the RAM portion. The startup code maps them via the flash
// cache MMU (DROM at esp32DromFlashAddr, patched into _drom_flash_addr).
if len(flashSegments) > 0 {
const flashBase = esp32FlashBase
const pageSize = esp32PageSize
dromFlashAddr := esp32DromFlashAddr
// Separate DROM and IROM segments.
var dromSegs, iromSegs []*espImageSegment
for _, seg := range flashSegments {
if seg.addr >= 0x3F400000 && seg.addr < 0x3F800000 {
dromSegs = append(dromSegs, seg)
} else {
iromSegs = append(iromSegs, seg)
}
}
// Write DROM segments at the computed page-aligned flash offset.
dromSize := 0
if len(dromSegs) > 0 {
targetImageOffset := int(dromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range dromSegs {
outf.Write(seg.data)
dromSize += len(seg.data)
}
}
// Write IROM segments immediately after DROM, at the next page boundary.
// IROM flash addr = dromFlashAddr + ceil(dromSize/pageSize)*pageSize
// (must match the computation in the startup assembly).
if len(iromSegs) > 0 {
dromPages := (dromSize + pageSize - 1) / pageSize
if dromPages == 0 {
dromPages = 1
}
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
targetImageOffset := int(iromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range iromSegs {
outf.Write(seg.data)
}
}
}
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the // QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
// image to be a certain size. // image to be a certain size.
if makeImage { if makeImage {
+19 -9
View File
@@ -17,6 +17,14 @@ import (
// concurrency or performance issues. // concurrency or performance issues.
const jobRunnerDebug = false const jobRunnerDebug = false
type jobState uint8
const (
jobStateQueued jobState = iota // not yet running
jobStateRunning // running
jobStateFinished // finished running
)
// compileJob is a single compiler job, comparable to a single Makefile target. // compileJob is a single compiler job, comparable to a single Makefile target.
// It is used to orchestrate various compiler tasks that can be run in parallel // It is used to orchestrate various compiler tasks that can be run in parallel
// but that have dependencies and thus have limitations in how they can be run. // but that have dependencies and thus have limitations in how they can be run.
@@ -47,11 +55,12 @@ func dummyCompileJob(result string) *compileJob {
// ordered as such in the job dependencies. // ordered as such in the job dependencies.
func runJobs(job *compileJob, sema chan struct{}) error { func runJobs(job *compileJob, sema chan struct{}) error {
if sema == nil { if sema == nil {
// Have a default, if the semaphore isn't set. This is useful for tests. // Have a default, if the semaphore isn't set. This is useful for
// tests.
sema = make(chan struct{}, runtime.NumCPU()) sema = make(chan struct{}, runtime.NumCPU())
} }
if cap(sema) == 0 { if cap(sema) == 0 {
return errors.New("cannot run 0 jobs at a time") return errors.New("cannot 0 jobs at a time")
} }
// Create a slice of jobs to run, where all dependencies are run in order. // Create a slice of jobs to run, where all dependencies are run in order.
@@ -72,10 +81,10 @@ func runJobs(job *compileJob, sema chan struct{}) error {
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs)) waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
dependents := make(map[*compileJob][]*compileJob, len(jobs)) dependents := make(map[*compileJob][]*compileJob, len(jobs))
compileJobs := make(map[*compileJob]int) jidx := make(map[*compileJob]int)
var ready intHeap var ready intHeap
for i, job := range jobs { for i, job := range jobs {
compileJobs[job] = i jidx[job] = i
if len(job.dependencies) == 0 { if len(job.dependencies) == 0 {
// This job is ready to run. // This job is ready to run.
ready.Push(i) ready.Push(i)
@@ -96,7 +105,8 @@ func runJobs(job *compileJob, sema chan struct{}) error {
// Create a channel to accept notifications of completion. // Create a channel to accept notifications of completion.
doneChan := make(chan *compileJob) doneChan := make(chan *compileJob)
// Send each job in the jobs slice to a worker, taking care of job dependencies. // Send each job in the jobs slice to a worker, taking care of job
// dependencies.
numRunningJobs := 0 numRunningJobs := 0
var totalTime time.Duration var totalTime time.Duration
start := time.Now() start := time.Now()
@@ -124,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.
@@ -146,7 +156,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
delete(wait, completed) delete(wait, completed)
if len(wait) == 0 { if len(wait) == 0 {
// This job is now ready to run. // This job is now ready to run.
ready.Push(compileJobs[j]) ready.Push(jidx[j])
delete(waiting, j) delete(waiting, j)
} }
} }
@@ -195,11 +205,11 @@ type intHeap struct {
sort.IntSlice sort.IntSlice
} }
func (h *intHeap) Push(x any) { func (h *intHeap) Push(x interface{}) {
h.IntSlice = append(h.IntSlice, x.(int)) h.IntSlice = append(h.IntSlice, x.(int))
} }
func (h *intHeap) Pop() any { func (h *intHeap) Pop() interface{} {
x := h.IntSlice[len(h.IntSlice)-1] x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1] h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x return x
+40 -76
View File
@@ -15,9 +15,6 @@ import (
// Library is a container for information about a single C library, such as a // Library is a container for information about a single C library, such as a
// compiler runtime or libc. // compiler runtime or libc.
//
// Note: whenever a library gets changed, the version in compileopts/config.go
// probably also needs to be incremented.
type Library struct { type Library struct {
// The library name, such as compiler-rt or picolibc. // The library name, such as compiler-rt or picolibc.
name string name string
@@ -28,22 +25,29 @@ type Library struct {
// cflags returns the C flags specific to this library // cflags returns the C flags specific to this library
cflags func(target, headerPath string) []string cflags func(target, headerPath string) []string
// cflagsForFile returns additional C flags for a particular source file.
cflagsForFile func(path string) []string
// needsLibc is set to true if this library needs libc headers.
needsLibc bool
// The source directory. // The source directory.
sourceDir func() string sourceDir func() string
// The source files, relative to sourceDir. // The source files, relative to sourceDir.
librarySources func(target string, libcNeedsMalloc bool) ([]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
@@ -53,8 +57,13 @@ type Library struct {
// As a side effect, this call creates the library header files if they didn't // As a side effect, this call creates the library header files if they didn't
// exist yet. // exist yet.
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) { func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
outdir := config.LibraryPath(l.name) outdir, precompiled := config.LibcPath(l.name)
archiveFilePath := filepath.Join(outdir, "lib.a") archiveFilePath := filepath.Join(outdir, "lib.a")
if precompiled {
// Found a precompiled library for this OS/architecture. Return the path
// directly.
return dummyCompileJob(archiveFilePath), func() {}, nil
}
// Create a lock on the output (if supported). // Create a lock on the output (if supported).
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build. // This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
@@ -133,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="+compileopts.ClangTriple(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.
@@ -150,43 +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="+riscvMarch(config, "rv32imac"), "-fforce-enable-int128")
case "riscv64":
args = append(args, "-march="+riscvMarch(config, "rv64gc"))
case "mips":
args = append(args, "-fno-pic")
} }
if config.Target.SoftFloat { 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")
} else {
if strings.HasPrefix(target, "armv5") {
// On ARMv5 we need to explicitly enable hardware floating point
// instructions: Clang appears to assume the hardware doesn't have a
// FPU otherwise.
args = append(args, "-mfpu=vfpv2")
}
} }
if l.needsLibc { if strings.HasPrefix(target, "riscv64-") {
args = append(args, config.LibcCFlags()...) args = append(args, "-march=rv64gc", "-mabi=lp64")
}
if strings.HasPrefix(target, "xtensa") {
// Hack to work around an issue in the Xtensa port:
// https://github.com/espressif/llvm-project/issues/52
// Hopefully this will be fixed soon (LLVM 14).
args = append(args, "-D__ELF__")
} }
var once sync.Once var once sync.Once
@@ -218,7 +211,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
return err return err
} }
// Store this archive in the cache. // Store this archive in the cache.
return robustRename(f.Name(), archiveFilePath) return os.Rename(f.Name(), archiveFilePath)
}, },
} }
@@ -226,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, config.LibcNeedsMalloc()) 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, "../") {
@@ -240,26 +229,19 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
objpath := filepath.Join(dir, cleanpath+".o") objpath := filepath.Join(dir, cleanpath+".o")
os.MkdirAll(filepath.Dir(objpath), 0o777) os.MkdirAll(filepath.Dir(objpath), 0o777)
objs = append(objs, objpath) objs = append(objs, objpath)
objfile := &compileJob{ job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath, description: "compile " + srcpath,
run: func(*compileJob) error { run: func(*compileJob) error {
var compileArgs []string var compileArgs []string
compileArgs = append(compileArgs, args...) compileArgs = append(compileArgs, args...)
if l.cflagsForFile != nil {
compileArgs = append(compileArgs, l.cflagsForFile(path)...)
}
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}
} }
return nil return nil
}, },
} })
job.dependencies = append(job.dependencies, objfile)
} }
// Create crt1.o job, if needed. // Create crt1.o job, if needed.
@@ -268,7 +250,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// won't make much of a difference in speed). // won't make much of a difference in speed).
if l.crt1Source != "" { if l.crt1Source != "" {
srcpath := filepath.Join(sourceDir, l.crt1Source) srcpath := filepath.Join(sourceDir, l.crt1Source)
crt1Job := &compileJob{ job.dependencies = append(job.dependencies, &compileJob{
description: "compile " + srcpath, description: "compile " + srcpath,
run: func(*compileJob) error { run: func(*compileJob) error {
var compileArgs []string var compileArgs []string
@@ -279,17 +261,13 @@ 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}
} }
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o")) return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
}, },
} })
job.dependencies = append(job.dependencies, crt1Job)
} }
ok = true ok = true
@@ -297,17 +275,3 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
once.Do(unlock) once.Do(unlock)
}, nil }, nil
} }
// riscvMarch returns the -march value for RISC-V library compilation.
// It extracts the value from the target's cflags if present, otherwise
// falls back to the provided default. This ensures libraries are compiled
// with the correct ISA extensions for each target (e.g. rv32imc for
// ESP32-C3 which lacks the atomic extension).
func riscvMarch(config *compileopts.Config, defaultMarch string) string {
for _, flag := range config.Target.CFlags {
if strings.HasPrefix(flag, "-march=") {
return flag[len("-march="):]
}
}
return defaultMarch
}
+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"
+31 -113
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,67 +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.
flags := []string{ return nil
"-nostdlibinc",
"-isystem", mingwDir + "/mingw-w64-crt/include",
"-isystem", mingwDir + "/mingw-w64-headers/crt",
"-isystem", mingwDir + "/mingw-w64-headers/include",
"-I", mingwDir + "/mingw-w64-headers/defaults/include",
"-I" + headerPath,
}
if strings.Split(target, "-")[0] == "i386" {
flags = append(flags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
"-D_CRTBLD",
"-Wno-pragma-pack",
)
}
return flags
}, },
librarySources: func(target string, _ bool) ([]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.
var sources []string return nil
if strings.Split(target, "-")[0] == "i386" {
// Old 32-bit x86 systems use msvcrt.dll.
sources = []string{
"mingw-w64-crt/crt/pseudo-reloc.c",
"mingw-w64-crt/gdtoa/dmisc.c",
"mingw-w64-crt/gdtoa/gdtoa.c",
"mingw-w64-crt/gdtoa/gmisc.c",
"mingw-w64-crt/gdtoa/misc.c",
"mingw-w64-crt/math/x86/exp2.S",
"mingw-w64-crt/math/x86/trunc.S",
"mingw-w64-crt/misc/___mb_cur_max_func.c",
"mingw-w64-crt/misc/lc_locale_func.c",
"mingw-w64-crt/misc/mbrtowc.c",
"mingw-w64-crt/misc/strnlen.c",
"mingw-w64-crt/misc/wcrtomb.c",
"mingw-w64-crt/misc/wcsnlen.c",
"mingw-w64-crt/stdio/acrt_iob_func.c",
"mingw-w64-crt/stdio/mingw_lock.c",
"mingw-w64-crt/stdio/mingw_pformat.c",
"mingw-w64-crt/stdio/mingw_vfprintf.c",
"mingw-w64-crt/stdio/mingw_vsnprintf.c",
}
} else {
// Anything somewhat modern (amd64, arm64) uses UCRT.
sources = []string{
"mingw-w64-crt/stdio/ucrt_fprintf.c",
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
"mingw-w64-crt/stdio/ucrt_printf.c",
"mingw-w64-crt/stdio/ucrt_snprintf.c",
"mingw-w64-crt/stdio/ucrt_sprintf.c",
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
"mingw-w64-crt/stdio/ucrt_vprintf.c",
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
}
}
return sources, nil
}, },
} }
@@ -97,44 +43,30 @@ 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")
var libs []string // Normally all the api-ms-win-crt-*.def files are all compiled to a single
if goarch == "386" { // .lib file. But to simplify things, we're going to leave them as separate
libs = []string{ // files.
// x86 uses msvcrt.dll instead of UCRT for compatibility with old for _, name := range []string{
// Windows versions. "kernel32.def.in",
"advapi32.def.in", "api-ms-win-crt-conio-l1-1-0.def",
"kernel32.def.in", "api-ms-win-crt-convert-l1-1-0.def",
"msvcrt.def.in", "api-ms-win-crt-environment-l1-1-0.def",
} "api-ms-win-crt-filesystem-l1-1-0.def",
} else { "api-ms-win-crt-heap-l1-1-0.def",
// Use the modernized UCRT on new systems. "api-ms-win-crt-locale-l1-1-0.def",
// Normally all the api-ms-win-crt-*.def files are all compiled to a "api-ms-win-crt-math-l1-1-0.def.in",
// single .lib file. But to simplify things, we're going to leave them "api-ms-win-crt-multibyte-l1-1-0.def",
// as separate files. "api-ms-win-crt-private-l1-1-0.def.in",
libs = []string{ "api-ms-win-crt-process-l1-1-0.def",
"advapi32.def.in", "api-ms-win-crt-runtime-l1-1-0.def.in",
"kernel32.def.in", "api-ms-win-crt-stdio-l1-1-0.def",
"api-ms-win-crt-conio-l1-1-0.def", "api-ms-win-crt-string-l1-1-0.def",
"api-ms-win-crt-convert-l1-1-0.def.in", "api-ms-win-crt-time-l1-1-0.def",
"api-ms-win-crt-environment-l1-1-0.def", "api-ms-win-crt-utility-l1-1-0.def",
"api-ms-win-crt-filesystem-l1-1-0.def", } {
"api-ms-win-crt-heap-l1-1-0.def",
"api-ms-win-crt-locale-l1-1-0.def",
"api-ms-win-crt-math-l1-1-0.def.in",
"api-ms-win-crt-multibyte-l1-1-0.def",
"api-ms-win-crt-private-l1-1-0.def.in",
"api-ms-win-crt-process-l1-1-0.def",
"api-ms-win-crt-runtime-l1-1-0.def.in",
"api-ms-win-crt-stdio-l1-1-0.def",
"api-ms-win-crt-string-l1-1-0.def",
"api-ms-win-crt-time-l1-1-0.def",
"api-ms-win-crt-utility-l1-1-0.def",
}
}
for _, name := range libs {
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib") outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name) inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
job := &compileJob{ job := &compileJob{
@@ -142,30 +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 "386":
archDef = "-DDEF_I386"
emulation = "i386pe"
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)
+37 -81
View File
@@ -12,46 +12,7 @@ import (
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
// Create the alltypes.h file from the appropriate alltypes.h.in files. var Musl = Library{
func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
// Create the file alltypes.h.
f, err := os.Create(filepath.Join(outputBitsDir, "alltypes.h"))
if err != nil {
return err
}
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := os.ReadFile(infile)
if err != nil {
return err
}
lines := strings.SplitSeq(string(data), "\n")
for line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
_, err := f.WriteString(line + "\n")
if err != nil {
return err
}
}
}
return f.Close()
}
var libMusl = Library{
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")
@@ -63,13 +24,41 @@ var libMusl = Library{
arch := compileopts.MuslArchitecture(target) arch := compileopts.MuslArchitecture(target)
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl") muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
err = buildMuslAllTypes(arch, muslDir, bits) // Create the file alltypes.h.
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
if err != nil { if err != nil {
return err return err
} }
infiles := []string{
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
filepath.Join(muslDir, "include", "alltypes.h.in"),
}
for _, infile := range infiles {
data, err := os.ReadFile(infile)
if err != nil {
return err
}
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
name := matches[2]
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
}
if strings.HasPrefix(line, "STRUCT ") {
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
name := matches[1]
value := matches[2]
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
}
f.WriteString(line + "\n")
}
}
f.Close()
// Create the file syscall.h. // Create the file syscall.h.
f, err := os.Create(filepath.Join(bits, "syscall.h")) f, err = os.Create(filepath.Join(bits, "syscall.h"))
if err != nil { if err != nil {
return err return err
} }
@@ -88,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
@@ -101,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),
@@ -118,56 +103,30 @@ 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, _ bool) ([]string, error) { librarySources: func(target string) []string {
arch := compileopts.MuslArchitecture(target) arch := compileopts.MuslArchitecture(target)
globs := []string{ globs := []string{
"conf/*.c",
"ctype/*.c",
"env/*.c", "env/*.c",
"errno/*.c", "errno/*.c",
"exit/*.c", "exit/*.c",
"fcntl/*.c",
"internal/defsysinfo.c", "internal/defsysinfo.c",
"internal/intscan.c",
"internal/libc.c", "internal/libc.c",
"internal/shgetc.c",
"internal/syscall_ret.c", "internal/syscall_ret.c",
"internal/vdso.c", "internal/vdso.c",
"legacy/*.c", "legacy/*.c",
"locale/*.c",
"linux/*.c",
"locale/*.c",
"malloc/*.c", "malloc/*.c",
"malloc/mallocng/*.c",
"mman/*.c", "mman/*.c",
"math/*.c", "math/*.c",
"misc/*.c",
"multibyte/*.c",
"sched/*.c",
"signal/" + arch + "/*.s",
"signal/*.c", "signal/*.c",
"stdio/*.c", "stdio/*.c",
"stdlib/*.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",
"process/*.c",
}
if arch == "arm" {
// These files need to be added to the start for some reason.
globs = append([]string{"thread/arm/*.c"}, globs...)
}
if arch != "aarch64" && arch != "mips" {
//aarch64 and mips have no architecture specific code, either they
// are not supported or don't need any?
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
} }
var sources []string var sources []string
@@ -182,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+"/", "/")
@@ -203,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 -180
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,15 +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",
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
"-nostdlibinc", "-nostdlibinc",
"-isystem", newlibDir + "/libc/include", "-isystem", newlibDir + "/libc/include",
"-I" + newlibDir + "/libc/tinystdio", "-I" + newlibDir + "/libc/tinystdio",
@@ -43,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, _ bool) ([]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",
@@ -164,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",
@@ -299,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",
@@ -317,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",
@@ -332,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",
@@ -339,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",
@@ -354,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",
} }
-9
View File
@@ -1,9 +0,0 @@
//go:build !windows
package builder
import "os"
func robustRename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
-44
View File
@@ -1,44 +0,0 @@
package builder
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const robustRenameTimeout = 2 * time.Second
func robustRename(oldpath, newpath string) error {
var bestErr error
start := time.Now()
nextSleep := time.Millisecond
for {
err := os.Rename(oldpath, newpath)
if err == nil || !isEphemeralRenameError(err) {
return err
}
if bestErr == nil {
bestErr = err
}
if d := time.Since(start) + nextSleep; d >= robustRenameTimeout {
return bestErr
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
}
func isEphemeralRenameError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.Errno(2), // ERROR_FILE_NOT_FOUND
syscall.Errno(5), // ERROR_ACCESS_DENIED
syscall.Errno(32): // ERROR_SHARING_VIOLATION
return true
}
}
return false
}
-56
View File
@@ -1,56 +0,0 @@
package builder
import (
_ "embed"
"fmt"
"html/template"
"os"
)
//go:embed size-report.html
var sizeReportBase string
func writeSizeReport(sizes *programSize, filename, pkgName string) error {
tmpl, err := template.New("report").Parse(sizeReportBase)
if err != nil {
return err
}
f, err := os.Create(filename)
if err != nil {
return fmt.Errorf("could not open report file: %w", err)
}
defer f.Close()
// Prepare data for the report.
type sizeLine struct {
Name string
Size *packageSize
}
programData := []sizeLine{}
for _, name := range sizes.sortedPackageNames() {
pkgSize := sizes.Packages[name]
programData = append(programData, sizeLine{
Name: name,
Size: pkgSize,
})
}
sizeTotal := map[string]uint64{
"code": sizes.Code,
"rodata": sizes.ROData,
"data": sizes.Data,
"bss": sizes.BSS,
"flash": sizes.Flash(),
}
// Write the report.
err = tmpl.Execute(f, map[string]any{
"pkgName": pkgName,
"sizes": programData,
"sizeTotal": sizeTotal,
})
if err != nil {
return fmt.Errorf("could not create report file: %w", err)
}
return nil
}
-109
View File
@@ -1,109 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Size Report for {{.pkgName}}</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
<style>
.table-vertical-border {
border-left: calc(var(--bs-border-width) * 2) solid currentcolor;
}
/* Hover on only the rows that are clickable. */
.row-package:hover > * {
--bs-table-color-state: var(--bs-table-hover-color);
--bs-table-bg-state: var(--bs-table-hover-bg);
}
</style>
</head>
<body>
<div class="container-xxl">
<h1>Size Report for {{.pkgName}}</h1>
<p>How much space is used by Go packages, C libraries, and other bits to set up the program environment.</p>
<ul>
<li><strong>Code</strong> is the actual program code (machine code instructions).</li>
<li><strong>Read-only data</strong> are read-only global variables. On most microcontrollers, these are stored in flash and do not take up any RAM.</li>
<li><strong>Data</strong> are writable global variables with a non-zero initializer. On microcontrollers, they are copied from flash to RAM on reset.</li>
<li><strong>BSS</strong> are writable global variables that are zero initialized. They do not take up any space in the binary, but do take up RAM. On microcontrollers, this area is zeroed on reset.</li>
</ul>
<p>The binary size consists of code, read-only data, and data. On microcontrollers, this is exactly the size of the firmware image. On other systems, there is some extra overhead: binary metadata (headers of the ELF/MachO/COFF file), debug information, exception tables, symbol names, etc. Using <code>-no-debug</code> strips most of those.</p>
<h2>Program breakdown</h2>
<p>You can click on the rows below to see which files contribute to the binary size.</p>
<div class="table-responsive">
<table class="table w-auto">
<thead>
<tr>
<th>Package</th>
<th class="table-vertical-border">Code</th>
<th>Read-only data</th>
<th>Data</th>
<th title="zero-initialized data">BSS</th>
<th class="table-vertical-border" style="min-width: 16em">Binary size</th>
</tr>
</thead>
<tbody class="table-group-divider">
{{range $i, $pkg := .sizes}}
<tr class="row-package" data-collapse=".collapse-row-{{$i}}">
<td>{{.Name}}</td>
<td class="table-vertical-border">{{.Size.Code}}</td>
<td>{{.Size.ROData}}</td>
<td>{{.Size.Data}}</td>
<td>{{.Size.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{.Size.FlashPercent}}%, var(--bs-table-bg) {{.Size.FlashPercent}}%)">
{{.Size.Flash}}
</td>
</tr>
{{range $filename, $sizes := .Size.Sub}}
<tr class="table-secondary collapse collapse-row-{{$i}}">
<td class="ps-4">
{{if eq $filename ""}}
(unknown file)
{{else}}
{{$filename}}
{{end}}
</td>
<td class="table-vertical-border">{{$sizes.Code}}</td>
<td>{{$sizes.ROData}}</td>
<td>{{$sizes.Data}}</td>
<td>{{$sizes.BSS}}</td>
<td class="table-vertical-border" style="background: linear-gradient(to right, var(--bs-info-bg-subtle) {{$sizes.FlashPercent}}%, var(--bs-table-bg) {{$sizes.FlashPercent}}%)">
{{$sizes.Flash}}
</td>
</tr>
{{end}}
{{end}}
</tbody>
<tfoot class="table-group-divider">
<tr>
<th>Total</th>
<td class="table-vertical-border">{{.sizeTotal.code}}</td>
<td>{{.sizeTotal.rodata}}</td>
<td>{{.sizeTotal.data}}</td>
<td>{{.sizeTotal.bss}}</td>
<td class="table-vertical-border">{{.sizeTotal.flash}}</td>
</tr>
</tfoot>
</table>
</div>
</div>
<script>
// Make table rows toggleable to show filenames.
for (let clickable of document.querySelectorAll('.row-package')) {
clickable.addEventListener('click', e => {
for (let row of document.querySelectorAll(clickable.dataset.collapse)) {
row.classList.toggle('show');
}
});
}
</script>
</body>
</html>
+79 -217
View File
@@ -12,7 +12,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"runtime"
"sort" "sort"
"strings" "strings"
@@ -25,7 +24,7 @@ const sizesDebug = false
// programSize contains size statistics per package of a compiled program. // programSize contains size statistics per package of a compiled program.
type programSize struct { type programSize struct {
Packages map[string]*packageSize Packages map[string]packageSize
Code uint64 Code uint64
ROData uint64 ROData uint64
Data uint64 Data uint64
@@ -53,29 +52,13 @@ func (ps *programSize) RAM() uint64 {
return ps.Data + ps.BSS return ps.Data + ps.BSS
} }
// Return the package size information for a given package path, creating it if
// it doesn't exist yet.
func (ps *programSize) getPackage(path string) *packageSize {
if field, ok := ps.Packages[path]; ok {
return field
}
field := &packageSize{
Program: ps,
Sub: map[string]*packageSize{},
}
ps.Packages[path] = field
return field
}
// packageSize contains the size of a package, calculated from the linked object // packageSize contains the size of a package, calculated from the linked object
// file. // file.
type packageSize struct { type packageSize struct {
Program *programSize Code uint64
Code uint64 ROData uint64
ROData uint64 Data uint64
Data uint64 BSS uint64
BSS uint64
Sub map[string]*packageSize
} }
// Flash usage in regular microcontrollers. // Flash usage in regular microcontrollers.
@@ -88,36 +71,10 @@ func (ps *packageSize) RAM() uint64 {
return ps.Data + ps.BSS return ps.Data + ps.BSS
} }
// Flash usage in regular microcontrollers, as a percentage of the total flash
// usage of the program.
func (ps *packageSize) FlashPercent() float64 {
return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100
}
// Add a single size data point to this package.
// This must only be called while calculating package size, not afterwards.
func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) {
if size == 0 {
return
}
// Add size for the package.
*getField(ps, isVariable) += size
// Add size for file inside package.
sub, ok := ps.Sub[filename]
if !ok {
sub = &packageSize{Program: ps.Program}
ps.Sub[filename] = sub
}
*getField(sub, isVariable) += size
}
// A mapping of a single chunk of code or data to a file path. // A mapping of a single chunk of code or data to a file path.
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
} }
@@ -129,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
@@ -161,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
@@ -236,22 +196,10 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
if !prevLineEntry.EndSequence { if !prevLineEntry.EndSequence {
// The chunk describes the code from prevLineEntry to // The chunk describes the code from prevLineEntry to
// lineEntry. // lineEntry.
path := prevLineEntry.File.Name
if runtime.GOOS == "windows" {
// Work around a Clang bug on Windows:
// https://github.com/llvm/llvm-project/issues/117317
path = strings.ReplaceAll(path, "\\\\", "\\")
// wasi-libc likes to use forward slashes, but we
// canonicalize everything to use backwards slashes as
// is common on Windows.
path = strings.ReplaceAll(path, "/", "\\")
}
line := addressLine{ 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: path,
} }
if line.Length != 0 { if line.Length != 0 {
addresses = append(addresses, line) addresses = append(addresses, line)
@@ -275,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)
@@ -288,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,
}) })
@@ -308,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) {
@@ -375,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
} }
@@ -432,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.
@@ -474,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,
@@ -490,7 +391,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
continue continue
} }
if section.Type == elf.SHT_NOBITS { if section.Type == elf.SHT_NOBITS {
if strings.HasPrefix(section.Name, ".stack") { if section.Name == ".stack" {
// TinyGo emits stack sections on microcontroller using the // TinyGo emits stack sections on microcontroller using the
// ".stack" name. // ".stack" name.
// This is a bit ugly, but I don't think there is a way to // This is a bit ugly, but I don't think there is a way to
@@ -498,30 +399,21 @@ 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 if section.Flags&elf.SHF_WRITE != 0 { } else {
// Regular .bss section. Zero-initialized RAM is always // Regular .bss section.
// writable, so require SHF_WRITE here.
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,
}) })
} }
// Other (non-writable) SHT_NOBITS sections are address-space
// placeholders that occupy no RAM, such as the ESP linker
// script's .irom_dummy / .rodata_dummy sections which reserve
// the flash-mapped XIP virtual address ranges. They must not be
// counted as bss/RAM usage.
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 { } else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
// .text // .text
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 {
@@ -529,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 {
@@ -537,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,
}) })
} }
@@ -564,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
@@ -572,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)
@@ -580,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 {
@@ -588,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,
}) })
} }
@@ -672,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.
@@ -744,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.
@@ -832,40 +718,49 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
// Now finally determine the binary/RAM size usage per package by going // Now finally determine the binary/RAM size usage per package by going
// through each allocated section. // through each allocated section.
sizes := make(map[string]*packageSize) sizes := make(map[string]packageSize)
program := &programSize{
Packages: sizes,
}
for _, section := range sections { for _, section := range sections {
switch section.Type { switch section.Type {
case memoryCode: case memoryCode:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { readSection(section, addresses, func(path string, size uint64, isVariable bool) {
field := sizes[path]
if isVariable { if isVariable {
return &ps.ROData field.ROData += size
} else {
field.Code += size
} }
return &ps.Code sizes[path] = field
}, packagePathMap) }, packagePathMap)
case memoryROData: case memoryROData:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { readSection(section, addresses, func(path string, size uint64, isVariable bool) {
return &ps.ROData field := sizes[path]
field.ROData += size
sizes[path] = field
}, packagePathMap) }, packagePathMap)
case memoryData: case memoryData:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { readSection(section, addresses, func(path string, size uint64, isVariable bool) {
return &ps.Data field := sizes[path]
field.Data += size
sizes[path] = field
}, packagePathMap) }, packagePathMap)
case memoryBSS: case memoryBSS:
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 { readSection(section, addresses, func(path string, size uint64, isVariable bool) {
return &ps.BSS field := sizes[path]
field.BSS += size
sizes[path] = field
}, packagePathMap) }, packagePathMap)
case memoryStack: case memoryStack:
// We store the C stack as a pseudo-package. // We store the C stack as a pseudo-package.
program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 { sizes["C stack"] = packageSize{
return &ps.BSS BSS: section.Size,
}, "", section.Size, false) }
} }
} }
// ...and summarize the results. // ...and summarize the results.
program := &programSize{
Packages: sizes,
}
for _, pkg := range sizes { for _, pkg := range sizes {
program.Code += pkg.Code program.Code += pkg.Code
program.ROData += pkg.ROData program.ROData += pkg.ROData
@@ -876,8 +771,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
} }
// readSection determines for each byte in this section to which package it // readSection determines for each byte in this section to which package it
// belongs. // belongs. It reports this usage through the addSize callback.
func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) { func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
// The addr variable tracks at which address we are while going through this // The addr variable tracks at which address we are while going through this
// section. We start at the beginning. // section. We start at the beginning.
addr := section.Address addr := section.Address
@@ -895,18 +790,10 @@ func readSection(section memorySection, addresses []addressLine, program *progra
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.
program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true)
} else {
program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
}
} }
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.
@@ -923,58 +810,35 @@ func readSection(section memorySection, addresses []addressLine, program *progra
length = line.Length - (addr - line.Address) length = line.Length - (addr - line.Address)
} }
// Finally, mark this chunk of memory as used by the given package. // Finally, mark this chunk of memory as used by the given package.
packagePath, filename := findPackagePath(line.File, packagePathMap) addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable)
addr = line.Address + line.Length addr = line.Address + line.Length
} }
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.
program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true)
} else {
program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false)
if sizesDebug {
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
}
} }
} }
} }
// findPackagePath returns the Go package (or a pseudo package) for the given // findPackagePath returns the Go package (or a pseudo package) for the given
// path. It uses some heuristics, for example for some C libraries. // path. It uses some heuristics, for example for some C libraries.
func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) { func findPackagePath(path string, packagePathMap map[string]string) string {
// Check whether this path is part of one of the compiled packages. // Check whether this path is part of one of the compiled packages.
packagePath, ok := packagePathMap[filepath.Dir(path)] packagePath, ok := packagePathMap[filepath.Dir(path)]
if ok { if !ok {
// Directory is known as a Go package.
// Add the file itself as well.
filename = filepath.Base(path)
} else {
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) { if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
// Emit C libraries (in the lib subdirectory of TinyGo) as a single // Emit C libraries (in the lib subdirectory of TinyGo) as a single
// package, with a "C" prefix. For example: "C picolibc" for the // package, with a "C" prefix. For example: "C compiler-rt" for the
// baremetal libc. // compiler runtime library from LLVM.
libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator)) packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
parts := strings.SplitN(libPath, string(os.PathSeparator), 2)
packagePath = "C " + parts[0]
if len(parts) > 1 {
filename = parts[1]
} else {
filename = parts[0]
}
} else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) {
packagePath = "C compiler-rt"
filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator))
} 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"
@@ -990,11 +854,9 @@ func findPackagePath(path string, packagePathMap map[string]string) (packagePath
// fixed in the compiler. // fixed in the compiler.
packagePath = "-" packagePath = "-"
} else { } else {
// This is some other path. Not sure what it is, so just emit its // This is some other path. Not sure what it is, so just emit its directory.
// directory as a fallback. packagePath = filepath.Dir(path) // fallback
packagePath = filepath.Dir(path)
filename = filepath.Base(path)
} }
} }
return return packagePath
} }
-138
View File
@@ -1,138 +0,0 @@
package builder
import (
"regexp"
"runtime"
"testing"
"time"
"github.com/tinygo-org/tinygo/compileopts"
)
var sema = make(chan struct{}, runtime.NumCPU())
type sizeTest struct {
target string
path string
codeSize uint64
rodataSize uint64
dataSize uint64
bssSize uint64
}
// Test whether code and data size is as expected for the given targets.
// This tests both the logic of loadProgramSize and checks that code size
// doesn't change unintentionally.
//
// If you find that code or data size is reduced, then great! You can reduce the
// number in this test.
// If you find that the code or data size is increased, take a look as to why
// this is. It could be due to an update (LLVM version, Go version, etc) which
// is fine, but it could also mean that a recent change introduced this size
// increase. If so, please consider whether this new feature is indeed worth the
// size increase for all users.
func TestBinarySize(t *testing.T) {
if runtime.GOOS == "linux" && !hasBuiltinTools {
// Debian LLVM packages are modified a bit and tend to produce
// different machine code. Ideally we'd fix this (with some attributes
// or something?), but for now skip it.
t.Skip("Skip: using external LLVM version so binary size might differ")
}
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4313, 323, 0, 2260},
{"microbit", "examples/serial", 2838, 382, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488},
// 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 {
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, tc.target, tc.path)
// Check whether the size of the binary matches the expected size.
sizes, err := loadProgramSize(result.Executable, nil)
if err != nil {
t.Fatal("could not read program size:", err)
}
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
t.Errorf(" code rodata data bss")
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
}
})
}
}
// Check that the -size=full flag attributes binary size to the correct package
// without filesystem paths and things like that.
func TestSizeFull(t *testing.T) {
tests := []string{
"microbit",
"wasip1",
}
libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector"
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests {
t.Run(target, func(t *testing.T) {
t.Parallel()
// Build the binary.
result := buildBinary(t, target, "examples/serial")
// Check whether the binary doesn't contain any unexpected package
// names.
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
if err != nil {
t.Fatal("could not read program size:", err)
}
for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" {
// TODO: correctly attribute all unknown binary size.
continue
}
if libMatch.MatchString(pkg) {
continue
}
if pkgMatch.MatchString(pkg) {
continue
}
t.Error("unexpected package name in size output:", pkg)
}
})
}
}
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
options := compileopts.Options{
Target: targetString,
Opt: "z",
Semaphore: sema,
InterpTimeout: 60 * time.Second,
Debug: true,
VerifyIR: true,
}
target, err := compileopts.LoadTarget(&options)
if err != nil {
t.Fatal("could not load target:", err)
}
config := &compileopts.Config{
Options: &options,
Target: target,
}
result, err := Build(pkgName, "", t.TempDir(), config)
if err != nil {
t.Fatal("could not build:", err)
}
return result
}
+29 -5
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,12 +27,21 @@ 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)))
defer C.free(buf) defer C.free(buf)
cflags := unsafe.Slice((**C.char)(buf), len(args)) cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
for i, flag := range args { for i, flag := range args {
cflag := C.CString(flag) cflag := C.CString(flag)
cflags[i] = cflag cflags[i] = 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
+27 -174
View File
@@ -1,197 +1,50 @@
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 {
// Find the right command to run Clang.
var cmd *exec.Cmd
if hasBuiltinTools { if hasBuiltinTools {
// Compile this with the internal Clang compiler. // Compile this with the internal Clang compiler.
cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...) headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
} else { if headerPath == "" {
// Compile this with an external invocation of the Clang compiler. return errors.New("could not locate Clang headers")
name, err := LookupCommand("clang")
if err != nil {
return err
} }
cmd = exec.Command(name, flags...) flags = append(flags, "-I"+headerPath)
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} }
cmd.Stdout = os.Stdout // Compile this with an external invocation of the Clang compiler.
cmd.Stderr = os.Stderr return execCommand("clang", flags...)
// Make sure the command doesn't use any environmental variables.
// Most importantly, it should not use C_INCLUDE_PATH and the like.
cmd.Env = []string{}
// Let some environment variables through. One important one is the
// temporary directory, especially on Windows it looks like Clang breaks if
// the temporary directory has not been set.
// See: https://github.com/tinygo-org/tinygo/issues/4557
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
for _, env := range os.Environ() {
// We could parse the key and look it up in a map, but since there are
// only a few keys iterating through them is easier and maybe even
// faster.
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
if strings.HasPrefix(env, prefix) {
cmd.Env = append(cmd.Env, env)
break
}
}
}
return cmd.Run()
} }
// 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(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[2]
for line := range strings.SplitSeq(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil {
parsedError = true
line, _ := strconv.Atoi(matches[3])
msg := "linker could not find symbol " + symbolName
switch symbolName {
case "runtime.alloc":
msg = "object allocated on the heap with -gc=none"
case "runtime.alloc_noheap":
msg = "object allocated on the heap in //go:noheap function"
}
linkErrors = append(linkErrors, scanner.Error{
Pos: token.Position{
Filename: matches[2],
Line: line,
},
Msg: msg,
})
}
}
}
// Check for flash/RAM overflow.
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: section '(.*?)' will not fit in region '(.*?)': overflowed by ([0-9]+) bytes$`).FindStringSubmatch(message); matches != nil {
region := matches[3]
n, err := strconv.ParseUint(matches[4], 10, 64)
if err != nil {
// Should not happen at all (unless it overflows an uint64 for some reason).
continue
}
// Check which area overflowed.
// Some chips use differently named memory areas, but these are by
// far the most common.
switch region {
case "FLASH_TEXT":
if n > flashOverflow {
flashOverflow = n
}
parsedError = true
case "RAM":
if n > ramOverflow {
ramOverflow = n
}
parsedError = true
}
}
// If we couldn't parse the linker error: show the error as-is to
// the user.
if !parsedError {
linkErrors = append(linkErrors, LinkerError{message})
}
}
if flashOverflow > 0 {
linkErrors = append(linkErrors, LinkerError{
Msg: fmt.Sprintf("program too large for this chip (flash overflowed by %d bytes)\n\toptimization guide: https://tinygo.org/docs/guides/optimizing-binaries/", flashOverflow),
})
}
if ramOverflow > 0 {
linkErrors = append(linkErrors, LinkerError{
Msg: fmt.Sprintf("program uses too much static RAM on this chip (RAM overflowed by %d bytes)", ramOverflow),
})
}
return newMultiError(linkErrors, "")
}
// LLD linker error that could not be parsed or doesn't refer to a source
// location.
type LinkerError struct {
Msg string
}
func (e LinkerError) Error() string {
return e.Msg
} }
+1 -1
View File
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
} }
bl.SetNumBlocks(len(blocks)) bl.SetNumBlocks(len(blocks))
for i := range blocks { for i := 0; i < len(blocks); i++ {
bl.SetBlockNo(i) bl.SetBlockNo(i)
bl.SetData(blocks[i]) bl.SetData(blocks[i])
-284
View File
@@ -1,284 +0,0 @@
package builder
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/tinygo-org/tinygo/goenv"
)
var libWasiLibc = Library{
name: "wasi-libc",
makeHeaders: func(target, includeDir string) error {
bits := filepath.Join(includeDir, "bits")
err := os.Mkdir(bits, 0777)
if err != nil {
return err
}
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "wasi-libc/libc-top-half/musl")
err = buildMuslAllTypes("wasm32", muslDir, bits)
if err != nil {
return err
}
// See MUSL_OMIT_HEADERS in the Makefile.
omitHeaders := map[string]struct{}{
"syslog.h": {},
"wait.h": {},
"ucontext.h": {},
"paths.h": {},
"utmp.h": {},
"utmpx.h": {},
"lastlog.h": {},
"elf.h": {},
"link.h": {},
"pwd.h": {},
"shadow.h": {},
"grp.h": {},
"mntent.h": {},
"netdb.h": {},
"resolv.h": {},
"pty.h": {},
"dlfcn.h": {},
"setjmp.h": {},
"ulimit.h": {},
"wordexp.h": {},
"spawn.h": {},
"termios.h": {},
"libintl.h": {},
"aio.h": {},
"stdarg.h": {},
"stddef.h": {},
"pthread.h": {},
}
for _, glob := range [][2]string{
{"libc-bottom-half/headers/public/*.h", ""},
{"libc-bottom-half/headers/public/wasi/*.h", "wasi"},
{"libc-top-half/musl/arch/wasm32/bits/*.h", "bits"},
{"libc-top-half/musl/include/*.h", ""},
{"libc-top-half/musl/include/netinet/*.h", "netinet"},
{"libc-top-half/musl/include/sys/*.h", "sys"},
} {
matches, _ := filepath.Glob(filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc", glob[0]))
outDir := filepath.Join(includeDir, glob[1])
os.MkdirAll(outDir, 0o777)
for _, match := range matches {
name := filepath.Base(match)
if _, ok := omitHeaders[name]; ok {
continue
}
data, err := os.ReadFile(match)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(outDir, name), data, 0o666)
if err != nil {
return err
}
}
}
return nil
},
cflags: func(target, headerPath string) []string {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", "-msign-ext", "-mbulk-memory",
"-Wno-null-pointer-arithmetic", "-Wno-unused-parameter", "-Wno-sign-compare", "-Wno-unused-variable", "-Wno-unused-function", "-Wno-ignored-attributes", "-Wno-missing-braces", "-Wno-ignored-pragmas", "-Wno-unused-but-set-variable", "-Wno-unknown-warning-option",
"-Wno-parentheses", "-Wno-shift-op-parentheses", "-Wno-bitwise-op-parentheses", "-Wno-logical-op-parentheses", "-Wno-string-plus-int", "-Wno-dangling-else", "-Wno-unknown-pragmas",
"-DNDEBUG",
"-D__wasilibc_printscan_no_long_double",
"-D__wasilibc_printscan_full_support_option=\"long double support is disabled\"",
"-DBULK_MEMORY_THRESHOLD=32", // default threshold in wasi-libc
"-isystem", headerPath,
"-I" + libcDir + "/libc-top-half/musl/src/include",
"-I" + libcDir + "/libc-top-half/musl/src/internal",
"-I" + libcDir + "/libc-top-half/musl/arch/wasm32",
"-I" + libcDir + "/libc-top-half/musl/arch/generic",
"-I" + libcDir + "/libc-top-half/headers/private",
}
},
cflagsForFile: func(path string) []string {
if strings.HasPrefix(path, "libc-bottom-half"+string(os.PathSeparator)) {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-I" + libcDir + "/libc-bottom-half/headers/private",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src/include",
"-I" + libcDir + "/libc-bottom-half/cloudlibc/src",
}
}
return nil
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string, libcNeedsMalloc bool) ([]string, error) {
type filePattern struct {
glob string
exclude []string
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
globs := []filePattern{
// Top half: mostly musl sources.
{glob: "libc-top-half/sources/*.c"},
{glob: "libc-top-half/musl/src/conf/*.c"},
{glob: "libc-top-half/musl/src/internal/*.c", exclude: []string{
"procfdname.c", "syscall.c", "syscall_ret.c", "vdso.c", "version.c",
}},
{glob: "libc-top-half/musl/src/locale/*.c", exclude: []string{
"dcngettext.c", "textdomain.c", "bind_textdomain_codeset.c"}},
{glob: "libc-top-half/musl/src/math/*.c", exclude: []string{
"__signbit.c", "__signbitf.c", "__signbitl.c",
"__fpclassify.c", "__fpclassifyf.c", "__fpclassifyl.c",
"ceilf.c", "ceil.c",
"floorf.c", "floor.c",
"truncf.c", "trunc.c",
"rintf.c", "rint.c",
"nearbyintf.c", "nearbyint.c",
"sqrtf.c", "sqrt.c",
"fabsf.c", "fabs.c",
"copysignf.c", "copysign.c",
"fminf.c", "fmaxf.c",
"fmin.c", "fmax.c,",
}},
{glob: "libc-top-half/musl/src/multibyte/*.c"},
{glob: "libc-top-half/musl/src/stdio/*.c", exclude: []string{
"vfwscanf.c", "vfwprintf.c", // long double is unsupported
"__lockfile.c", "flockfile.c", "funlockfile.c", "ftrylockfile.c",
"rename.c",
"tmpnam.c", "tmpfile.c", "tempnam.c",
"popen.c", "pclose.c",
"remove.c",
"gets.c"}},
{glob: "libc-top-half/musl/src/stdlib/*.c"},
{glob: "libc-top-half/musl/src/string/*.c", exclude: []string{
"strsignal.c"}},
// Bottom half: connect top half to WASI equivalents.
{glob: "libc-bottom-half/cloudlibc/src/libc/*/*.c"},
{glob: "libc-bottom-half/cloudlibc/src/libc/sys/*/*.c"},
{glob: "libc-bottom-half/sources/*.c"},
}
// We're using the Boehm GC, so we need a heap implementation in the libc.
if libcNeedsMalloc {
globs = append(globs, filePattern{glob: "dlmalloc/src/dlmalloc.c"})
}
// See: LIBC_TOP_HALF_MUSL_SOURCES in the Makefile
sources := []string{
"libc-top-half/musl/src/misc/a64l.c",
"libc-top-half/musl/src/misc/basename.c",
"libc-top-half/musl/src/misc/dirname.c",
"libc-top-half/musl/src/misc/ffs.c",
"libc-top-half/musl/src/misc/ffsl.c",
"libc-top-half/musl/src/misc/ffsll.c",
"libc-top-half/musl/src/misc/fmtmsg.c",
"libc-top-half/musl/src/misc/getdomainname.c",
"libc-top-half/musl/src/misc/gethostid.c",
"libc-top-half/musl/src/misc/getopt.c",
"libc-top-half/musl/src/misc/getopt_long.c",
"libc-top-half/musl/src/misc/getsubopt.c",
"libc-top-half/musl/src/misc/uname.c",
"libc-top-half/musl/src/misc/nftw.c",
"libc-top-half/musl/src/errno/strerror.c",
"libc-top-half/musl/src/network/htonl.c",
"libc-top-half/musl/src/network/htons.c",
"libc-top-half/musl/src/network/ntohl.c",
"libc-top-half/musl/src/network/ntohs.c",
"libc-top-half/musl/src/network/inet_ntop.c",
"libc-top-half/musl/src/network/inet_pton.c",
"libc-top-half/musl/src/network/inet_aton.c",
"libc-top-half/musl/src/network/in6addr_any.c",
"libc-top-half/musl/src/network/in6addr_loopback.c",
"libc-top-half/musl/src/fenv/fenv.c",
"libc-top-half/musl/src/fenv/fesetround.c",
"libc-top-half/musl/src/fenv/feupdateenv.c",
"libc-top-half/musl/src/fenv/fesetexceptflag.c",
"libc-top-half/musl/src/fenv/fegetexceptflag.c",
"libc-top-half/musl/src/fenv/feholdexcept.c",
"libc-top-half/musl/src/exit/exit.c",
"libc-top-half/musl/src/exit/atexit.c",
"libc-top-half/musl/src/exit/assert.c",
"libc-top-half/musl/src/exit/quick_exit.c",
"libc-top-half/musl/src/exit/at_quick_exit.c",
"libc-top-half/musl/src/time/strftime.c",
"libc-top-half/musl/src/time/asctime.c",
"libc-top-half/musl/src/time/asctime_r.c",
"libc-top-half/musl/src/time/ctime.c",
"libc-top-half/musl/src/time/ctime_r.c",
"libc-top-half/musl/src/time/wcsftime.c",
"libc-top-half/musl/src/time/strptime.c",
"libc-top-half/musl/src/time/difftime.c",
"libc-top-half/musl/src/time/timegm.c",
"libc-top-half/musl/src/time/ftime.c",
"libc-top-half/musl/src/time/gmtime.c",
"libc-top-half/musl/src/time/gmtime_r.c",
"libc-top-half/musl/src/time/timespec_get.c",
"libc-top-half/musl/src/time/getdate.c",
"libc-top-half/musl/src/time/localtime.c",
"libc-top-half/musl/src/time/localtime_r.c",
"libc-top-half/musl/src/time/mktime.c",
"libc-top-half/musl/src/time/__tm_to_secs.c",
"libc-top-half/musl/src/time/__month_to_secs.c",
"libc-top-half/musl/src/time/__secs_to_tm.c",
"libc-top-half/musl/src/time/__year_to_secs.c",
"libc-top-half/musl/src/time/__tz.c",
"libc-top-half/musl/src/fcntl/creat.c",
"libc-top-half/musl/src/dirent/alphasort.c",
"libc-top-half/musl/src/dirent/versionsort.c",
"libc-top-half/musl/src/env/__stack_chk_fail.c",
"libc-top-half/musl/src/env/clearenv.c",
"libc-top-half/musl/src/env/getenv.c",
"libc-top-half/musl/src/env/putenv.c",
"libc-top-half/musl/src/env/setenv.c",
"libc-top-half/musl/src/env/unsetenv.c",
"libc-top-half/musl/src/unistd/posix_close.c",
"libc-top-half/musl/src/stat/futimesat.c",
"libc-top-half/musl/src/legacy/getpagesize.c",
"libc-top-half/musl/src/thread/thrd_sleep.c",
}
basepath := goenv.Get("TINYGOROOT") + "/lib/wasi-libc/"
for _, pattern := range globs {
matches, err := filepath.Glob(basepath + pattern.glob)
if err != nil {
// From the documentation:
// > Glob ignores file system errors such as I/O errors reading
// > directories. The only possible returned error is
// > ErrBadPattern, when pattern is malformed.
// So the only possible error is when the (statically defined)
// pattern is wrong. In other words, a programming bug.
return nil, fmt.Errorf("wasi-libc: could not glob source dirs: %w", err)
}
if len(matches) == 0 {
return nil, fmt.Errorf("wasi-libc: did not find any files for pattern %#v", pattern)
}
excludeSet := map[string]struct{}{}
for _, exclude := range pattern.exclude {
excludeSet[exclude] = struct{}{}
}
for _, match := range matches {
if _, ok := excludeSet[filepath.Base(match)]; ok {
continue
}
relpath, err := filepath.Rel(basepath, match)
if err != nil {
// Not sure if this is even possible.
return nil, err
}
sources = append(sources, relpath)
}
}
return sources, nil
},
}
-83
View File
@@ -1,83 +0,0 @@
package builder
import (
"os"
"path/filepath"
"github.com/tinygo-org/tinygo/goenv"
)
var libWasmBuiltins = Library{
name: "wasmbuiltins",
makeHeaders: func(target, includeDir string) error {
if err := os.Mkdir(includeDir+"/bits", 0o777); err != nil {
return err
}
f, err := os.Create(includeDir + "/bits/alltypes.h")
if err != nil {
return err
}
if _, err := f.Write([]byte(wasmAllTypes)); err != nil {
return err
}
return f.Close()
},
cflags: func(target, headerPath string) []string {
libcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc")
return []string{
"-Werror",
"-Wall",
"-std=gnu11",
"-nostdlibinc",
"-mnontrapping-fptoint", // match wasm-unknown (default on in LLVM 20)
"-mno-bulk-memory", // same here
"-isystem", libcDir + "/libc-top-half/musl/arch/wasm32",
"-isystem", libcDir + "/libc-top-half/musl/arch/generic",
"-isystem", libcDir + "/libc-top-half/musl/src/internal",
"-isystem", libcDir + "/libc-top-half/musl/src/include",
"-isystem", libcDir + "/libc-top-half/musl/include",
"-isystem", libcDir + "/libc-bottom-half/headers/public",
"-I" + headerPath,
}
},
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/wasi-libc") },
librarySources: func(target string, _ bool) ([]string, error) {
return []string{
// memory builtins needed for llvm.memcpy.*, llvm.memmove.*, and
// llvm.memset.* LLVM intrinsics.
"libc-top-half/musl/src/string/memcpy.c",
"libc-top-half/musl/src/string/memmove.c",
"libc-top-half/musl/src/string/memset.c",
// exp, exp2, and log are needed for LLVM math builtin functions
// like llvm.exp.*.
"libc-top-half/musl/src/math/__math_divzero.c",
"libc-top-half/musl/src/math/__math_invalid.c",
"libc-top-half/musl/src/math/__math_oflow.c",
"libc-top-half/musl/src/math/__math_uflow.c",
"libc-top-half/musl/src/math/__math_xflow.c",
"libc-top-half/musl/src/math/exp.c",
"libc-top-half/musl/src/math/exp_data.c",
"libc-top-half/musl/src/math/exp2.c",
"libc-top-half/musl/src/math/log.c",
"libc-top-half/musl/src/math/log_data.c",
}, nil
},
}
// alltypes.h for wasm-libc, using the types as defined inside Clang.
const wasmAllTypes = `
typedef __SIZE_TYPE__ size_t;
typedef __INT8_TYPE__ int8_t;
typedef __INT16_TYPE__ int16_t;
typedef __INT32_TYPE__ int32_t;
typedef __INT64_TYPE__ int64_t;
typedef __UINT8_TYPE__ uint8_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT64_TYPE__ uint64_t;
typedef __UINTPTR_TYPE__ uintptr_t;
// This type is used internally in wasi-libc.
typedef double double_t;
`
+82 -234
View File
@@ -18,7 +18,6 @@ import (
"go/scanner" "go/scanner"
"go/token" "go/token"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
@@ -29,8 +28,6 @@ import (
// 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
@@ -39,8 +36,7 @@ type cgoPackage struct {
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
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines anonDecls map[interface{}]string
anonDecls map[any]string
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
@@ -78,28 +74,18 @@ type bitfieldInfo struct {
endBit int64 // may be 0 meaning "until the end of the field" endBit int64 // may be 0 meaning "until the end of the field"
} }
// Information about a #cgo noescape line in the source code.
type noescapingFunc struct {
name string
pos token.Pos
used bool // true if used somewhere in the source (for proper error reporting)
}
// cgoAliases list type aliases between Go and C, for types that are equivalent // cgoAliases list type aliases between Go and C, for types that are equivalent
// in both languages. See addTypeAliases. // in both languages. See addTypeAliases.
var cgoAliases = map[string]string{ var cgoAliases = map[string]string{
"_Cgo_int8_t": "int8", "C.int8_t": "int8",
"_Cgo_int16_t": "int16", "C.int16_t": "int16",
"_Cgo_int32_t": "int32", "C.int32_t": "int32",
"_Cgo_int64_t": "int64", "C.int64_t": "int64",
"_Cgo_uint8_t": "uint8", "C.uint8_t": "uint8",
"_Cgo_uint16_t": "uint16", "C.uint16_t": "uint16",
"_Cgo_uint32_t": "uint32", "C.uint32_t": "uint32",
"_Cgo_uint64_t": "uint64", "C.uint64_t": "uint64",
"_Cgo_uintptr_t": "uintptr", "C.uintptr_t": "uintptr",
"_Cgo_float": "float32",
"_Cgo_double": "float64",
"_Cgo__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
@@ -141,105 +127,31 @@ typedef unsigned long long _Cgo_ulonglong;
// The string/bytes functions below implement C.CString etc. To make sure the // The string/bytes functions below implement C.CString etc. To make sure the
// runtime doesn't need to know the C int type, lengths are converted to uintptr // runtime doesn't need to know the C int type, lengths are converted to uintptr
// first. // first.
const generatedGoFilePrefixBase = ` // These functions will be modified to get a "C." prefix, so the source below
import "syscall" // doesn't reflect the final AST.
const generatedGoFilePrefix = `
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func __GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func __GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
`
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
}
`
// Windows uses fake errno values in the syscall package.
// See for example: https://github.com/golang/go/issues/23468
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
// syscall package is the standard library one we can't change it, but we can
// map the errno values to match the values in the syscall package.
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
var _Cgo___errno_mapping = [...]syscall.Errno{
1: syscall.EPERM,
2: syscall.ENOENT,
3: syscall.ESRCH,
4: syscall.EINTR,
5: syscall.EIO,
6: syscall.ENXIO,
7: syscall.E2BIG,
8: syscall.ENOEXEC,
9: syscall.EBADF,
10: syscall.ECHILD,
11: syscall.EAGAIN,
12: syscall.ENOMEM,
13: syscall.EACCES,
14: syscall.EFAULT,
16: syscall.EBUSY,
17: syscall.EEXIST,
18: syscall.EXDEV,
19: syscall.ENODEV,
20: syscall.ENOTDIR,
21: syscall.EISDIR,
22: syscall.EINVAL,
23: syscall.ENFILE,
24: syscall.EMFILE,
25: syscall.ENOTTY,
27: syscall.EFBIG,
28: syscall.ENOSPC,
29: syscall.ESPIPE,
30: syscall.EROFS,
31: syscall.EMLINK,
32: syscall.EPIPE,
33: syscall.EDOM,
34: syscall.ERANGE,
36: syscall.EDEADLK,
38: syscall.ENAMETOOLONG,
39: syscall.ENOLCK,
40: syscall.ENOSYS,
41: syscall.ENOTEMPTY,
42: syscall.EILSEQ,
}
func _Cgo___get_errno() error {
num := _Cgo___get_errno_num()
if num < uintptr(len(_Cgo___errno_mapping)) {
if mapped := _Cgo___errno_mapping[num]; mapped != 0 {
return mapped
}
}
return syscall.Errno(num)
} }
` `
@@ -250,16 +162,14 @@ func _Cgo___get_errno() error {
// 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, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) { func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, clangHeaders string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
p := &cgoPackage{ p := &cgoPackage{
packageName: files[0].Name.Name,
currentDir: dir, currentDir: dir,
importPath: importPath, 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{},
noescapingFuncs: map[string]*noescapingFunc{}, anonDecls: map[interface{}]string{},
anonDecls: map[any]string{},
visitedFiles: map[string][]byte{}, visitedFiles: map[string][]byte{},
} }
@@ -283,26 +193,37 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Construct a new in-memory AST for CGo declarations of this package. // Construct a new in-memory AST for CGo declarations of this package.
// The first part is written as Go code that is then parsed, but more code // The first part is written as Go code that is then parsed, but more code
// is added later to the AST to declare functions, globals, etc. // is added later to the AST to declare functions, globals, etc.
goCode := "package " + files[0].Name.Name + "\n\n" goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
if goos == "windows" {
goCode += generatedGoFilePrefixWindows
} else {
goCode += generatedGoFilePrefixOther
}
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments) p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
if err != nil { if err != nil {
// 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
// Adjust some of the functions in there.
for _, decl := range p.generated.Decls {
switch decl := decl.(type) {
case *ast.FuncDecl:
switch decl.Name.Name {
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
// Adjust the name to have a "C." prefix so it is correctly
// resolved.
decl.Name.Name = "C." + decl.Name.Name
}
}
}
// Patch some types, for example *C.char in C.CString.
cf := p.newCGoFile(nil, -1) // dummy *cgoFile for the walker
astutil.Apply(p.generated, func(cursor *astutil.Cursor) bool {
return cf.walker(cursor, nil)
}, nil)
// Find `import "C"` C fragments in the file. // Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files { for i, f := range files {
var cgoHeader strings.Builder var cgoHeader string
for i := 0; i < len(f.Decls); i++ { for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i] decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
@@ -337,8 +258,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Iterate through all parts of the CGo header. Note that every // // Iterate through all parts of the CGo header. Note that every //
// line is a new comment. // line is a new comment.
position := fset.Position(genDecl.Doc.Pos()) position := fset.Position(genDecl.Doc.Pos())
var fragment strings.Builder fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
for _, comment := range genDecl.Doc.List { for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and // Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations). // replace the lines with spaces (to preserve locations).
@@ -355,13 +275,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} else { // comment } else { // comment
c = " " + c[2:len(c)-2] c = " " + c[2:len(c)-2]
} }
fragment.WriteString(c) fragment += c + "\n"
fragment.WriteByte('\n')
} }
cgoHeader.WriteString(fragment.String()) cgoHeader += fragment
} }
p.cgoHeaders[i] = cgoHeader.String() p.cgoHeaders[i] = cgoHeader
} }
// Define CFlags that will be used while parsing the package. // Define CFlags that will be used while parsing the package.
@@ -370,6 +289,9 @@ 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(nil, -1).readNames(builtinAliasTypedefs, cflagsForCGo, "", func(names map[string]clangCursor) {
@@ -378,7 +300,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
Tok: token.TYPE, Tok: token.TYPE,
} }
for _, name := range builtinAliases { for _, name := range builtinAliases {
typeSpec := p.getIntegerType("_Cgo_"+name, names["_Cgo_"+name]) typeSpec := p.getIntegerType("C."+name, names["_Cgo_"+name])
gen.Specs = append(gen.Specs, typeSpec) gen.Specs = append(gen.Specs, typeSpec)
} }
p.generated.Decls = append(p.generated.Decls, gen) p.generated.Decls = append(p.generated.Decls, gen)
@@ -387,13 +309,6 @@ 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(f, i)
// These types are aliased with the corresponding types in C. For
// 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) { 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.
@@ -407,26 +322,10 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
}) })
} }
// Show an error when a #cgo noescape line isn't used in practice.
// This matches upstream Go. I think the goal is to avoid issues with
// misspelled function names, which seems very useful.
var unusedNoescapeLines []*noescapingFunc
for _, value := range p.noescapingFuncs {
if !value.used {
unusedNoescapeLines = append(unusedNoescapeLines, value)
}
}
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
})
for _, value := range unusedNoescapeLines {
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
}
// Print the newly generated in-memory AST, for debugging. // 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, p.cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
} }
func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile { func (p *cgoPackage) newCGoFile(file *ast.File, index int) *cgoFile {
@@ -488,33 +387,6 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
} }
text = text[:lineStart] + string(spaces) + text[lineEnd:] text = text[:lineStart] + string(spaces) + text[lineEnd:]
allFields := strings.Fields(line[4:])
switch allFields[0] {
case "noescape":
// The code indicates that pointer parameters will not be captured
// by the called C function.
if len(allFields) < 2 {
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
continue
}
if len(allFields) > 2 {
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
continue
}
name := allFields[1]
p.noescapingFuncs[name] = &noescapingFunc{
name: name,
pos: pos,
used: false,
}
continue
case "nocallback":
// We don't do anything special when calling a C function, so there
// appears to be no optimization that we can do here.
// Accept, but ignore the parameter for compatibility.
continue
}
// Get the text before the colon in the #cgo directive. // Get the text before the colon in the #cgo directive.
colon := strings.IndexByte(line, ':') colon := strings.IndexByte(line, ':')
if colon < 0 { if colon < 0 {
@@ -652,6 +524,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{ X: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "union", Name: "union",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: pos, NamePos: pos,
@@ -705,6 +578,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{ X: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -760,6 +634,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -806,6 +681,11 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
}, },
}, },
Type: &ast.StarExpr{ Type: &ast.StarExpr{
@@ -813,6 +693,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -870,6 +751,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -952,6 +834,11 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
}, },
}, },
Type: &ast.StarExpr{ Type: &ast.StarExpr{
@@ -959,6 +846,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -979,6 +867,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "value", Name: "value",
Obj: nil,
}, },
}, },
Type: bitfield.field.Type, Type: bitfield.field.Type,
@@ -996,6 +885,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -1058,9 +948,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
} }
@@ -1219,7 +1106,7 @@ func getPos(node ast.Node) token.Pos {
// getUnnamedDeclName creates a name (with the given prefix) for the given C // getUnnamedDeclName creates a name (with the given prefix) for the given C
// declaration. This is used for structs, unions, and enums that are often // declaration. This is used for structs, unions, and enums that are often
// defined without a name and used in a typedef. // defined without a name and used in a typedef.
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string { func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
if name, ok := p.anonDecls[itf]; ok { if name, ok := p.anonDecls[itf]; ok {
return name return name
} }
@@ -1233,22 +1120,22 @@ func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string {
func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string { func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) string {
// Some types are defined in stdint.h and map directly to a particular Go // Some types are defined in stdint.h and map directly to a particular Go
// type. // type.
if alias := cgoAliases["_Cgo_"+name]; alias != "" { if alias := cgoAliases["C."+name]; alias != "" {
return alias return alias
} }
node := f.getASTDeclNode(name, found) node := f.getASTDeclNode(name, found, iscall)
if node, ok := node.(*ast.FuncDecl); ok { if node, ok := node.(*ast.FuncDecl); ok {
if !iscall { if !iscall {
return node.Name.Name + "$funcaddr" return node.Name.Name + "$funcaddr"
} }
return node.Name.Name return node.Name.Name
} }
return "_Cgo_" + name return "C." + name
} }
// getASTDeclNode will declare the given C AST node (if not already defined) and // getASTDeclNode will declare the given C AST node (if not already defined) and
// returns it. // returns it.
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node { func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
if node, ok := f.defined[name]; ok { if node, ok := f.defined[name]; ok {
// Declaration was found in the current file, so return it immediately. // Declaration was found in the current file, so return it immediately.
return node return node
@@ -1343,8 +1230,8 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
case *elaboratedTypeInfo: case *elaboratedTypeInfo:
// Add struct bitfields. // Add struct bitfields.
for _, bitfield := range elaboratedType.bitfields { for _, bitfield := range elaboratedType.bitfields {
f.createBitfieldGetter(bitfield, "_Cgo_"+name) f.createBitfieldGetter(bitfield, "C."+name)
f.createBitfieldSetter(bitfield, "_Cgo_"+name) f.createBitfieldSetter(bitfield, "C."+name)
} }
if elaboratedType.unionSize != 0 { if elaboratedType.unionSize != 0 {
// Create union getters/setters. // Create union getters/setters.
@@ -1353,7 +1240,7 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names))) f.addError(elaboratedType.pos, fmt.Sprintf("union must have field with a single name, it has %d names", len(field.Names)))
continue continue
} }
f.createUnionAccessor(field, "_Cgo_"+name) f.createUnionAccessor(field, "C."+name)
} }
} }
} }
@@ -1367,45 +1254,6 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
// separate namespace (no _Cgo_ hacks like in gc). // separate namespace (no _Cgo_ hacks like in gc).
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool { func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
switch node := cursor.Node().(type) { switch node := cursor.Node().(type) {
case *ast.AssignStmt:
// An assign statement could be something like this:
//
// val, errno := C.some_func()
//
// Check whether it looks like that, and if so, read the errno value and
// return it as the second return value. The call will be transformed
// into something like this:
//
// val, errno := C.some_func(), C.__get_errno()
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
return true
}
rhs, ok := node.Rhs[0].(*ast.CallExpr)
if !ok {
return true
}
fun, ok := rhs.Fun.(*ast.SelectorExpr)
if !ok {
return true
}
x, ok := fun.X.(*ast.Ident)
if !ok {
return true
}
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
// Replace "C"."some_func" into "C.somefunc".
rhs.Fun = &ast.Ident{
NamePos: x.NamePos,
Name: f.getASTDeclName(fun.Sel.Name, found, true),
}
// Add the errno value as the second value in the statement.
node.Rhs = append(node.Rhs, &ast.CallExpr{
Fun: &ast.Ident{
NamePos: node.Lhs[1].End(),
Name: "_Cgo___get_errno",
},
})
}
case *ast.CallExpr: case *ast.CallExpr:
fun, ok := node.Fun.(*ast.SelectorExpr) fun, ok := node.Fun.(*ast.SelectorExpr)
if !ok { if !ok {
@@ -1427,7 +1275,7 @@ func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) b
return true return true
} }
if x.Name == "C" { if x.Name == "C" {
name := "_Cgo_" + node.Sel.Name name := "C." + node.Sel.Name
if found, ok := names[node.Sel.Name]; ok { if found, ok := names[node.Sel.Name]; ok {
name = f.getASTDeclName(node.Sel.Name, found, false) name = f.getASTDeclName(node.Sel.Name, found, false)
} }
+13 -123
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) {
@@ -45,6 +37,7 @@ func TestCGo(t *testing.T) {
"flags", "flags",
"const", "const",
} { } {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
// Read the AST in memory. // Read the AST in memory.
path := filepath.Join("testdata", name+".go") path := filepath.Join("testdata", name+".go")
@@ -55,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, "linux") cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "")
// Check the AST for type errors. // Check the AST for type errors.
var typecheckErrors []error var typecheckErrors []error
@@ -63,10 +56,10 @@ func TestCGo(t *testing.T) {
Error: func(err error) { Error: func(err error) {
typecheckErrors = append(typecheckErrors, err) typecheckErrors = append(typecheckErrors, err)
}, },
Importer: newSimpleImporter(), 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).
@@ -91,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")
@@ -122,112 +115,15 @@ 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 syscall and unsafe packages. // importing the unsafe package.
type simpleImporter struct { type simpleImporter struct {
syscallPkg *types.Package
}
func newSimpleImporter() *simpleImporter {
i := &simpleImporter{}
// Implement a dummy syscall package with the Errno type.
i.syscallPkg = types.NewPackage("syscall", "syscall")
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
named := types.NewNamed(obj, nil, nil)
i.syscallPkg.Scope().Insert(obj)
named.SetUnderlying(types.Typ[types.Uintptr])
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
i.syscallPkg.MarkComplete()
return i
} }
// Import implements the Importer interface. For testing usage only: it only // Import implements the Importer interface. For testing usage only: it only
// supports importing the unsafe package. // supports importing the unsafe package.
func (i *simpleImporter) Import(path string) (*types.Package, error) { func (i simpleImporter) Import(path string) (*types.Package, error) {
switch path { switch path {
case "syscall":
return i.syscallPkg, nil
case "unsafe": case "unsafe":
return types.Unsafe, nil return types.Unsafe, nil
default: default:
@@ -235,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/")
+7 -176
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
@@ -54,72 +44,8 @@ func init() {
} }
// parseConst parses the given string as a C constant. // parseConst parses the given string as a C constant.
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) { func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
t := newTokenizer(pos, fset, value, f) t := newTokenizer(pos, fset, value)
// If params is non-nil (could be a zero length slice), this const is
// actually a function-call like expression from another macro.
// This means we have to parse a string like "(a, b) (a+b)".
// We do this by parsing the parameters at the start and then treating the
// following like a normal constant expression.
if params != nil {
// Parse opening paren.
if t.curToken != token.LPAREN {
return nil, unexpectedToken(t, token.LPAREN)
}
t.Next()
// Parse parameters (identifiers) and closing paren.
var paramIdents []string
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
// No parameters, break early.
t.Next()
break
}
// Read the parameter name.
if t.curToken != token.IDENT {
return nil, unexpectedToken(t, token.IDENT)
}
paramIdents = append(paramIdents, t.curValue)
t.Next()
// Read the next token: either a continuation (comma) or end of list
// (rparen).
if t.curToken == token.RPAREN {
// End of parameter list.
t.Next()
break
} else if t.curToken == token.COMMA {
// Comma, so there will be another parameter name.
t.Next()
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
}
}
}
// Report an error if there is a mismatch in parameter length.
// The error is reported at the location of the closing paren from the
// caller location.
if len(params) != len(paramIdents) {
return nil, &scanner.Error{
Pos: t.fset.Position(callerPos),
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
}
}
// Assign values to the parameters.
// These parameter names are closer in 'scope' than other identifiers so
// will be used first when parsing an identifier.
for i, name := range paramIdents {
t.params[name] = params[i]
}
}
expr, err := parseConstExpr(t, precedenceLowest) expr, err := parseConstExpr(t, precedenceLowest)
t.Next() t.Next()
if t.curToken != token.EOF { if t.curToken != token.EOF {
@@ -150,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)
} }
@@ -160,68 +86,6 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
} }
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) { func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
// If the identifier is one of the parameters of this function-like macro,
// use the parameter value.
if val, ok := t.params[t.curValue]; ok {
return val, nil
}
if t.f != nil {
// Check whether this identifier is actually a macro "call" with
// parameters. In that case, we should parse the parameters and pass it
// on to a new invocation of parseConst.
if t.peekToken == token.LPAREN {
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
// We know the current and peek tokens (the peek one is the '('
// token). So skip ahead until the current token is the first
// unknown token.
t.Next()
t.Next()
// Parse the list of parameters until ')' (rparen) is found.
params := []ast.Expr{}
for i := 0; ; i++ {
if i == 0 && t.curToken == token.RPAREN {
break
}
x, err := parseConstExpr(t, precedenceLowest)
if err != nil {
return nil, err
}
params = append(params, x)
t.Next()
if t.curToken == token.COMMA {
t.Next()
} else if t.curToken == token.RPAREN {
break
} else {
return nil, &scanner.Error{
Pos: t.fset.Position(t.curPos),
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
}
}
}
// Evaluate the macro value and use it as the identifier value.
rparen := t.curPos
pos, text := t.f.getMacro(cursor)
return parseConst(pos, t.fset, text, params, rparen, t.f)
}
}
// Normally the name is something defined in the file (like another
// macro) which we get the declaration from using getASTDeclName.
// This ensures that names that are only referenced inside a macro are
// still getting defined.
if cursor, ok := t.f.names[t.curValue]; ok {
return &ast.Ident{
NamePos: t.curPos,
Name: t.f.getASTDeclName(t.curValue, cursor, false),
}, nil
}
}
// t.f is nil during testing. This is a fallback.
return &ast.Ident{ return &ast.Ident{
NamePos: t.curPos, NamePos: t.curPos,
Name: "C." + t.curValue, Name: "C." + t.curValue,
@@ -290,25 +154,21 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
// tokenizer reads C source code and converts it to Go tokens. // tokenizer reads C source code and converts it to Go tokens.
type tokenizer struct { type tokenizer struct {
f *cgoFile
curPos, peekPos token.Pos curPos, peekPos token.Pos
fset *token.FileSet fset *token.FileSet
curToken, peekToken token.Token curToken, peekToken token.Token
curValue, peekValue string curValue, peekValue string
buf string buf string
params map[string]ast.Expr
} }
// newTokenizer initializes a new tokenizer, positioned at the first token in // newTokenizer initializes a new tokenizer, positioned at the first token in
// the string. // the string.
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer { func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
t := &tokenizer{ t := &tokenizer{
f: f,
peekPos: start, peekPos: start,
fset: fset, fset: fset,
buf: buf, buf: buf,
peekToken: token.ILLEGAL, peekToken: token.ILLEGAL,
params: make(map[string]ast.Expr),
} }
// Parse the first two tokens (cur and peek). // Parse the first two tokens (cur and peek).
t.Next() t.Next()
@@ -325,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
@@ -339,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 == '|' || c == '^':
// Single-character tokens. // Single-character tokens.
// TODO: ++ (increment) and -- (decrement) operators. // TODO: ++ (increment) and -- (decrement) operators.
switch c { switch c {
@@ -368,8 +207,6 @@ func (t *tokenizer) Next() {
t.peekToken = token.LPAREN t.peekToken = token.LPAREN
case ')': case ')':
t.peekToken = token.RPAREN t.peekToken = token.RPAREN
case ',':
t.peekToken = token.COMMA
case '+': case '+':
t.peekToken = token.ADD t.peekToken = token.ADD
case '-': case '-':
@@ -380,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:]
+1 -9
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`},
@@ -59,7 +51,7 @@ func TestParseConst(t *testing.T) {
} { } {
fset := token.NewFileSet() fset := token.NewFileSet()
startPos := fset.AddFile("", -1, 1000).Pos(0) startPos := fset.AddFile("", -1, 1000).Pos(0)
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil) expr, err := parseConst(startPos, fset, tc.C)
s := "<invalid>" s := "<invalid>"
if err != nil { if err != nil {
if !strings.HasPrefix(tc.Go, "error: ") { if !strings.HasPrefix(tc.Go, "error: ") {
+152 -168
View File
@@ -4,7 +4,6 @@ package cgo
// modification. It does not touch the AST itself. // modification. It does not touch the AST itself.
import ( import (
"bytes"
"crypto/sha256" "crypto/sha256"
"crypto/sha512" "crypto/sha512"
"encoding/hex" "encoding/hex"
@@ -61,26 +60,11 @@ 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);
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
// In file included from _cgo_export.c:4:
// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
// libclang.go:68:5: note: previous declaration is here
// See: https://github.com/golang/go/issues/49721
#if defined(_WIN32)
#define CGO_DECL __declspec(dllexport)
#else
#define CGO_DECL
#endif
CGO_DECL
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data); int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
CGO_DECL int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data); void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
*/ */
import "C" import "C"
@@ -130,7 +114,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// convert Go slice of strings to C array of strings. // convert Go slice of strings to C array of strings.
cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0)))) cmdargsC := C.malloc(C.size_t(len(cflags)) * C.size_t(unsafe.Sizeof(uintptr(0))))
defer C.free(cmdargsC) defer C.free(cmdargsC)
cmdargs := unsafe.Slice((**C.char)(cmdargsC), len(cflags)) cmdargs := (*[1 << 16]*C.char)(cmdargsC)
for i, cflag := range cflags { for i, cflag := range cflags {
s := C.CString(cflag) s := C.CString(cflag)
cmdargs[i] = s cmdargs[i] = s
@@ -160,7 +144,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
pos := f.getClangLocationPosition(location, unit) pos := f.getClangLocationPosition(location, unit)
f.addError(pos, severity+": "+spelling) f.addError(pos, severity+": "+spelling)
} }
for i := range numDiagnostics { for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic) addDiagnostic(diagnostic)
@@ -190,7 +174,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
// Sanity check. This should (hopefully) never trigger. // Sanity check. This should (hopefully) never trigger.
panic("libclang: file contents was not loaded") panic("libclang: file contents was not loaded")
} }
data := unsafe.Slice((*byte)(unsafe.Pointer(rawData)), size) data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
// Hash the contents if it isn't hashed yet. // Hash the contents if it isn't hashed yet.
if _, ok := f.visitedFiles[path]; !ok { if _, ok := f.visitedFiles[path]; !ok {
@@ -217,6 +201,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
case C.CXCursor_FunctionDecl: case C.CXCursor_FunctionDecl:
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
obj := &ast.Object{
Kind: ast.Fun,
Name: "C." + name,
}
exportName := name exportName := name
localName := name localName := name
var stringSignature string var stringSignature string
@@ -253,7 +241,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + localName, Name: "C." + localName,
Obj: obj,
}, },
Type: &ast.FuncType{ Type: &ast.FuncType{
Func: pos, Func: pos,
@@ -264,21 +253,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
}, },
} }
var doc []string
if C.clang_isFunctionTypeVariadic(cursorType) != 0 { if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
doc = append(doc, "//go:variadic")
}
if _, ok := f.noescapingFuncs[name]; ok {
doc = append(doc, "//go:noescape")
f.noescapingFuncs[name].used = true
}
if len(doc) != 0 {
decl.Doc.List = append(decl.Doc.List, &ast.Comment{ decl.Doc.List = append(decl.Doc.List, &ast.Comment{
Slash: pos - 1, Slash: pos - 1,
Text: strings.Join(doc, "\n"), Text: "//go:variadic",
}) })
} }
for i := range numArgs { for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg)) argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i)) argType := C.clang_getArgType(cursorType, C.uint(i))
@@ -290,6 +271,11 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
{ {
NamePos: pos, NamePos: pos,
Name: argName, Name: argName,
Obj: &ast.Object{
Kind: ast.Var,
Name: argName,
Decl: decl,
},
}, },
}, },
Type: f.makeDecayingASTType(argType, pos), Type: f.makeDecayingASTType(argType, pos),
@@ -305,34 +291,49 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
} }
} }
obj.Decl = decl
return decl, stringSignature return decl, stringSignature
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 := "_Cgo_" + name typeName := "C." + name
typeExpr := typ.typeExpr typeExpr := typ.typeExpr
if typ.unionSize != 0 { if typ.unionSize != 0 {
// Convert to a single-field struct type. // Convert to a single-field struct type.
typeExpr = f.makeUnionField(typ) typeExpr = f.makeUnionField(typ)
} }
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: typ.pos, NamePos: typ.pos,
Name: typeName, Name: typeName,
Obj: obj,
}, },
Type: typeExpr, Type: typeExpr,
} }
obj.Decl = typeSpec
return typeSpec, typ return typeSpec, typ
case C.CXCursor_TypedefDecl: case C.CXCursor_TypedefDecl:
typeName := "_Cgo_" + name typeName := "C." + name
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: typeName, Name: typeName,
Obj: obj,
}, },
Assign: pos, Type: f.makeASTType(underlyingType, pos),
Type: f.makeASTType(underlyingType, pos),
} }
if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos
}
obj.Decl = typeSpec
return typeSpec, nil return typeSpec, nil
case C.CXCursor_VarDecl: case C.CXCursor_VarDecl:
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
@@ -351,18 +352,58 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
}, },
} }
obj := &ast.Object{
Kind: ast.Var,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "C." + name,
Obj: obj,
}}, }},
Type: typeExpr, Type: typeExpr,
} }
obj.Decl = valueSpec
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:
tokenPos, value := f.getMacro(c) sourceRange := C.tinygo_clang_getCursorExtent(c)
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f) start := C.clang_getRangeStart(sourceRange)
end := C.clang_getRangeEnd(sourceRange)
var file, endFile C.CXFile
var startOffset, endOffset C.unsigned
C.clang_getExpansionLocation(start, &file, nil, nil, &startOffset)
if file == nil {
f.addError(pos, "internal error: could not find file where macro is defined")
return nil, nil
}
C.clang_getExpansionLocation(end, &endFile, nil, nil, &endOffset)
if file != endFile {
f.addError(pos, "internal error: expected start and end location of a macro to be in the same file")
return nil, nil
}
if startOffset > endOffset {
f.addError(pos, "internal error: start offset of macro is after end offset")
return nil, nil
}
// read file contents and extract the relevant byte range
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size)
if endOffset >= C.uint(size) {
f.addError(pos, "internal error: end offset of macro lies after end of file")
return nil, nil
}
source := string(((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[startOffset:endOffset:endOffset])
if !strings.HasPrefix(source, name) {
f.addError(pos, fmt.Sprintf("internal error: expected macro value to start with %#v, got %#v", name, source))
return nil, nil
}
value := source[len(name):]
// Try to convert this #define into a Go constant expression.
expr, scannerError := parseConst(pos+token.Pos(len(name)), f.fset, value)
if scannerError != nil { if scannerError != nil {
f.errors = append(f.errors, *scannerError) f.errors = append(f.errors, *scannerError)
return nil, nil return nil, nil
@@ -374,27 +415,39 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos, Lparen: token.NoPos,
Rparen: token.NoPos, Rparen: token.NoPos,
} }
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "C." + name,
Obj: obj,
}}, }},
Values: []ast.Expr{expr}, Values: []ast.Expr{expr},
} }
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
case C.CXCursor_EnumDecl: case C.CXCursor_EnumDecl:
obj := &ast.Object{
Kind: ast.Typ,
Name: "C." + name,
}
underlying := C.tinygo_clang_getEnumDeclIntegerType(c) underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
// TODO: gc's CGo implementation uses types such as `uint32` for enums // TODO: gc's CGo implementation uses types such as `uint32` for enums
// instead of types such as C.int, which are used here. // instead of types such as C.int, which are used here.
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "C." + name,
Obj: obj,
}, },
Assign: pos, Assign: pos,
Type: f.makeASTType(underlying, pos), Type: f.makeASTType(underlying, pos),
} }
obj.Decl = typeSpec
return typeSpec, nil return typeSpec, nil
case C.CXCursor_EnumConstantDecl: case C.CXCursor_EnumConstantDecl:
value := C.tinygo_clang_getEnumConstantDeclValue(c) value := C.tinygo_clang_getEnumConstantDeclValue(c)
@@ -409,13 +462,19 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos, Lparen: token.NoPos,
Rparen: token.NoPos, Rparen: token.NoPos,
} }
obj := &ast.Object{
Kind: ast.Con,
Name: "C." + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "C." + name,
Obj: obj,
}}, }},
Values: []ast.Expr{expr}, Values: []ast.Expr{expr},
} }
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
default: default:
@@ -424,62 +483,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
} }
} }
// Return whether this is a macro that's also function-like, like this:
//
// #define add(a, b) (a+b)
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
return false
}
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
}
// Get the macro value: the position in the source file and the string value of
// the macro.
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
// Extract tokens from the Clang tokenizer.
// See: https://stackoverflow.com/a/19074846/559350
sourceRange := C.tinygo_clang_getCursorExtent(c)
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
var rawTokens *C.CXToken
var numTokens C.unsigned
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
tokens := unsafe.Slice(rawTokens, numTokens)
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
// Convert this range of tokens back to source text.
// Ugly, but it works well enough.
sourceBuf := &bytes.Buffer{}
var startOffset int
for i, token := range tokens {
spelling := getString(C.clang_getTokenSpelling(tu, token))
location := C.clang_getTokenLocation(tu, token)
var tokenOffset C.unsigned
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
if i == 0 {
// The first token is the macro name itself.
// Skip it (after using its location).
startOffset = int(tokenOffset)
} else {
// Later tokens are the macro contents.
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
// Pad the source text with whitespace (that must have been
// present in the original source as well).
sourceBuf.WriteByte(' ')
}
sourceBuf.WriteString(spelling)
}
}
value = sourceBuf.String()
// Obtain the position of this token. This is the position of the first
// character in the 'value' string and is used to report errors at the
// correct location in the source file.
pos = f.getCursorPosition(c)
return
}
func getString(clangString C.CXString) (s string) { func getString(clangString C.CXString) (s string) {
rawString := C.clang_getCString(clangString) rawString := C.clang_getCString(clangString)
s = C.GoString(rawString) s = C.GoString(rawString)
@@ -530,26 +533,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) any {
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))
@@ -575,7 +558,7 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
// now by reading the file from libclang. // now by reading the file from libclang.
var size C.size_t var size C.size_t
sourcePtr := C.clang_getFileContents(tu, file, &size) sourcePtr := C.clang_getFileContents(tu, file, &size)
source := unsafe.Slice((*byte)(unsafe.Pointer(sourcePtr)), size) source := ((*[1 << 28]byte)(unsafe.Pointer(sourcePtr)))[:size:size]
lines := []int{0} lines := []int{0}
for i := 0; i < len(source)-1; i++ { for i := 0; i < len(source)-1; i++ {
if source[i] == '\n' { if source[i] == '\n' {
@@ -585,14 +568,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),
}
astFile.FileStart = f.Pos(0)
astFile.FileEnd = f.Pos(int(size))
p.cgoFiles = append(p.cgoFiles, astFile)
} }
positionFile := p.tokenFiles[filename] positionFile := p.tokenFiles[filename]
@@ -639,6 +614,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,
@@ -651,19 +633,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
@@ -697,27 +668,27 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
var typeName string var typeName string
switch typ.kind { switch typ.kind {
case C.CXType_Char_S, C.CXType_Char_U: case C.CXType_Char_S, C.CXType_Char_U:
typeName = "_Cgo_char" typeName = "C.char"
case C.CXType_SChar: case C.CXType_SChar:
typeName = "_Cgo_schar" typeName = "C.schar"
case C.CXType_UChar: case C.CXType_UChar:
typeName = "_Cgo_uchar" typeName = "C.uchar"
case C.CXType_Short: case C.CXType_Short:
typeName = "_Cgo_short" typeName = "C.short"
case C.CXType_UShort: case C.CXType_UShort:
typeName = "_Cgo_ushort" typeName = "C.ushort"
case C.CXType_Int: case C.CXType_Int:
typeName = "_Cgo_int" typeName = "C.int"
case C.CXType_UInt: case C.CXType_UInt:
typeName = "_Cgo_uint" typeName = "C.uint"
case C.CXType_Long: case C.CXType_Long:
typeName = "_Cgo_long" typeName = "C.long"
case C.CXType_ULong: case C.CXType_ULong:
typeName = "_Cgo_ulong" typeName = "C.ulong"
case C.CXType_LongLong: case C.CXType_LongLong:
typeName = "_Cgo_longlong" typeName = "C.longlong"
case C.CXType_ULongLong: case C.CXType_ULongLong:
typeName = "_Cgo_ulonglong" typeName = "C.ulonglong"
case C.CXType_Bool: case C.CXType_Bool:
typeName = "bool" typeName = "bool"
case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble: case C.CXType_Float, C.CXType_Double, C.CXType_LongDouble:
@@ -797,21 +768,11 @@ 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))
typeName = "<unknown>" typeName = "<unknown>"
} }
case C.CXType_Unexposed:
// LLVM 22+ may report certain builtin type aliases (e.g. __size_t)
// as Unexposed. Resolve via the canonical type.
canonical := C.clang_getCanonicalType(typ)
if canonical.kind != C.CXType_Unexposed && canonical.kind != C.CXType_Invalid {
return f.makeASTType(canonical, pos)
}
// If still unexposed, fall through to the error below.
case C.CXType_Record: case C.CXType_Record:
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))
@@ -825,9 +786,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
@@ -840,9 +814,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
} }
@@ -856,7 +828,7 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
typeSpelling := getString(C.clang_getTypeSpelling(typ)) typeSpelling := getString(C.clang_getTypeSpelling(typ))
typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind)) typeKindSpelling := getString(C.clang_getTypeKindSpelling(typ.kind))
f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling)) f.addError(pos, fmt.Sprintf("unknown C type: %v (libclang type kind %s)", typeSpelling, typeKindSpelling))
typeName = "_Cgo_<unknown>" typeName = "C.<unknown>"
} }
return &ast.Ident{ return &ast.Ident{
NamePos: pos, NamePos: pos,
@@ -873,7 +845,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
var goName string var goName string
typeSize := C.clang_Type_getSizeOf(underlyingType) typeSize := C.clang_Type_getSizeOf(underlyingType)
switch name { switch name {
case "_Cgo_char": case "C.char":
if typeSize != 1 { if typeSize != 1 {
// This happens for some very special purpose architectures // This happens for some very special purpose architectures
// (DSPs etc.) that are not currently targeted. // (DSPs etc.) that are not currently targeted.
@@ -886,7 +858,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case C.CXType_Char_U: case C.CXType_Char_U:
goName = "uint8" goName = "uint8"
} }
case "_Cgo_schar", "_Cgo_short", "_Cgo_int", "_Cgo_long", "_Cgo_longlong": case "C.schar", "C.short", "C.int", "C.long", "C.longlong":
switch typeSize { switch typeSize {
case 1: case 1:
goName = "int8" goName = "int8"
@@ -897,7 +869,7 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
case 8: case 8:
goName = "int64" goName = "int64"
} }
case "_Cgo_uchar", "_Cgo_ushort", "_Cgo_uint", "_Cgo_ulong", "_Cgo_ulonglong": case "C.uchar", "C.ushort", "C.uint", "C.ulong", "C.ulonglong":
switch typeSize { switch typeSize {
case 1: case 1:
goName = "uint8" goName = "uint8"
@@ -916,16 +888,22 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
} }
// Construct an *ast.TypeSpec for this type. // Construct an *ast.TypeSpec for this type.
obj := &ast.Object{
Kind: ast.Typ,
Name: name,
}
spec := &ast.TypeSpec{ spec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: name, Name: name,
Obj: obj,
}, },
Type: &ast.Ident{ Type: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: goName, Name: goName,
}, },
} }
obj.Decl = spec
return spec return spec
} }
@@ -1058,6 +1036,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
pos: prevField.Names[0].NamePos, pos: prevField.Names[0].NamePos,
}) })
prevField.Names[0].Name = bitfieldName prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
} }
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset prevBitfield.endBit = bitfieldOffset
@@ -1074,6 +1053,11 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
{ {
NamePos: pos, NamePos: pos,
Name: name, Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
}, },
} }
fieldList.List = append(fieldList.List, field) fieldList.List = append(fieldList.List, field)
+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 && llvm18
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-18 -I/usr/include/llvm-c-18 -I/usr/lib/llvm-18/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@18/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@18/include
#cgo freebsd CFLAGS: -I/usr/local/llvm18/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-18/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@18/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@18/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm18/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm19
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-19 -I/usr/include/llvm-c-19 -I/usr/lib/llvm-19/include -I/usr/lib64/llvm19/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@19/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@19/include
#cgo freebsd CFLAGS: -I/usr/local/llvm19/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-19/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@19/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@19/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm19/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib/llvm-20/include -I/usr/lib64/llvm20/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@20/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@20/include
#cgo freebsd CFLAGS: -I/usr/local/llvm20/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-20/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@20/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@20/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm20/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm21
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-21 -I/usr/include/llvm-c-21 -I/usr/lib/llvm-21/include -I/usr/lib64/llvm21/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@21/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@21/include
#cgo freebsd CFLAGS: -I/usr/local/llvm21/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-21/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@21/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@21/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm21/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm22
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-22 -I/usr/include/llvm-c-22 -I/usr/lib/llvm-22/include -I/usr/lib64/llvm22/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@22/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@22/include
#cgo freebsd CFLAGS: -I/usr/local/llvm22/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-22/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@22/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@22/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm22/lib -lclang
*/
import "C"
+1 -9
View File
@@ -77,14 +77,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);
} }
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
return clang_Cursor_isMacroFunctionLike(c);
}
-2
View File
@@ -78,7 +78,6 @@ var validCompilerFlags = []*regexp.Regexp{
re(`-f(no-)?(pic|PIC|pie|PIE)`), re(`-f(no-)?(pic|PIC|pie|PIE)`),
re(`-f(no-)?plt`), re(`-f(no-)?plt`),
re(`-f(no-)?rtti`), re(`-f(no-)?rtti`),
re(`-f(no-)?short-enums`),
re(`-f(no-)?split-stack`), re(`-f(no-)?split-stack`),
re(`-f(no-)?stack-(.+)`), re(`-f(no-)?stack-(.+)`),
re(`-f(no-)?strict-aliasing`), re(`-f(no-)?strict-aliasing`),
@@ -143,7 +142,6 @@ var validLinkerFlags = []*regexp.Regexp{
re(`-L([^@\-].*)`), re(`-L([^@\-].*)`),
re(`-O`), re(`-O`),
re(`-O([^@\-].*)`), re(`-O([^@\-].*)`),
re(`--export=([^@\-].*)`),
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"},
+4 -4
View File
@@ -12,17 +12,17 @@ import "C"
// C. It is useful if an API uses function pointers and you cannot pass a Go // C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer. // pointer but only a C pointer.
type refMap struct { type refMap struct {
refs map[unsafe.Pointer]any refs map[unsafe.Pointer]interface{}
lock sync.Mutex lock sync.Mutex
} }
// Put stores a value in the map. It can later be retrieved using Get. It must // Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks. // be removed using Remove to avoid memory leaks.
func (m *refMap) Put(v any) unsafe.Pointer { func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if m.refs == nil { if m.refs == nil {
m.refs = make(map[unsafe.Pointer]any, 1) m.refs = make(map[unsafe.Pointer]interface{}, 1)
} }
ref := C.malloc(1) ref := C.malloc(1)
m.refs[ref] = v m.refs[ref] = v
@@ -31,7 +31,7 @@ func (m *refMap) Put(v any) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same // Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put. // reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) any { func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
return m.refs[ref] return m.refs[ref]
+23 -38
View File
@@ -1,54 +1,39 @@
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
-16
View File
@@ -3,26 +3,10 @@ package main
/* /*
#define foo 3 #define foo 3
#define bar foo #define bar foo
#define unreferenced 4
#define referenced unreferenced
#define fnlike() 5
#define fnlike_val fnlike()
#define square(n) (n*n)
#define square_val square(20)
#define add(a, b) (a + b)
#define add_val add(3, 5)
*/ */
import "C" import "C"
const ( const (
Foo = C.foo Foo = C.foo
Bar = C.bar Bar = C.bar
Baz = C.referenced
fnlike = C.fnlike_val
square = C.square_val
add = C.add_val
) )
+25 -45
View File
@@ -1,62 +1,42 @@
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
const _Cgo_foo = 3 const C.foo = 3
const _Cgo_bar = _Cgo_foo const C.bar = C.foo
const _Cgo_unreferenced = 4
const _Cgo_referenced = _Cgo_unreferenced
const _Cgo_fnlike_val = 5
const _Cgo_square_val = (20 * 20)
const _Cgo_add_val = (3 + 5)
-26
View File
@@ -10,35 +10,20 @@ typedef struct {
typedef someType noType; // undefined type typedef someType noType; // undefined type
// Some invalid noescape lines
#cgo noescape
#cgo noescape foo bar
#cgo noescape unusedFunction
#define SOME_CONST_1 5) // invalid const syntax #define SOME_CONST_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
import "C" import "C"
// #define add(a, b) (a+b)
// #define add_toomuch add(1, 2, 3)
// #define add_toolittle add(1)
import "C"
// Make sure that errors for the following lines won't change with future // 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
@@ -53,15 +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
_ = C.add_toomuch
_ = C.add_toolittle
) )
+35 -64
View File
@@ -1,89 +1,60 @@
// CGo errors: // CGo errors:
// testdata/errors.go:14:1: missing function name in #cgo noescape line
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
// testdata/errors.go:4:2: warning: some warning // testdata/errors.go: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:31:5: warning: another warning // testdata/errors.go:22:5: warning: another warning
// testdata/errors.go:18:23: unexpected token ), expected end of expression // testdata/errors.go:13:23: unexpected token ), expected end of expression
// testdata/errors.go:26:26: unexpected token ), expected end of expression // testdata/errors.go:19:26: unexpected token ), expected end of expression
// testdata/errors.go:21:33: unexpected token ), expected end of expression
// testdata/errors.go:22:34: unexpected token ), expected end of expression
// -: unexpected token INT, expected end of expression
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
// 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 _Cgo_char value in variable declaration (overflows) // testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal // testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undefined: _Cgo_SOME_CONST_1 // testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:110: cannot use _Cgo_SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows) // testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undefined: _Cgo_SOME_CONST_4 // testdata/errors.go:112: undeclared name: C.SOME_CONST_4
// testdata/errors.go:114: undefined: _Cgo_SOME_CONST_b
// testdata/errors.go:116: undefined: _Cgo_SOME_CONST_startspace
// testdata/errors.go:119: undefined: _Cgo_SOME_PARAM_CONST_invalid
// testdata/errors.go:122: undefined: _Cgo_add_toomuch
// testdata/errors.go:123: undefined: _Cgo_add_toolittle
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
type _Cgo_struct_point_t struct { type C._Ctype_struct___0 struct {
x _Cgo_int x C.int
y _Cgo_int y C.int
} }
type _Cgo_point_t = _Cgo_struct_point_t type C.point_t = C._Ctype_struct___0
const _Cgo_SOME_CONST_3 = 1234 const C.SOME_CONST_3 = 1234
const _Cgo_SOME_PARAM_CONST_valid = 3 + 4
+25 -40
View File
@@ -5,58 +5,43 @@
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
const _Cgo_BAR = 3 const C.BAR = 3
const _Cgo_FOO_H = 1 const C.FOO_H = 1
-5
View File
@@ -9,10 +9,6 @@ static void staticfunc(int x);
// Global variable signatures. // Global variable signatures.
extern int someValue; extern int someValue;
void notEscapingFunction(int *a);
#cgo noescape notEscapingFunction
*/ */
import "C" import "C"
@@ -22,7 +18,6 @@ func accessFunctions() {
C.variadic0() C.variadic0()
C.variadic2(3, 5) C.variadic2(3, 5)
C.staticfunc(3) C.staticfunc(3)
C.notEscapingFunction(nil)
} }
func accessGlobals() { func accessGlobals() {
+32 -52
View File
@@ -1,84 +1,64 @@
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
//export foo //export foo
func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int func C.foo(a C.int, b C.int) C.int
var _Cgo_foo$funcaddr unsafe.Pointer var C.foo$funcaddr unsafe.Pointer
//export variadic0 //export variadic0
//go:variadic //go:variadic
func _Cgo_variadic0() func C.variadic0()
var _Cgo_variadic0$funcaddr unsafe.Pointer var C.variadic0$funcaddr unsafe.Pointer
//export variadic2 //export variadic2
//go:variadic //go:variadic
func _Cgo_variadic2(x _Cgo_int, y _Cgo_int) func C.variadic2(x C.int, y C.int)
var _Cgo_variadic2$funcaddr unsafe.Pointer var C.variadic2$funcaddr unsafe.Pointer
//export _Cgo_static_173c95a79b6df1980521_staticfunc //export _Cgo_static_173c95a79b6df1980521_staticfunc
func _Cgo_staticfunc!symbols.go(x _Cgo_int) func C.staticfunc!symbols.go(x C.int)
var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
//export notEscapingFunction
//go:noescape
func _Cgo_notEscapingFunction(a *_Cgo_int)
var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer
//go:extern someValue //go:extern someValue
var _Cgo_someValue _Cgo_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
+92 -109
View File
@@ -1,166 +1,149 @@
package main package main
import "syscall"
import "unsafe" import "unsafe"
var _ unsafe.Pointer var _ unsafe.Pointer
//go:linkname _Cgo_CString runtime.cgo_CString //go:linkname C.CString runtime.cgo_CString
func _Cgo_CString(string) *_Cgo_char func C.CString(string) *C.char
//go:linkname _Cgo_GoString runtime.cgo_GoString //go:linkname C.GoString runtime.cgo_GoString
func _Cgo_GoString(*_Cgo_char) string func C.GoString(*C.char) string
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN //go:linkname C.__GoStringN runtime.cgo_GoStringN
func _Cgo___GoStringN(*_Cgo_char, uintptr) string func C.__GoStringN(*C.char, uintptr) string
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string { func C.GoStringN(cstr *C.char, length C.int) string {
return _Cgo___GoStringN(cstr, uintptr(length)) return C.__GoStringN(cstr, uintptr(length))
} }
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes //go:linkname C.__GoBytes runtime.cgo_GoBytes
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte func C.__GoBytes(unsafe.Pointer, uintptr) []byte
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte { func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
return _Cgo___GoBytes(ptr, uintptr(length)) return C.__GoBytes(ptr, uintptr(length))
}
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
func _Cgo___CBytes([]byte) unsafe.Pointer
func _Cgo_CBytes(b []byte) unsafe.Pointer {
return _Cgo___CBytes(b)
}
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
func _Cgo___get_errno_num() uintptr
func _Cgo___get_errno() error {
return syscall.Errno(_Cgo___get_errno_num())
} }
type ( type (
_Cgo_char uint8 C.char uint8
_Cgo_schar int8 C.schar int8
_Cgo_uchar uint8 C.uchar uint8
_Cgo_short int16 C.short int16
_Cgo_ushort uint16 C.ushort uint16
_Cgo_int int32 C.int int32
_Cgo_uint uint32 C.uint uint32
_Cgo_long int32 C.long int32
_Cgo_ulong uint32 C.ulong uint32
_Cgo_longlong int64 C.longlong int64
_Cgo_ulonglong uint64 C.ulonglong uint64
) )
type _Cgo_myint = _Cgo_int type C.myint = C.int
type _Cgo_struct_point2d_t struct { type C._Ctype_struct___0 struct {
x _Cgo_int x C.int
y _Cgo_int y C.int
} }
type _Cgo_point2d_t = _Cgo_struct_point2d_t type C.point2d_t = C._Ctype_struct___0
type _Cgo_struct_point3d struct { type C.struct_point3d struct {
x _Cgo_int x C.int
y _Cgo_int y C.int
z _Cgo_int z C.int
} }
type _Cgo_point3d_t = _Cgo_struct_point3d type C.point3d_t = C.struct_point3d
type _Cgo_struct_type1 struct { type C.struct_type1 struct {
_type _Cgo_int _type C.int
__type _Cgo_int __type C.int
___type _Cgo_int ___type C.int
} }
type _Cgo_struct_type2 struct{ _type _Cgo_int } type C.struct_type2 struct{ _type C.int }
type _Cgo_union_union1_t struct{ i _Cgo_int } type C._Ctype_union___1 struct{ i C.int }
type _Cgo_union1_t = _Cgo_union_union1_t type C.union1_t = C._Ctype_union___1
type _Cgo_union_union3_t struct{ $union uint64 } type C._Ctype_union___2 struct{ $union uint64 }
func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int { func (union *C._Ctype_union___2) unionfield_i() *C.int {
return (*_Cgo_int)(unsafe.Pointer(&union.$union)) return (*C.int)(unsafe.Pointer(&union.$union))
} }
func (union *_Cgo_union_union3_t) unionfield_d() *float64 { func (union *C._Ctype_union___2) unionfield_d() *float64 {
return (*float64)(unsafe.Pointer(&union.$union)) return (*float64)(unsafe.Pointer(&union.$union))
} }
func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short { func (union *C._Ctype_union___2) unionfield_s() *C.short {
return (*_Cgo_short)(unsafe.Pointer(&union.$union)) return (*C.short)(unsafe.Pointer(&union.$union))
} }
type _Cgo_union3_t = _Cgo_union_union3_t type C.union3_t = C._Ctype_union___2
type _Cgo_union_union2d struct{ $union [2]uint64 } type C.union_union2d struct{ $union [2]uint64 }
func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int { func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
return (*_Cgo_int)(unsafe.Pointer(&union.$union)) func (union *C.union_union2d) unionfield_d() *[2]float64 {
}
func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 {
return (*[2]float64)(unsafe.Pointer(&union.$union)) return (*[2]float64)(unsafe.Pointer(&union.$union))
} }
type _Cgo_union2d_t = _Cgo_union_union2d type C.union2d_t = C.union_union2d
type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar } type C._Ctype_union___3 struct{ arr [10]C.uchar }
type _Cgo_unionarray_t = _Cgo_union_unionarray_t type C.unionarray_t = C._Ctype_union___3
type _Cgo__Ctype_union___0 struct{ $union [3]uint32 } type C._Ctype_union___5 struct{ $union [3]uint32 }
func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t { func (union *C._Ctype_union___5) unionfield_area() *C.point2d_t {
return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union)) return (*C.point2d_t)(unsafe.Pointer(&union.$union))
} }
func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t { func (union *C._Ctype_union___5) unionfield_solid() *C.point3d_t {
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) return (*C.point3d_t)(unsafe.Pointer(&union.$union))
} }
type _Cgo_struct_struct_nested_t struct { type C._Ctype_struct___4 struct {
begin _Cgo_point2d_t begin C.point2d_t
end _Cgo_point2d_t end C.point2d_t
tag _Cgo_int tag C.int
coord _Cgo__Ctype_union___0 coord C._Ctype_union___5
} }
type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t type C.struct_nested_t = C._Ctype_struct___4
type _Cgo_union_union_nested_t struct{ $union [2]uint64 } type C._Ctype_union___6 struct{ $union [2]uint64 }
func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t { func (union *C._Ctype_union___6) unionfield_point() *C.point3d_t {
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union)) return (*C.point3d_t)(unsafe.Pointer(&union.$union))
} }
func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t { func (union *C._Ctype_union___6) unionfield_array() *C.unionarray_t {
return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union)) return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
} }
func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t { func (union *C._Ctype_union___6) unionfield_thing() *C.union3_t {
return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union)) return (*C.union3_t)(unsafe.Pointer(&union.$union))
} }
type _Cgo_union_nested_t = _Cgo_union_union_nested_t type C.union_nested_t = C._Ctype_union___6
type _Cgo_enum_option = _Cgo_int type C.enum_option = C.int
type _Cgo_option_t = _Cgo_enum_option type C.option_t = C.enum_option
type _Cgo_enum_option2_t = _Cgo_uint type C._Ctype_enum___7 = C.uint
type _Cgo_option2_t = _Cgo_enum_option2_t type C.option2_t = C._Ctype_enum___7
type _Cgo_struct_types_t struct { type C._Ctype_struct___8 struct {
f float32 f float32
d float64 d float64
ptr *_Cgo_int ptr *C.int
} }
type _Cgo_types_t = _Cgo_struct_types_t type C.types_t = C._Ctype_struct___8
type _Cgo_myIntArray = [10]_Cgo_int type C.myIntArray = [10]C.int
type _Cgo_struct_bitfield_t struct { type C._Ctype_struct___9 struct {
start _Cgo_uchar start C.uchar
__bitfield_1 _Cgo_uchar __bitfield_1 C.uchar
d _Cgo_uchar d C.uchar
e _Cgo_uchar e C.uchar
} }
func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f } func (s *C._Ctype_struct___9) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_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 *_Cgo_struct_bitfield_t) bitfield_b() _Cgo_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 *_Cgo_struct_bitfield_t) set_bitfield_b(value _Cgo_uchar) { func (s *C._Ctype_struct___9) set_bitfield_b(value C.uchar) {
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
} }
func (s *_Cgo_struct_bitfield_t) bitfield_c() _Cgo_uchar { func (s *C._Ctype_struct___9) bitfield_c() C.uchar {
return s.__bitfield_1 >> 6 return s.__bitfield_1 >> 6
} }
func (s *_Cgo_struct_bitfield_t) set_bitfield_c(value _Cgo_uchar, func (s *C._Ctype_struct___9) set_bitfield_c(value C.uchar,
) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 } ) { s.__bitfield_1 = s.__bitfield_1&0x3f | value<<6 }
type _Cgo_bitfield_t = _Cgo_struct_bitfield_t type C.bitfield_t = C._Ctype_struct___9
+124 -247
View File
@@ -8,30 +8,18 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"strconv"
"strings" "strings"
"github.com/google/shlex" "github.com/google/shlex"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
// Library versions. Whenever an existing library is changed, this number should
// be added/increased so that existing caches are invalidated.
//
// (This is a bit of a layering violation, this should really be part of the
// builder.Library struct but that's hard to do since we want to know the
// library path in advance in several places).
var libVersions = map[string]int{
"musl": 3,
"bdwgc": 2,
}
// Config keeps all configuration affecting the build in a single struct. // Config keeps all configuration affecting the build in a single struct.
type Config struct { 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,36 +34,17 @@ func (c *Config) CPU() string {
return c.Target.CPU return c.Target.CPU
} }
// The current build mode (like the `-buildmode` command line flag).
func (c *Config) BuildMode() string {
if c.Options.BuildMode != "" {
return c.Options.BuildMode
}
if c.Target.BuildMode != "" {
return c.Target.BuildMode
}
return "default"
}
// Features returns a list of features this CPU supports. For example, for a // Features returns a list of features this CPU supports. For example, for a
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list // RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
// will be returned. // will be returned.
func (c *Config) Features() string { func (c *Config) Features() string {
var features string
if c.Target.Features == "" { if c.Target.Features == "" {
features = c.Options.LLVMFeatures return c.Options.LLVMFeatures
} else if c.Options.LLVMFeatures == "" {
features = c.Target.Features
} else {
features = c.Target.Features + "," + c.Options.LLVMFeatures
} }
return patchFeatures(features) if c.Options.LLVMFeatures == "" {
} return c.Target.Features
}
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length return c.Target.Features + "," + c.Options.LLVMFeatures
// 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:
@@ -86,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
@@ -98,27 +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
switch c.Scheduler() {
case "threads", "cores":
default:
tags = append(tags, "tinygo.unicore")
}
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))
} }
@@ -126,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
@@ -142,8 +99,14 @@ 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", "boehm": case "conservative":
return slices.Contains(c.BuildTags(), "tinygo.wasm") for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
default: default:
return false return false
} }
@@ -176,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.
@@ -221,13 +184,23 @@ func (c *Config) StackSize() uint64 {
return c.Target.DefaultStackSize return c.Target.DefaultStackSize
} }
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap. // UseThinLTO returns whether ThinLTO should be used for the given target. Some
func (c *Config) MaxStackAlloc() uint64 { // targets (such as wasm) are not yet supported.
if c.StackSize() >= 16*1024 { // We should try and remove as many exceptions as possible in the future, so
return 1024 // that this optimization can be applied in more places.
func (c *Config) UseThinLTO() bool {
parts := strings.Split(c.Triple(), "-")
if parts[0] == "wasm32" {
// wasm-ld doesn't seem to support ThinLTO yet.
return false
} }
if parts[0] == "avr" || parts[0] == "xtensa" {
return 256 // These use external (GNU) linkers which might perhaps support ThinLTO
// through a plugin, but it's too much hassle to set up.
return false
}
// Other architectures support ThinLTO.
return true
} }
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that // RP2040BootPatch returns whether the RP2040 boot patch should be applied that
@@ -239,68 +212,36 @@ func (c *Config) RP2040BootPatch() bool {
return false return false
} }
// Return a canonicalized architecture name, so we don't have to deal with arm* // MuslArchitecture returns the architecture name as used in musl libc. It is
// vs thumb* vs arm64. // usually the same as the first part of the LLVM triple, but not always.
func CanonicalArchName(triple string) string { func MuslArchitecture(triple string) string {
arch, _, _ := strings.Cut(triple, "-") arch := strings.Split(triple, "-")[0]
if arch == "arm64" {
return "aarch64"
}
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") { if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
return "arm" arch = "arm"
}
if arch == "mipsel" {
return "mips"
} }
return arch return arch
} }
// MuslArchitecture returns the architecture name as used in musl libc. It is // LibcPath returns the path to the libc directory. The libc path will be either
// usually the same as the first part of the LLVM triple, but not always. // a precompiled libc shipped with a TinyGo build, or a libc path in the cache
func MuslArchitecture(triple string) string { // directory (which might not yet be built).
return CanonicalArchName(triple) func (c *Config) LibcPath(name string) (path string, precompiled bool) {
}
// Returns true if the libc needs to include malloc, for the libcs where this
// matters.
func (c *Config) LibcNeedsMalloc() bool {
if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" {
return true
}
return false
}
// LibraryPath returns the path to the library build directory. The path will be
// a library path in the cache directory (which might not yet be built).
func (c *Config) LibraryPath(name string) string {
archname := c.Triple() archname := c.Triple()
if c.CPU() != "" { if c.CPU() != "" {
archname += "-" + c.CPU() archname += "-" + c.CPU()
} }
if c.ABI() != "" {
archname += "-" + c.ABI()
}
if c.Target.SoftFloat {
archname += "-softfloat"
}
if name == "bdwgc" {
// Boehm GC is compiled against a particular libc.
archname += "-" + c.Target.Libc
}
// Append a version string, if this library has a version. // Try to load a precompiled library.
if v, ok := libVersions[name]; ok { precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
archname += "-v" + strconv.Itoa(v) if _, err := os.Stat(precompiledDir); err == nil {
} // Found a precompiled library for this OS/architecture. Return the path
// directly.
options := "" return precompiledDir, true
if c.LibcNeedsMalloc() {
options += "+malloc"
} }
// No precompiled library found. Determine the path name that will be used // No precompiled library found. Determine the path name that will be used
// in the build cache. // in the build cache.
return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname) return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
} }
// DefaultBinaryExtension returns the default extension for binaries, such as // DefaultBinaryExtension returns the default extension for binaries, such as
@@ -328,28 +269,62 @@ 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) switch c.Target.Libc {
if resourceDir != "" { case "darwin-libSystem":
// The resource directory contains the built-in clang headers like root := goenv.Get("TINYGOROOT")
// 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, cflags = append(cflags,
"-resource-dir="+resourceDir, "--sysroot="+filepath.Join(root, "lib/macos-minimal-sdk/src"),
) )
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path, _ := c.LibcPath("picolibc")
cflags = append(cflags,
"--sysroot="+path,
"-isystem", filepath.Join(path, "include"), // necessary for Xtensa
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
)
case "musl":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("musl")
arch := MuslArchitecture(c.Triple())
cflags = append(cflags,
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
)
case "wasi-libc":
root := goenv.Get("TINYGOROOT")
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path, _ := c.LibcPath("mingw-w64")
cflags = append(cflags,
"--sysroot="+path,
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
"-D_UCRT",
)
case "":
// No libc specified, nothing to add.
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
} }
cflags = append(cflags, c.LibcCFlags()...)
// Always emit debug information. It is optionally stripped at link time. // 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.
cflags = append(cflags, "--target="+ClangTriple(c.Triple())) cflags = append(cflags, "--target="+c.Triple())
// Set the -mcpu (or similar) flag. // Set the -mcpu (or similar) flag.
if c.Target.CPU != "" { if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" { if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
@@ -363,87 +338,9 @@ 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
} }
// LibcCFlags returns the C compiler flags for the configured libc.
// It only uses flags that are part of the libc path (triple, cpu, abi, libc
// name) so it can safely be used to compile another C library.
func (c *Config) LibcCFlags() []string {
switch c.Target.Libc {
case "darwin-libSystem":
root := goenv.Get("TINYGOROOT")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(root, "lib/macos-minimal-sdk/src/usr/include"),
}
case "picolibc":
root := goenv.Get("TINYGOROOT")
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
path := c.LibraryPath("picolibc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(picolibcDir, "include"),
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
}
case "musl":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("musl")
arch := MuslArchitecture(c.Triple())
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
"-isystem", filepath.Join(root, "lib", "musl", "arch", "generic"),
"-isystem", filepath.Join(root, "lib", "musl", "include"),
}
case "wasi-libc":
path := c.LibraryPath("wasi-libc")
return []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
}
case "wasmbuiltins":
// nothing to add (library is purely for builtins)
return nil
case "mingw-w64":
root := goenv.Get("TINYGOROOT")
path := c.LibraryPath("mingw-w64")
cflags := []string{
"-nostdlibinc",
"-isystem", filepath.Join(path, "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "include"),
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
}
if c.GOARCH() == "386" {
cflags = append(cflags,
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
"-D_WIN32_WINNT=0x0501", // target Windows XP
)
} else {
cflags = append(cflags,
"-D_UCRT",
"-D_WIN32_WINNT=0x0a00", // target Windows 10
)
}
return cflags
case "":
// No libc specified, nothing to add.
return nil
default:
// Incorrect configuration. This could be handled in a better way, but
// usually this will be found by developers (not by TinyGo users).
panic("unknown libc: " + c.Target.Libc)
}
}
// LDFlags returns the flags to pass to the linker. A few more flags are needed // LDFlags returns the flags to pass to the linker. A few more flags are needed
// (like the one for the compiler runtime), but this represents the majority of // (like the one for the compiler runtime), but this represents the majority of
// the flags. // the flags.
@@ -458,27 +355,9 @@ func (c *Config) LDFlags() []string {
if c.Target.LinkerScript != "" { if c.Target.LinkerScript != "" {
ldflags = append(ldflags, "-T", c.Target.LinkerScript) ldflags = append(ldflags, "-T", c.Target.LinkerScript)
} }
ldflags = append(ldflags, c.Options.ExtLDFlags...)
return ldflags return ldflags
} }
// LinkerFlavor returns how the configured linker should be driven.
// Usually this is derived from GOOS, but targets may override it explicitly.
func (c *Config) LinkerFlavor() string {
if c.Target.LinkerFlavor != "" {
return c.Target.LinkerFlavor
}
switch c.GOOS() {
case "windows":
return "coff"
case "darwin":
return "darwin"
default:
return "gnu"
}
}
// ExtraFiles returns the list of extra files to be built and linked with the // ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files. // executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string { func (c *Config) ExtraFiles() []string {
@@ -498,8 +377,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
} }
@@ -543,16 +422,16 @@ 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 "":
// No configuration supplied. // No configuration supplied.
return c.Target.FlashMethod, c.Target.OpenOCDInterface return c.Target.FlashMethod, c.Target.OpenOCDInterface
case "openocd", "msd", "command", "adb": case "openocd", "msd", "command":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp", "probe-rs": case "bmp":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, "" return c.Options.Programmer, ""
default: default:
@@ -583,6 +462,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" {
@@ -594,9 +476,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
} }
@@ -619,6 +498,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 {
@@ -652,8 +540,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)
} }
@@ -662,14 +548,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
} }
-9
View File
@@ -1,9 +0,0 @@
//go:build !llvm22 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
package compileopts
// patchFeatures applies LLVM-version-specific feature name mappings.
// For LLVM 20/21, features in the target JSON files are already correct.
func patchFeatures(features string) string {
return features
}
-26
View File
@@ -1,26 +0,0 @@
//go:build llvm22
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 22 renamed several Xtensa target features.
func patchFeatures(features string) string {
// Xtensa feature renames in LLVM 22:
// atomctl → (removed, no direct replacement)
// memctl → (removed, no direct replacement)
// esp32s3 → esp32s3ops
// timerint → timers3 (for esp32/esp32s3) or timers1 (for esp8266)
// Since we can't distinguish which timer variant at this level,
// just remove the obsolete features. The CPU definition already
// implies the correct features in LLVM 22.
replacer := strings.NewReplacer(
"+atomctl,", "",
"+memctl,", "",
"+esp32s3,", "+esp32s3ops,",
"+timerint,", "",
",+timerint", "",
)
return replacer.Replace(features)
}
-16
View File
@@ -1,16 +0,0 @@
//go:build llvm14 || llvm15 || llvm16 || llvm17 || llvm18 || llvm19
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 19 and earlier do not have +bulk-memory-opt or
// +call-indirect-overlong for WebAssembly (added in LLVM 20).
func patchFeatures(features string) string {
features = strings.ReplaceAll(features, ",+bulk-memory-opt", "")
features = strings.ReplaceAll(features, "+bulk-memory-opt,", "")
features = strings.ReplaceAll(features, ",+call-indirect-overlong", "")
features = strings.ReplaceAll(features, "+call-indirect-overlong,", "")
return features
}
+51 -61
View File
@@ -3,17 +3,15 @@ package compileopts
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"slices"
"strings" "strings"
"time" "time"
) )
var ( var (
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"} validGCOptions = []string{"none", "leaking", "conservative"}
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"} validSchedulerOptions = []string{"none", "tasks", "asyncify"}
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"} validSerialOptions = []string{"none", "uart", "usb"}
validSerialOptions = []string{"none", "uart", "usb", "rtt"} validPrintSizeOptions = []string{"none", "short", "full"}
validPrintSizeOptions = []string{"none", "short", "full", "html"}
validPanicStrategyOptions = []string{"print", "trap"} validPanicStrategyOptions = []string{"print", "trap"}
validOptOptions = []string{"none", "0", "1", "2", "s", "z"} validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
) )
@@ -22,61 +20,44 @@ var (
// usually passed from the command line, but can also be passed in environment // usually passed from the command line, but can also be passed in environment
// variables for example. // variables for example.
type Options struct { 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) Target string
Directory string // working dir, leave it unset to use the current working dir Opt string
Target string GC string
BuildMode string // -buildmode flag PanicStrategy string
Opt string Scheduler string
GC string StackSize uint64 // goroutine stack size (if none could be automatically determined)
PanicStrategy string Serial string
Scheduler string Work bool // -work flag to print temporary build directory
StackSize uint64 // goroutine stack size (if none could be automatically determined) InterpTimeout time.Duration
Serial string PrintIR bool
Work bool // -work flag to print temporary build directory DumpSSA bool
InterpTimeout time.Duration VerifyIR bool
InterpMaxLoopIterations int PrintCommands func(cmd string, args ...string) `json:"-"`
PrintIR bool Semaphore chan struct{} `json:"-"` // -p flag controls cap
DumpSSA bool Debug bool
VerifyIR bool PrintSizes string
SkipDWARF bool PrintAllocs *regexp.Regexp // regexp string
PrintCommands func(cmd string, args ...string) `json:"-"` PrintStacks bool
Semaphore chan struct{} `json:"-"` // -p flag controls cap Tags []string
Debug bool WasmAbi string
Nobounds bool GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
PrintSizes string TestConfig TestConfig
PrintAllocs *regexp.Regexp // regexp string Programmer string
PrintAllocsCover bool // emit allocs in go coverage tool format OpenOCDCommands []string
PrintStacks bool LLVMFeatures string
Tags []string Directory string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value PrintJSON bool
TestConfig TestConfig Monitor bool
Programmer string BaudRate int
OpenOCDCommands []string
LLVMFeatures string
Monitor bool
BaudRate int
Timeout time.Duration
WITPackage string // pass through to wasm-tools component embed invocation
WITWorld string // pass through to wasm-tools component embed -w option
ExtLDFlags []string
GoCompatibility bool // enable to check for Go version compatibility
} }
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error { func (o *Options) Verify() error {
if o.BuildMode != "" {
valid := slices.Contains(validBuildModeOptions, o.BuildMode)
if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode,
strings.Join(validBuildModeOptions, ", "))
}
}
if o.GC != "" { if o.GC != "" {
valid := slices.Contains(validGCOptions, o.GC) valid := isInArray(validGCOptions, o.GC)
if !valid { if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`, return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC, o.GC,
@@ -85,7 +66,7 @@ func (o *Options) Verify() error {
} }
if o.Scheduler != "" { if o.Scheduler != "" {
valid := slices.Contains(validSchedulerOptions, o.Scheduler) valid := isInArray(validSchedulerOptions, o.Scheduler)
if !valid { if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler, o.Scheduler,
@@ -94,7 +75,7 @@ func (o *Options) Verify() error {
} }
if o.Serial != "" { if o.Serial != "" {
valid := slices.Contains(validSerialOptions, o.Serial) valid := isInArray(validSerialOptions, o.Serial)
if !valid { if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`, return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial, o.Serial,
@@ -103,7 +84,7 @@ func (o *Options) Verify() error {
} }
if o.PrintSizes != "" { if o.PrintSizes != "" {
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes) valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid { if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`, return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes, o.PrintSizes,
@@ -112,7 +93,7 @@ func (o *Options) Verify() error {
} }
if o.PanicStrategy != "" { if o.PanicStrategy != "" {
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy) valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
if !valid { if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`, return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy, o.PanicStrategy,
@@ -121,10 +102,19 @@ func (o *Options) Verify() error {
} }
if o.Opt != "" { if o.Opt != "" {
if !slices.Contains(validOptOptions, o.Opt) { if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", ")) return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
} }
} }
return nil return nil
} }
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
+3 -9
View File
@@ -9,9 +9,9 @@ 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, boehm`) 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, threads, cores`) 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, html`) 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`)
testCases := []struct { testCases := []struct {
@@ -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{
+130 -351
View File
@@ -23,56 +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"`
InheritableOnly bool `json:"inheritable-only"` // this target is only meant to be inherited from, not used directly Triple string `json:"llvm-target"`
Triple string `json:"llvm-target,omitempty"` CPU string `json:"cpu"`
CPU string `json:"cpu,omitempty"` Features string `json:"features"`
ABI string `json:"target-abi,omitempty"` // roughly equivalent to -mabi= flag GOOS string `json:"goos"`
Features string `json:"features,omitempty"` GOARCH string `json:"goarch"`
GOOS string `json:"goos,omitempty"` BuildTags []string `json:"build-tags"`
GOARCH string `json:"goarch,omitempty"` GC string `json:"gc"`
SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc) Scheduler string `json:"scheduler"`
BuildTags []string `json:"build-tags,omitempty"` Serial string `json:"serial"` // which serial output to use (uart, usb, none)
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified) Linker string `json:"linker"`
GC string `json:"gc,omitempty"` RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
Scheduler string `json:"scheduler,omitempty"` Libc string `json:"libc"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
Linker string `json:"linker,omitempty"` DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin) CFlags []string `json:"cflags"`
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) LDFlags []string `json:"ldflags"`
Libc string `json:"libc,omitempty"` LinkerScript string `json:"linkerscript"`
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. ExtraFiles []string `json:"extra-files"`
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time. RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
CFlags []string `json:"cflags,omitempty"` Emulator string `json:"emulator"`
LDFlags []string `json:"ldflags,omitempty"` FlashCommand string `json:"flash-command"`
LinkerScript string `json:"linkerscript,omitempty"` GDB []string `json:"gdb"`
ExtraFiles []string `json:"extra-files,omitempty"` PortReset string `json:"flash-1200-bps-reset"`
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear. FlashMethod string `json:"flash-method"`
Emulator string `json:"emulator,omitempty"` FlashVolume string `json:"msd-volume-name"`
FlashCommand string `json:"flash-command,omitempty"` FlashFilename string `json:"msd-firmware-name"`
GDB []string `json:"gdb,omitempty"` UF2FamilyID string `json:"uf2-family-id"`
PortReset string `json:"flash-1200-bps-reset,omitempty"` BinaryFormat string `json:"binary-format"`
SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid" OpenOCDInterface string `json:"openocd-interface"`
FlashMethod string `json:"flash-method,omitempty"` OpenOCDTarget string `json:"openocd-target"`
FlashVolume []string `json:"msd-volume-name,omitempty"` OpenOCDTransport string `json:"openocd-transport"`
FlashFilename string `json:"msd-firmware-name,omitempty"` OpenOCDCommands []string `json:"openocd-commands"`
UF2FamilyID string `json:"uf2-family-id,omitempty"` OpenOCDVerify *bool `json:"openocd-verify"` // enable verify when flashing with openocd
BinaryFormat string `json:"binary-format,omitempty"` JLinkDevice string `json:"jlink-device"`
OpenOCDInterface string `json:"openocd-interface,omitempty"` CodeModel string `json:"code-model"`
OpenOCDTarget string `json:"openocd-target,omitempty"` RelocationModel string `json:"relocation-model"`
OpenOCDTransport string `json:"openocd-transport,omitempty"` WasmAbi string `json:"wasm-abi"`
OpenOCDCommands []string `json:"openocd-commands,omitempty"`
OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd
JLinkDevice string `json:"jlink-device,omitempty"`
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
ADBPushRemote string `json:"adb-push-remote,omitempty"`
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
ProbeRSChip string `json:"probe-rs-chip,omitempty"`
CodeModel string `json:"code-model,omitempty"`
RelocationModel string `json:"relocation-model,omitempty"`
WITPackage string `json:"wit-package,omitempty"`
WITWorld string `json:"wit-world,omitempty"`
} }
// 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.
@@ -95,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)
@@ -154,11 +139,6 @@ func (spec *TargetSpec) loadFromGivenStr(str string) error {
// resolveInherits loads inherited targets, recursively. // resolveInherits loads inherited targets, recursively.
func (spec *TargetSpec) resolveInherits() error { func (spec *TargetSpec) resolveInherits() error {
// Save InheritableOnly before resolving, since it must not propagate
// from parent to child (a board target should not become inheritable-only
// just because its parent processor target is).
inheritableOnly := spec.InheritableOnly
// First create a new spec with all the inherited properties. // First create a new spec with all the inherited properties.
newSpec := &TargetSpec{} newSpec := &TargetSpec{}
for _, name := range spec.Inherits { for _, name := range spec.Inherits {
@@ -184,31 +164,57 @@ func (spec *TargetSpec) resolveInherits() error {
} }
*spec = *newSpec *spec = *newSpec
// Restore InheritableOnly from the original spec, not from parents.
spec.InheritableOnly = inheritableOnly
return nil return nil
} }
// Load a target specification. // Load a target specification.
func LoadTarget(options *Options) (*TargetSpec, error) { func LoadTarget(options *Options) (*TargetSpec, error) {
if options.Target == "" && options.GOARCH == "wasm" {
// Set a specific target if we're building from a known GOOS/GOARCH
// combination that is defined in a target JSON file.
switch options.GOOS {
case "js":
options.Target = "wasm"
case "wasip1":
options.Target = "wasip1"
case "wasip2":
options.Target = "wasip2"
default:
return nil, errors.New("GOARCH=wasm but GOOS is not set correctly. Please set GOOS to js, wasip1, or wasip2.")
}
}
if options.Target == "" { if options.Target == "" {
return defaultTarget(options) // Configure based on GOOS/GOARCH environment variables (falling back to
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
var llvmarch string
switch options.GOARCH {
case "386":
llvmarch = "i386"
case "amd64":
llvmarch = "x86_64"
case "arm64":
llvmarch = "aarch64"
case "arm":
switch options.GOARM {
case "5":
llvmarch = "armv5"
case "6":
llvmarch = "armv6"
case "7":
llvmarch = "armv7"
default:
return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM)
}
default:
llvmarch = options.GOARCH
}
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.
@@ -232,315 +238,93 @@ 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.InheritableOnly {
// Skip targets that are only meant to be inherited from, not used directly.
continue
}
if spec.FlashMethod == "" && spec.FlashCommand == "" && spec.Emulator == "" {
// This doesn't look like a regular target file, but rather like
// a parent target (such as targets/cortex-m.json).
continue
}
name := entry.Name()
name = name[:len(name)-5]
maps[name] = spec
}
return maps, nil
}
// Load a target from environment variables (which default to
// runtime.GOOS/runtime.GOARCH).
func defaultTarget(options *Options) (*TargetSpec, error) {
spec := TargetSpec{ spec := TargetSpec{
GOOS: options.GOOS, Triple: triple,
GOARCH: options.GOARCH, GOOS: goos,
BuildTags: []string{options.GOOS, options.GOARCH}, GOARCH: goarch,
BuildTags: []string{goos, goarch},
Scheduler: "tasks",
Linker: "cc", Linker: "cc",
DefaultStackSize: 1024 * 64, // 64kB DefaultStackSize: 1024 * 64, // 64kB
GDB: []string{"gdb"}, GDB: []string{"gdb"},
PortReset: "false", PortReset: "false",
} }
switch goarch {
// Configure target based on GOARCH.
var llvmarch string
switch options.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 options.GOOS == "darwin" {
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a"
// Looks like Apple prefers to call this architecture ARM64
// instead of AArch64.
llvmarch = "arm64"
} else if options.GOOS == "windows" {
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv"
} else { // linux
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics"
}
case "mips", "mipsle":
spec.CPU = "mips32"
spec.CFlags = append(spec.CFlags, "-fno-pic")
if options.GOARCH == "mips" {
llvmarch = "mips" // big endian
} else {
llvmarch = "mipsel" // little endian
}
switch options.GOMIPS {
case "hardfloat":
spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls"
case "softfloat":
spec.SoftFloat = true
spec.Features = "+mips32,+soft-float,-noabicalls"
spec.CFlags = append(spec.CFlags, "-msoft-float")
default:
return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS)
}
case "wasm":
return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to js, wasip1, or wasip2.")
default:
return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH)
} }
if goos == "darwin" {
// Configure target based on GOOS.
llvmos := options.GOOS
llvmvendor := "unknown"
switch options.GOOS {
case "darwin":
spec.GC = "boehm"
platformVersion := "10.12.0"
if options.GOARCH == "arm64" {
platformVersion = "11.0.0" // first macosx platform with arm64 support
}
llvmvendor = "apple"
spec.Scheduler = "threads"
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/internal/futex/futex_darwin.c",
"src/internal/task/task_threads.c",
"src/runtime/os_darwin.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "linux":
spec.GC = "boehm"
spec.Scheduler = "threads"
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 options.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/internal/futex/futex_linux.c",
"src/internal/task/task_threads.c",
"src/runtime/runtime_unix.c",
"src/runtime/signal.c")
case "windows":
spec.GC = "boehm"
spec.Scheduler = "tasks"
spec.Linker = "ld.lld" spec.Linker = "ld.lld"
spec.Libc = "mingw-w64" spec.Libc = "mingw-w64"
switch options.GOARCH { // Note: using a medium code model, low image base and no ASLR
case "386": // because Go doesn't really need those features. ASLR patches
spec.LDFlags = append(spec.LDFlags, // around issues for unsafe languages like C/C++ that are not
"-m", "i386pe", // normally present in Go (without explicitly opting in).
"--major-os-version", "4", // For more discussion:
"--major-subsystem-version", "4", // https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
)
// __udivdi3 is not present in ucrt it seems.
spec.RTLib = "compiler-rt"
case "amd64":
// Note: using a medium code model, low image base and no ASLR
// because Go doesn't really need those features. ASLR patches
// around issues for unsafe languages like C/C++ that are not
// normally present in Go (without explicitly opting in).
// For more discussion:
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
spec.LDFlags = append(spec.LDFlags,
"-m", "i386pep",
"--image-base", "0x400000",
)
spec.RTLib = "compiler-rt"
case "arm64":
spec.LDFlags = append(spec.LDFlags,
"-m", "arm64pe",
)
spec.RTLib = "compiler-rt"
}
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",
) )
spec.ExtraFiles = append(spec.ExtraFiles, } else {
"src/runtime/runtime_windows.c") spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
case "wasm", "wasip1", "wasip2":
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
default:
return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS)
} }
if goarch != "wasm" {
if spec.GC == "boehm" {
// Add this file only when needed. This fixes a build failure on
// Windows.
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_boehm.c")
}
// Target triples (which actually have four components, but are called
// triples for historical reasons) have the form:
// arch-vendor-os-environment
spec.Triple = llvmarch + "-" + llvmvendor + "-" + llvmos
if options.GOOS == "windows" {
spec.Triple += "-gnu"
} else if options.GOOS == "linux" {
// We use musl on Linux (not glibc) so we should use -musleabi* instead
// of -gnueabi*.
// The *hf suffix selects between soft/hard floating point ABI.
if spec.SoftFloat {
spec.Triple += "-musleabi"
} else {
spec.Triple += "-musleabihf"
}
}
// Add extra assembly files (needed for the scheduler etc).
if options.GOARCH != "wasm" {
suffix := "" suffix := ""
if options.GOOS == "windows" && options.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 := options.GOARCH spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+goarch+suffix+".S")
if options.GOARCH == "mips" || options.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")
} }
if goarch != runtime.GOARCH {
// Configure the emulator.
if options.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"}
if options.GOOS == "linux" { if goos == "linux" {
switch options.GOARCH { switch goarch {
case "386": case "386":
// amd64 can _usually_ run 32-bit programs, so skip the emulator in that case. // amd64 can _usually_ run 32-bit programs, so skip the emulator in that case.
if runtime.GOARCH != "amd64" { if runtime.GOARCH != "amd64" {
@@ -552,19 +336,14 @@ func defaultTarget(options *Options) (*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 {}"
} }
} }
} }
if options.GOOS != runtime.GOOS { if goos != runtime.GOOS {
if options.GOOS == "windows" { if goos == "windows" {
spec.Emulator = "wine {}" spec.Emulator = "wine {}"
} }
} }
return &spec, nil return &spec, nil
} }
-80
View File
@@ -23,37 +23,6 @@ func TestLoadTarget(t *testing.T) {
} }
} }
func TestGetTargetSpecs_InheritableOnlyTargetsExcluded(t *testing.T) {
specs, err := GetTargetSpecs()
if err != nil {
t.Fatal("GetTargetSpecs failed:", err)
}
// Inheritable-only processor-level targets should not appear in the listing.
inheritableOnlyTargets := []string{"esp32", "esp32c3", "esp32s3", "esp8266", "rp2040", "rp2350", "rp2350b"}
for _, name := range inheritableOnlyTargets {
if _, ok := specs[name]; ok {
t.Errorf("inheritable-only target %q should not appear in GetTargetSpecs", name)
}
}
// Board targets that inherit from inheritable-only targets should still appear.
boardTargets := []string{"esp32-coreboard-v2", "pico"}
for _, name := range boardTargets {
if _, ok := specs[name]; !ok {
t.Errorf("board target %q should appear in GetTargetSpecs", name)
}
}
}
func TestLoadTarget_InheritableOnlyTargetStillLoadable(t *testing.T) {
// Inheritable-only targets should still be loadable directly (for building).
_, err := LoadTarget(&Options{Target: "esp32"})
if err != nil {
t.Errorf("LoadTarget should still load inheritable-only target esp32: %v", err)
}
}
func TestOverrideProperties(t *testing.T) { func TestOverrideProperties(t *testing.T) {
baseAutoStackSize := true baseAutoStackSize := true
base := &TargetSpec{ base := &TargetSpec{
@@ -112,52 +81,3 @@ func TestOverrideProperties(t *testing.T) {
} }
} }
func TestConfigLinkerFlavor(t *testing.T) {
tests := []struct {
name string
target *TargetSpec
goos string
want string
}{
{
name: "default gnu",
target: &TargetSpec{},
goos: "linux",
want: "gnu",
},
{
name: "default coff",
target: &TargetSpec{},
goos: "windows",
want: "coff",
},
{
name: "default darwin",
target: &TargetSpec{},
goos: "darwin",
want: "darwin",
},
{
name: "target override",
target: &TargetSpec{
LinkerFlavor: "coff",
},
goos: "linux",
want: "coff",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.target.GOOS = tc.goos
config := &Config{
Options: &Options{},
Target: tc.target,
}
if got := config.LinkerFlavor(); got != tc.want {
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
}
})
}
}
-12
View File
@@ -1,12 +0,0 @@
//go:build llvm22
package compileopts
import "strings"
// ClangTriple returns the target triple to pass to Clang's --target flag.
// LLVM 22 deprecated the "wasm32-unknown-wasi" triple in favor of
// "wasm32-unknown-wasip1", so we substitute it here to avoid warnings.
func ClangTriple(triple string) string {
return strings.Replace(triple, "wasm32-unknown-wasi", "wasm32-unknown-wasip1", 1)
}
-9
View File
@@ -1,9 +0,0 @@
//go:build !llvm22
package compileopts
// ClangTriple returns the target triple to pass to Clang's --target flag.
// For pre-LLVM 22, the triple is used as-is.
func ClangTriple(triple string) string {
return triple
}
+8 -13
View File
@@ -16,18 +16,13 @@ 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",
"internal/chacha8rand.block": "internal/chacha8rand.block_generic",
// 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",
@@ -57,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 {
+19 -33
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.
@@ -241,35 +240,22 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
} }
} }
faultBlock := b.getRuntimeAssertBlock(blockPrefix, assertFunc) // Put the fault block at the end of the function and the next block at the
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(blockPrefix + ".next") nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock) b.CreateCondBr(assert, faultBlock, nextBlock)
// Ok: assert didn't trigger so continue normally. // Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(nextBlock) b.SetInsertPointAtEnd(faultBlock)
}
func (b *builder) getRuntimeAssertBlock(blockPrefix, assertFunc string) llvm.BasicBlock {
if b.runtimeAssertBlocks == nil {
b.runtimeAssertBlocks = make(map[string]llvm.BasicBlock)
}
if block := b.runtimeAssertBlocks[assertFunc]; !block.IsNil() {
return block
}
savedBlock := b.GetInsertBlock()
block := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
b.runtimeAssertBlocks[assertFunc] = block
b.SetInsertPointAtEnd(block)
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall(assertFunc, nil, "") b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable() b.CreateUnreachable()
b.SetInsertPointAtEnd(savedBlock)
return block // Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
} }
// extendInteger extends the value to at least targetType using a zero or sign // extendInteger extends the value to at least targetType using a zero or sign
+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
+46 -105
View File
@@ -19,9 +19,8 @@ const maxFieldsPerParam = 3
// useful while declaring or defining a function. // useful while declaring or defining a function.
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
flags paramFlags // extra flags for this parameter
} }
// paramFlags identifies parameter attributes for flags. Most importantly, it // paramFlags identifies parameter attributes for flags. Most importantly, it
@@ -29,34 +28,24 @@ type paramInfo struct {
type paramFlags uint8 type paramFlags uint8
const ( const (
// Whether this is a full or partial Go parameter (int, slice, etc). // Parameter may have the deferenceable_or_null attribute. This attribute
// The extra context parameter is not a Go parameter. // cannot be applied to unsafe.Pointer and to the data pointer of slices.
paramIsGoParam = 1 << iota paramIsDeferenceableOrNull = 1 << iota
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
// Whether this parameter is passed through backing storage.
paramIsIndirect
) )
// 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
@@ -76,47 +65,27 @@ 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...)
} }
call := b.CreateCall(fnType, fn, expanded, name) return b.CreateCall(fn, expanded, name)
if !fn.IsAFunction().IsNil() {
if cc := fn.FunctionCallConv(); cc != llvm.CCallConv {
// Set a different calling convention if needed.
// This is needed for GetModuleHandleExA on Windows, for example.
call.SetInstructionCallConv(cc)
}
}
return call
} }
// 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
// parameter list. // parameter list.
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo { func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
if c.isIndirectAggregate(t) {
return []paramInfo{{
llvmType: c.dataPtrType,
name: name,
elemSize: c.targetData.TypeAllocSize(t),
flags: paramIsGoParam | paramIsReadonly | paramIsIndirect,
}}
}
return c.expandDirectFormalParamType(t, name, goType)
}
func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() { switch t.TypeKind() {
case llvm.StructTypeKind: case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType) fieldInfos := c.flattenAggregateType(t, name, goType)
@@ -127,39 +96,13 @@ func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string,
// 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,
func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type { name: name,
if c.isIndirectParam(t, exported) { flags: getTypeFlags(goType),
return c.dataPtrType },
} }
return t
}
func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool {
return !exported && c.isIndirectAggregate(t)
}
func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type {
for _, value := range values {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(value.Type()), exported))
}
return valueTypes
}
func (b *builder) appendStoredParamTypes(valueTypes []llvm.Type, params []*types.Var, exported bool) []llvm.Type {
for _, param := range params {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(param.Type()), exported))
}
return valueTypes
}
func (b *builder) prependIndirectResult(sig *types.Signature, exported bool, params []llvm.Value, name string) []llvm.Value {
if resultType, indirect := b.hasIndirectResult(sig); !exported && indirect {
return append([]llvm.Value{b.createIndirectStorage(resultType, name)}, params...)
}
return params
} }
// expandFormalParamOffsets returns a list of offsets from the start of an // expandFormalParamOffsets returns a list of offsets from the start of an
@@ -209,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
@@ -217,7 +161,6 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
continue continue
} }
suffix := strconv.Itoa(i) suffix := strconv.Itoa(i)
isString := false
if goType != nil { if goType != nil {
// Try to come up with a good suffix for this struct field, // Try to come up with a good suffix for this struct field,
// depending on which Go type it's based on. // depending on which Go type it's based on.
@@ -234,48 +177,46 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
suffix = []string{"r", "i"}[i] suffix = []string{"r", "i"}[i]
case types.String: case types.String:
suffix = []string{"data", "len"}[i] suffix = []string{"data", "len"}[i]
isString = true
} }
case *types.Signature: case *types.Signature:
suffix = []string{"context", "funcptr"}[i] suffix = []string{"context", "funcptr"}[i]
} }
} }
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i)) subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
if isString { for i := range subInfos {
subInfos[0].flags |= paramIsReadonly 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,
flags: paramIsGoParam,
} }
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
@@ -298,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.
+72 -71
View File
@@ -4,9 +4,7 @@ package compiler
// or pseudo-operations that are lowered during goroutine lowering. // or pseudo-operations that are lowered during goroutine lowering.
import ( import (
"fmt"
"go/types" "go/types"
"math"
"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"
@@ -16,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, "")
@@ -29,55 +27,81 @@ 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)
// 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 storage valueStorage var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize { if isZeroSize {
storage.ptr = llvm.ConstNull(b.dataPtrType) valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else { } else {
storage = b.getValueStorage(instr.X, "chan.value") valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
} }
// Allocate buffer for the channel operation. // Allocate blockedlist buffer.
channelOp := b.getLLVMRuntimeType("channelOp") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the send. // Do the send.
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, channelOpAlloca}, "") 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(channelOpAlloca, channelOpAllocaSize) b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
b.endValueStorage(storage) if !isZeroSize {
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
} }
// createChanRecv emits a pseudo chan receive operation. It is lowered to the // createChanRecv emits a pseudo chan receive operation. It is lowered to the
// actual channel receive operation during goroutine lowering. // actual channel receive operation during goroutine lowering.
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value { func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem()) valueType := b.getLLVMType(unop.X.Type().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.
result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan") isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
} else {
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate buffer for the channel operation. // Allocate blockedlist buffer.
channelOp := b.getLLVMRuntimeType("channelOp") channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
// Do the receive. // Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "") commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
received := result.finish(b, commaOk, "chan.received") var received llvm.Value
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize) if isZeroSize {
return received received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
}
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
} }
// createChanClose closes the given channel. // createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) { func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "") b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
} }
// createSelect emits all IR necessary for a select statements. That's a // createSelect emits all IR necessary for a select statements. That's a
@@ -102,20 +126,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
} }
} }
const maxSelectStates = math.MaxUint32 >> 2
if len(expr.States) > maxSelectStates {
// The runtime code assumes that the number of state must fit in 30 bits
// (so the select index can be stored in a uint32 with two bits reserved
// for other purposes). It seems unlikely that a real program would have
// that many states, but we check for this case anyway to be sure.
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
// operations which aren't available everywhere.
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
// Continue as usual (we'll generate broken code but the error will
// prevent the compilation to complete).
}
// This code create a (stack-allocated) slice containing all the select // This code create a (stack-allocated) slice containing all the select
// cases and then calls runtime.chanSelect to perform the actual select // cases and then calls runtime.chanSelect to perform the actual select
// statement. // statement.
@@ -130,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 {
@@ -146,8 +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.
alloca := b.getSelectSendStorage(state.Send) sendValue := b.getValue(state.Send)
selectState = b.CreateInsertValue(selectState, alloca, 1, "") alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
default: default:
panic("unreachable") panic("unreachable")
} }
@@ -155,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")
@@ -168,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")
@@ -188,10 +201,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
if expr.Blocking { if expr.Blocking {
// 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.
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates)) chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca") chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false) chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []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")
@@ -199,23 +212,20 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
results = b.createRuntimeCall("chanSelect", []llvm.Value{ results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf, recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
}, "select.result") }, "select.result")
// Terminate the lifetime of the operation structures. // Terminate the lifetime of the operation structures.
b.emitLifetimeEnd(opsAlloca, opsSize) b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
} else { } else {
opsPtr := llvm.ConstNull(b.dataPtrType) results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
results = b.createRuntimeCall("chanSelect", []llvm.Value{
recvbuf, recvbuf,
statesPtr, statesLen, statesLen, // []chanSelectState statesPtr, statesLen, statesLen, // []chanSelectState
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
}, "select.result") }, "select.result")
} }
// 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
@@ -237,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, "")
@@ -245,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
@@ -254,17 +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)]
return b.loadFromStorage(recvbuf, expr.Type(), "select.received") typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
ptr := b.CreateBitCast(recvbuf, typ, "")
return b.CreateLoad(ptr, "")
} }
} }
func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value {
typ := b.getLLVMType(value.Type())
if b.isIndirectAggregate(typ) {
return b.getValuePointer(value)
}
llvmValue := b.getValue(value, getPos(value))
ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value")
b.CreateStore(llvmValue, ptr)
return ptr
}

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