mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-30 16:48:40 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a3f96b221 |
@@ -0,0 +1,419 @@
|
|||||||
|
version: 2.1
|
||||||
|
|
||||||
|
commands:
|
||||||
|
submodules:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Pull submodules"
|
||||||
|
command: git submodule update --init
|
||||||
|
apt-dependencies:
|
||||||
|
parameters:
|
||||||
|
llvm:
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install apt dependencies"
|
||||||
|
command: |
|
||||||
|
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install \
|
||||||
|
llvm-<<parameters.llvm>>-dev \
|
||||||
|
clang-<<parameters.llvm>> \
|
||||||
|
libclang-<<parameters.llvm>>-dev \
|
||||||
|
lld-<<parameters.llvm>> \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-user \
|
||||||
|
gcc-avr \
|
||||||
|
avr-libc
|
||||||
|
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
|
||||||
|
install-node:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install node.js"
|
||||||
|
command: |
|
||||||
|
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
|
||||||
|
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||||
|
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||||
|
rm node-v10.15.1-linux-x64.tar.xz
|
||||||
|
install-chrome:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install Chrome"
|
||||||
|
command: |
|
||||||
|
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||||
|
sudo apt install ./google-chrome-stable_current_amd64.deb
|
||||||
|
install-wasmtime:
|
||||||
|
steps:
|
||||||
|
- run:
|
||||||
|
name: "Install wasmtime"
|
||||||
|
command: |
|
||||||
|
curl https://wasmtime.dev/install.sh -sSf | bash
|
||||||
|
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
|
||||||
|
install-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-11-v2
|
||||||
|
- run:
|
||||||
|
name: "Fetch LLVM source"
|
||||||
|
command: make llvm-source
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-source-11-v2
|
||||||
|
paths:
|
||||||
|
- llvm-project/clang/lib/Headers
|
||||||
|
- llvm-project/clang/include
|
||||||
|
- llvm-project/lld/include
|
||||||
|
- llvm-project/llvm/include
|
||||||
|
build-wasi-libc:
|
||||||
|
steps:
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- wasi-libc-sysroot-v4
|
||||||
|
- run:
|
||||||
|
name: "Build wasi-libc"
|
||||||
|
command: make wasi-libc
|
||||||
|
- save_cache:
|
||||||
|
key: wasi-libc-sysroot-v4
|
||||||
|
paths:
|
||||||
|
- lib/wasi-libc/sysroot
|
||||||
|
test-linux:
|
||||||
|
parameters:
|
||||||
|
llvm:
|
||||||
|
type: string
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- submodules
|
||||||
|
- apt-dependencies:
|
||||||
|
llvm: "<<parameters.llvm>>"
|
||||||
|
- install-node
|
||||||
|
- install-chrome
|
||||||
|
- install-wasmtime
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}
|
||||||
|
- llvm-source-linux
|
||||||
|
- run: go install -tags=llvm<<parameters.llvm>> .
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- wasi-libc-sysroot-systemclang-v3
|
||||||
|
- run: make wasi-libc
|
||||||
|
- save_cache:
|
||||||
|
key: wasi-libc-sysroot-systemclang-v3
|
||||||
|
paths:
|
||||||
|
- lib/wasi-libc/sysroot
|
||||||
|
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||||
|
- run: make gen-device -j4
|
||||||
|
- run: make smoketest XTENSA=0
|
||||||
|
- run: make tinygo-test
|
||||||
|
- run: make wasmtest
|
||||||
|
- save_cache:
|
||||||
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/go-build
|
||||||
|
- /go/pkg/mod
|
||||||
|
- run: make fmt-check
|
||||||
|
assert-test-linux:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- submodules
|
||||||
|
- run:
|
||||||
|
name: "Install apt dependencies"
|
||||||
|
command: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
libc6-dev-armel-cross \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-user \
|
||||||
|
gcc-avr \
|
||||||
|
avr-libc
|
||||||
|
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||||
|
- install-node
|
||||||
|
- install-wasmtime
|
||||||
|
- install-xtensa-toolchain:
|
||||||
|
variant: "linux-amd64"
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}
|
||||||
|
- llvm-source-linux
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- llvm-build-11-linux-v3-assert
|
||||||
|
- run:
|
||||||
|
name: "Build LLVM"
|
||||||
|
command: |
|
||||||
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
|
then
|
||||||
|
# fetch LLVM source
|
||||||
|
rm -rf llvm-project
|
||||||
|
make llvm-source
|
||||||
|
# install dependencies
|
||||||
|
sudo apt-get install cmake ninja-build
|
||||||
|
# hack ninja to use less jobs
|
||||||
|
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||||
|
chmod +x /go/bin/ninja
|
||||||
|
# build!
|
||||||
|
make ASSERT=1 llvm-build
|
||||||
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
fi
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-build-11-linux-v3-assert
|
||||||
|
paths:
|
||||||
|
llvm-build
|
||||||
|
- run: make ASSERT=1
|
||||||
|
- build-wasi-libc
|
||||||
|
- run:
|
||||||
|
name: "Test TinyGo"
|
||||||
|
command: make ASSERT=1 test
|
||||||
|
environment:
|
||||||
|
# Note: -p=2 limits parallelism to two jobs at a time, which is
|
||||||
|
# necessary to keep memory consumption down and avoid OOM (for a
|
||||||
|
# 2CPU/4GB executor).
|
||||||
|
GOFLAGS: -p=2
|
||||||
|
- save_cache:
|
||||||
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/go-build
|
||||||
|
- /go/pkg/mod
|
||||||
|
- run: make gen-device -j4
|
||||||
|
- run: make smoketest TINYGO=build/tinygo
|
||||||
|
build-linux:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- submodules
|
||||||
|
- run:
|
||||||
|
name: "Install apt dependencies"
|
||||||
|
command: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install \
|
||||||
|
gcc-arm-linux-gnueabihf \
|
||||||
|
libc6-dev-armel-cross \
|
||||||
|
gcc-aarch64-linux-gnu \
|
||||||
|
libc6-dev-arm64-cross \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-user \
|
||||||
|
gcc-avr \
|
||||||
|
avr-libc
|
||||||
|
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||||
|
- install-node
|
||||||
|
- install-wasmtime
|
||||||
|
- install-xtensa-toolchain:
|
||||||
|
variant: "linux-amd64"
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
|
- go-cache-v2-{{ checksum "go.mod" }}
|
||||||
|
- llvm-source-linux
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- llvm-build-11-linux-v3-noassert
|
||||||
|
- run:
|
||||||
|
name: "Build LLVM"
|
||||||
|
command: |
|
||||||
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
|
then
|
||||||
|
# fetch LLVM source
|
||||||
|
rm -rf llvm-project
|
||||||
|
make llvm-source
|
||||||
|
# install dependencies
|
||||||
|
sudo apt-get install cmake ninja-build
|
||||||
|
# hack ninja to use less jobs
|
||||||
|
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||||
|
chmod +x /go/bin/ninja
|
||||||
|
# build!
|
||||||
|
make llvm-build
|
||||||
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
fi
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-build-11-linux-v3-noassert
|
||||||
|
paths:
|
||||||
|
llvm-build
|
||||||
|
- build-wasi-libc
|
||||||
|
- run:
|
||||||
|
name: "Test TinyGo"
|
||||||
|
command: make test
|
||||||
|
- run:
|
||||||
|
name: "Install fpm"
|
||||||
|
command: |
|
||||||
|
sudo apt-get install ruby ruby-dev
|
||||||
|
sudo gem install --no-document fpm
|
||||||
|
- run:
|
||||||
|
name: "Build TinyGo release"
|
||||||
|
command: |
|
||||||
|
make release deb -j3
|
||||||
|
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||||
|
cp -p build/release.deb /tmp/tinygo_amd64.deb
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/tinygo_amd64.deb
|
||||||
|
- save_cache:
|
||||||
|
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/go-build
|
||||||
|
- /go/pkg/mod
|
||||||
|
- run:
|
||||||
|
name: "Extract release tarball"
|
||||||
|
command: |
|
||||||
|
mkdir -p ~/lib
|
||||||
|
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||||
|
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||||
|
tinygo version
|
||||||
|
- run: make smoketest
|
||||||
|
build-macos:
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- submodules
|
||||||
|
- run:
|
||||||
|
name: "Install dependencies"
|
||||||
|
command: |
|
||||||
|
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
|
||||||
|
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
|
||||||
|
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||||
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||||
|
- install-xtensa-toolchain:
|
||||||
|
variant: "macos"
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||||
|
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- llvm-source-11-macos-v2
|
||||||
|
- run:
|
||||||
|
name: "Fetch LLVM source"
|
||||||
|
command: make llvm-source
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-source-11-macos-v2
|
||||||
|
paths:
|
||||||
|
- llvm-project/clang/lib/Headers
|
||||||
|
- llvm-project/clang/include
|
||||||
|
- llvm-project/lld/include
|
||||||
|
- llvm-project/llvm/include
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- llvm-build-11-macos-v3
|
||||||
|
- run:
|
||||||
|
name: "Build LLVM"
|
||||||
|
command: |
|
||||||
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
|
then
|
||||||
|
# fetch LLVM source
|
||||||
|
rm -rf llvm-project
|
||||||
|
make llvm-source
|
||||||
|
# install dependencies
|
||||||
|
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||||
|
# build!
|
||||||
|
make llvm-build
|
||||||
|
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||||
|
fi
|
||||||
|
- save_cache:
|
||||||
|
key: llvm-build-11-macos-v3
|
||||||
|
paths:
|
||||||
|
llvm-build
|
||||||
|
- restore_cache:
|
||||||
|
keys:
|
||||||
|
- wasi-libc-sysroot-macos-v3
|
||||||
|
- run:
|
||||||
|
name: "Build wasi-libc"
|
||||||
|
command: make wasi-libc
|
||||||
|
- save_cache:
|
||||||
|
key: wasi-libc-sysroot-macos-v3
|
||||||
|
paths:
|
||||||
|
- lib/wasi-libc/sysroot
|
||||||
|
- run:
|
||||||
|
name: "Test TinyGo"
|
||||||
|
command: make test
|
||||||
|
- run:
|
||||||
|
name: "Build TinyGo release"
|
||||||
|
command: |
|
||||||
|
make release -j3
|
||||||
|
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
|
||||||
|
- store_artifacts:
|
||||||
|
path: /tmp/tinygo.darwin-amd64.tar.gz
|
||||||
|
- run:
|
||||||
|
name: "Extract release tarball"
|
||||||
|
command: |
|
||||||
|
mkdir -p ~/lib
|
||||||
|
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||||
|
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||||
|
tinygo version
|
||||||
|
- run: make smoketest AVR=0
|
||||||
|
- save_cache:
|
||||||
|
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||||
|
paths:
|
||||||
|
- ~/.cache/go-build
|
||||||
|
- /go/pkg/mod
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-llvm10-go113:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.13-buster
|
||||||
|
steps:
|
||||||
|
- test-linux:
|
||||||
|
llvm: "10"
|
||||||
|
test-llvm10-go114:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14-buster
|
||||||
|
steps:
|
||||||
|
- test-linux:
|
||||||
|
llvm: "10"
|
||||||
|
test-llvm11-go115:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.15-buster
|
||||||
|
steps:
|
||||||
|
- test-linux:
|
||||||
|
llvm: "11"
|
||||||
|
test-llvm11-go116:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.16-buster
|
||||||
|
steps:
|
||||||
|
- test-linux:
|
||||||
|
llvm: "11"
|
||||||
|
assert-test-linux:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14-stretch
|
||||||
|
steps:
|
||||||
|
- assert-test-linux
|
||||||
|
build-linux:
|
||||||
|
docker:
|
||||||
|
- image: circleci/golang:1.14-stretch
|
||||||
|
steps:
|
||||||
|
- build-linux
|
||||||
|
build-macos:
|
||||||
|
macos:
|
||||||
|
xcode: "10.1.0"
|
||||||
|
steps:
|
||||||
|
- build-macos
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
workflows:
|
||||||
|
test-all:
|
||||||
|
jobs:
|
||||||
|
- test-llvm10-go113
|
||||||
|
- test-llvm10-go114
|
||||||
|
- test-llvm11-go115
|
||||||
|
- test-llvm11-go116
|
||||||
|
- build-linux
|
||||||
|
- build-macos
|
||||||
|
- assert-test-linux
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
build/
|
|
||||||
llvm-*/
|
|
||||||
.github
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# These are supported funding model platforms
|
|
||||||
|
|
||||||
open_collective: tinygo
|
|
||||||
@@ -1,145 +0,0 @@
|
|||||||
name: macOS
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-macos:
|
|
||||||
name: build-macos
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
# macos-14: arm64 (oldest supported version as of 18-11-2025)
|
|
||||||
# macos-15-intel: amd64 (last intel version to be supported by github runners)
|
|
||||||
# See https://github.com/actions/runner-images/issues/13046
|
|
||||||
os: [macos-14, macos-15-intel]
|
|
||||||
include:
|
|
||||||
- os: macos-14
|
|
||||||
goarch: arm64
|
|
||||||
- os: macos-15-intel
|
|
||||||
goarch: amd64
|
|
||||||
runs-on: ${{ matrix.os }}
|
|
||||||
steps:
|
|
||||||
- name: Install Dependencies
|
|
||||||
run: |
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Extract TinyGo version
|
|
||||||
id: version
|
|
||||||
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-${{ matrix.os }}-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: 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/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
|
|
||||||
with:
|
|
||||||
key: llvm-build-20-${{ matrix.os }}-v2
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# install dependencies
|
|
||||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install ninja
|
|
||||||
# build!
|
|
||||||
make llvm-build
|
|
||||||
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: make gen-device
|
|
||||||
run: make -j3 gen-device
|
|
||||||
- name: Test TinyGo
|
|
||||||
run: make test GOTESTFLAGS="-only-current-os"
|
|
||||||
- name: Build TinyGo release tarball
|
|
||||||
run: make release -j3
|
|
||||||
- name: Test stdlib packages
|
|
||||||
run: make tinygo-test
|
|
||||||
- name: Make release artifact
|
|
||||||
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
|
|
||||||
- name: Publish release artifact
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
|
|
||||||
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
|
|
||||||
archive: false
|
|
||||||
- name: Smoke tests
|
|
||||||
run: make smoketest TINYGO=$(PWD)/build/tinygo
|
|
||||||
test-macos-homebrew:
|
|
||||||
name: homebrew-install
|
|
||||||
runs-on: macos-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
version: [16, 17, 18, 19, 20]
|
|
||||||
steps:
|
|
||||||
- name: Set up Homebrew
|
|
||||||
uses: Homebrew/actions/setup-homebrew@main
|
|
||||||
- name: Fix Python symlinks
|
|
||||||
run: |
|
|
||||||
# Github runners have broken symlinks, so relink
|
|
||||||
# see: https://github.com/actions/setup-python/issues/577
|
|
||||||
brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done
|
|
||||||
- name: Install LLVM
|
|
||||||
run: |
|
|
||||||
brew install llvm@${{ matrix.version }}
|
|
||||||
brew link llvm@${{ matrix.version }}
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Build TinyGo (LLVM ${{ matrix.version }})
|
|
||||||
run: go install -tags=llvm${{ matrix.version }}
|
|
||||||
- name: Check binary
|
|
||||||
run: tinygo version
|
|
||||||
- name: Build TinyGo (default LLVM)
|
|
||||||
if: matrix.version == 20
|
|
||||||
run: go install
|
|
||||||
- name: Check binary
|
|
||||||
if: matrix.version == 20
|
|
||||||
run: tinygo version
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# This is the Github action to build and push the tinygo/tinygo-dev Docker image.
|
|
||||||
# If you are looking for the tinygo/tinygo "release" Docker image please see
|
|
||||||
# https://github.com/tinygo-org/docker
|
|
||||||
#
|
|
||||||
name: Docker
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ dev, fix-docker-llvm-build ]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
push_to_registry:
|
|
||||||
name: build-push-dev
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
packages: write
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- name: Free Disk space
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
df -h
|
|
||||||
sudo rm -rf /opt/hostedtoolcache
|
|
||||||
sudo rm -rf /usr/local/lib/android
|
|
||||||
sudo rm -rf /usr/share/dotnet
|
|
||||||
sudo rm -rf /opt/ghc
|
|
||||||
sudo rm -rf /usr/local/graalvm
|
|
||||||
sudo rm -rf /usr/local/share/boost
|
|
||||||
df -h
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
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/tinygo-dev
|
|
||||||
ghcr.io/${{ github.repository_owner }}/tinygo-dev
|
|
||||||
tags: |
|
|
||||||
type=sha,format=long
|
|
||||||
type=raw,value=latest
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v4
|
|
||||||
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:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
@@ -1,405 +0,0 @@
|
|||||||
name: Linux
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-linux:
|
|
||||||
# Build Linux binaries, ready for release.
|
|
||||||
# This runs inside an Alpine Linux container so we can more easily create a
|
|
||||||
# statically linked binary.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
container:
|
|
||||||
image: golang:1.26-alpine
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Install apk dependencies
|
|
||||||
# tar: needed for actions/cache@v5
|
|
||||||
# git+openssh: needed for checkout (I think?)
|
|
||||||
# ruby: needed to install fpm
|
|
||||||
run: apk add tar git openssh make g++ ruby-dev mold
|
|
||||||
- name: Work around CVE-2022-24765
|
|
||||||
# We're not on a multi-user machine, so this is safe.
|
|
||||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Extract TinyGo version
|
|
||||||
id: version
|
|
||||||
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
|
|
||||||
- name: Cache Go
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
key: go-cache-linux-alpine-v2-${{ hashFiles('go.mod') }}
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-linux-alpine-v2
|
|
||||||
path: |
|
|
||||||
llvm-project/clang/lib/Headers
|
|
||||||
llvm-project/clang/include
|
|
||||||
llvm-project/compiler-rt
|
|
||||||
llvm-project/lld/include
|
|
||||||
llvm-project/llvm/include
|
|
||||||
- name: Download LLVM source
|
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
|
||||||
run: make llvm-source
|
|
||||||
- name: 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/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
|
|
||||||
with:
|
|
||||||
key: llvm-build-20-linux-alpine-v2
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# install dependencies
|
|
||||||
apk add cmake samurai python3
|
|
||||||
# build!
|
|
||||||
make llvm-build
|
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
- name: 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
|
|
||||||
uses: actions/cache@v5
|
|
||||||
id: cache-binaryen
|
|
||||||
with:
|
|
||||||
key: binaryen-linux-alpine-v2
|
|
||||||
path: build/wasm-opt
|
|
||||||
- name: Build Binaryen
|
|
||||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
apk add cmake samurai python3
|
|
||||||
make binaryen STATIC=1
|
|
||||||
- name: Install fpm
|
|
||||||
run: |
|
|
||||||
gem install --version 4.0.7 public_suffix
|
|
||||||
gem install --version 2.7.6 dotenv
|
|
||||||
gem install --no-document fpm
|
|
||||||
- name: Run linter
|
|
||||||
run: make lint
|
|
||||||
- name: Run spellcheck
|
|
||||||
run: make spell
|
|
||||||
- name: Build TinyGo release
|
|
||||||
run: |
|
|
||||||
make release deb -j3 STATIC=1
|
|
||||||
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
|
|
||||||
- name: "Publish release artifact: tarball"
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
path: /tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
archive: false
|
|
||||||
- name: "Publish release artifact: Debian package"
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: tinygo_${{ steps.version.outputs.version }}_amd64.deb
|
|
||||||
path: /tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
|
|
||||||
archive: false
|
|
||||||
test-linux-build:
|
|
||||||
# Test the binaries built in the build-linux job by running the smoke tests.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: build-linux
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Install wasmtime
|
|
||||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
|
||||||
with:
|
|
||||||
version: "29.0.1"
|
|
||||||
- name: Install wasm-tools
|
|
||||||
uses: bytecodealliance/actions/wasm-tools/setup@v1
|
|
||||||
- name: Download release artifact
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
- name: Extract release tarball
|
|
||||||
run: |
|
|
||||||
mkdir -p ~/lib
|
|
||||||
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
|
|
||||||
- run: make tinygo-test-wasip1-fast
|
|
||||||
- run: make tinygo-test-wasip2-fast
|
|
||||||
- run: make tinygo-test-wasm
|
|
||||||
- run: make smoketest
|
|
||||||
assert-test-linux:
|
|
||||||
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
|
|
||||||
# potential bugs.
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Install apt dependencies
|
|
||||||
run: |
|
|
||||||
echo "Show cpuinfo; sometimes useful when troubleshooting"
|
|
||||||
cat /proc/cpuinfo
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends \
|
|
||||||
qemu-system-arm \
|
|
||||||
qemu-system-riscv32 \
|
|
||||||
qemu-user \
|
|
||||||
simavr \
|
|
||||||
ninja-build
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Install Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version: '18'
|
|
||||||
- name: Install wasmtime
|
|
||||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
|
||||||
with:
|
|
||||||
version: "29.0.1"
|
|
||||||
- name: Setup `wasm-tools`
|
|
||||||
uses: bytecodealliance/actions/wasm-tools/setup@v1
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-linux-asserts-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: 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/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
|
|
||||||
with:
|
|
||||||
key: llvm-build-20-linux-asserts-v1
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# build!
|
|
||||||
make llvm-build ASSERT=1
|
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
- name: 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
|
|
||||||
uses: actions/cache@v5
|
|
||||||
id: cache-binaryen
|
|
||||||
with:
|
|
||||||
key: binaryen-linux-asserts-v1
|
|
||||||
path: build/wasm-opt
|
|
||||||
- name: Build Binaryen
|
|
||||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
|
||||||
run: make binaryen
|
|
||||||
- run: make gen-device -j4
|
|
||||||
- name: Test TinyGo
|
|
||||||
run: make ASSERT=1 test
|
|
||||||
- name: Build TinyGo
|
|
||||||
run: |
|
|
||||||
make ASSERT=1
|
|
||||||
echo "$(pwd)/build" >> $GITHUB_PATH
|
|
||||||
- name: Test stdlib packages
|
|
||||||
run: make tinygo-test
|
|
||||||
- run: make smoketest
|
|
||||||
- run: make wasmtest
|
|
||||||
- run: make tinygo-test-baremetal
|
|
||||||
build-linux-cross:
|
|
||||||
# Build ARM Linux binaries, ready for release.
|
|
||||||
# This intentionally uses an older Linux image, so that we compile against
|
|
||||||
# an older glibc version and therefore are compatible with a wide range of
|
|
||||||
# Linux distributions.
|
|
||||||
# It is set to "needs: build-linux" because it modifies the release created
|
|
||||||
# in that process to avoid doing lots of duplicate work and to avoid
|
|
||||||
# complications around precompiled libraries such as compiler-rt shipped as
|
|
||||||
# part of the release tarball.
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
goarch: [ arm, arm64 ]
|
|
||||||
include:
|
|
||||||
- goarch: arm64
|
|
||||||
toolchain: aarch64-linux-gnu
|
|
||||||
libc: arm64
|
|
||||||
- goarch: arm
|
|
||||||
toolchain: arm-linux-gnueabihf
|
|
||||||
libc: armhf
|
|
||||||
runs-on: ubuntu-22.04 # note: use the oldest image available! (see above)
|
|
||||||
needs: build-linux
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Get TinyGo version
|
|
||||||
id: version
|
|
||||||
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
|
|
||||||
- name: Install apt dependencies
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends \
|
|
||||||
qemu-user \
|
|
||||||
g++-${{ matrix.toolchain }} \
|
|
||||||
libc6-dev-${{ matrix.libc }}-cross
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-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: 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/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
|
|
||||||
with:
|
|
||||||
key: llvm-build-20-linux-${{ matrix.goarch }}-v1
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# Install build dependencies.
|
|
||||||
sudo apt-get install --no-install-recommends ninja-build
|
|
||||||
# build!
|
|
||||||
make llvm-build CROSS=${{ matrix.toolchain }}
|
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
- name: Save LLVM build cache
|
|
||||||
uses: actions/cache/save@v5
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
with:
|
|
||||||
key: ${{ steps.cache-llvm-build.outputs.cache-primary-key }}
|
|
||||||
path: llvm-build
|
|
||||||
- name: Cache Binaryen
|
|
||||||
uses: actions/cache@v5
|
|
||||||
id: cache-binaryen
|
|
||||||
with:
|
|
||||||
key: binaryen-linux-${{ matrix.goarch }}-v4
|
|
||||||
path: build/wasm-opt
|
|
||||||
- name: Build Binaryen
|
|
||||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
sudo apt-get install --no-install-recommends ninja-build
|
|
||||||
git submodule update --init lib/binaryen
|
|
||||||
make CROSS=${{ matrix.toolchain }} binaryen
|
|
||||||
- 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=${{ matrix.toolchain }}
|
|
||||||
- name: Download amd64 release
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
|
||||||
- name: Extract amd64 release
|
|
||||||
run: |
|
|
||||||
mkdir -p build/release
|
|
||||||
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
|
|
||||||
- name: Modify release
|
|
||||||
run: |
|
|
||||||
cp -p build/tinygo build/release/tinygo/bin
|
|
||||||
cp -p build/wasm-opt build/release/tinygo/bin
|
|
||||||
- name: Create ${{ matrix.goarch }} release
|
|
||||||
run: |
|
|
||||||
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
|
|
||||||
cp -p build/release.tar.gz /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
|
|
||||||
cp -p build/release.deb /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
|
|
||||||
- name: "Publish release artifact: tarball"
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
|
|
||||||
path: /tmp/tinygo${{ needs.build-linux.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
|
|
||||||
archive: false
|
|
||||||
- name: "Publish release artifact: Debian package"
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: linux-${{ matrix.goarch }}-double-zipped-${{ needs.build-linux.outputs.version }}
|
|
||||||
path: /tmp/tinygo_${{ needs.build-linux.outputs.version }}_${{ matrix.libc }}.deb
|
|
||||||
archive: false
|
|
||||||
@@ -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
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
name: Nix
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nix-test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Uninstall system LLVM
|
|
||||||
# Hack to work around issue where we still include system headers for
|
|
||||||
# some reason.
|
|
||||||
# See: https://github.com/tinygo-org/tinygo/pull/4516#issuecomment-2416363668
|
|
||||||
run: sudo apt-get remove llvm-18
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Pull musl, bdwgc
|
|
||||||
run: |
|
|
||||||
git submodule update --init lib/musl lib/bdwgc
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-linux-nix-v1
|
|
||||||
path: |
|
|
||||||
llvm-project/compiler-rt
|
|
||||||
- name: Download LLVM source
|
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
|
||||||
run: make llvm-source
|
|
||||||
- name: Save LLVM source cache
|
|
||||||
uses: actions/cache/save@v5
|
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
|
||||||
with:
|
|
||||||
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
|
|
||||||
path: |
|
|
||||||
llvm-project/compiler-rt
|
|
||||||
- uses: cachix/install-nix-action@v31
|
|
||||||
- name: Test
|
|
||||||
run: |
|
|
||||||
nix develop --ignore-environment --keep HOME --command bash -c "go install && ~/go/bin/tinygo version && ~/go/bin/tinygo build -o test ./testdata/cgo"
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
# Command that's part of sizediff.yml. This is put in a separate file so that it
|
|
||||||
# still works after checking out the dev branch (that is, when going from LLVM
|
|
||||||
# 16 to LLVM 17 for example, both Clang 16 and Clang 17 are installed).
|
|
||||||
|
|
||||||
echo 'deb https://apt.llvm.org/noble/ llvm-toolchain-noble-20 main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
|
||||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install --no-install-recommends -y \
|
|
||||||
llvm-20-dev \
|
|
||||||
clang-20 \
|
|
||||||
libclang-20-dev \
|
|
||||||
lld-20
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
name: Binary size difference
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
sizediff:
|
|
||||||
# Note: when updating the Ubuntu version, also update the Ubuntu version in
|
|
||||||
# sizediff-install-pkgs.sh
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
permissions:
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
# Prepare, install tools
|
|
||||||
- name: Add GOBIN to $PATH
|
|
||||||
run: |
|
|
||||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # fetch all history (no sparse checkout)
|
|
||||||
submodules: true
|
|
||||||
- name: Install apt dependencies
|
|
||||||
run: ./.github/workflows/sizediff-install-pkgs.sh
|
|
||||||
- name: Restore LLVM source cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-sizediff-v1
|
|
||||||
path: |
|
|
||||||
llvm-project/compiler-rt
|
|
||||||
- name: Download LLVM source
|
|
||||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
|
||||||
run: make llvm-source
|
|
||||||
- name: Cache Go
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
key: go-cache-linux-sizediff-v2-${{ hashFiles('go.mod') }}
|
|
||||||
path: |
|
|
||||||
~/.cache/go-build
|
|
||||||
~/go/pkg/mod
|
|
||||||
- run: make gen-device -j4
|
|
||||||
- name: Download drivers repo
|
|
||||||
run: git clone https://github.com/tinygo-org/drivers.git
|
|
||||||
- name: Save HEAD
|
|
||||||
run: git branch github-actions-saved-HEAD HEAD
|
|
||||||
|
|
||||||
# Compute sizes for the PR branch
|
|
||||||
- name: Build tinygo binary for the PR branch
|
|
||||||
run: go install
|
|
||||||
- name: Determine binary sizes on the PR branch
|
|
||||||
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-pr.txt)
|
|
||||||
|
|
||||||
# Compute sizes for the dev branch
|
|
||||||
- name: Checkout dev branch
|
|
||||||
run: |
|
|
||||||
git reset --hard origin/dev
|
|
||||||
git checkout --no-recurse-submodules `git merge-base HEAD origin/dev`
|
|
||||||
- name: Install apt dependencies on the dev branch
|
|
||||||
# this is only needed on a PR that changes the LLVM version
|
|
||||||
run: ./.github/workflows/sizediff-install-pkgs.sh
|
|
||||||
- name: Build tinygo binary for the dev branch
|
|
||||||
run: go install
|
|
||||||
- name: Determine binary sizes on the dev branch
|
|
||||||
run: (cd drivers; make smoke-test XTENSA=0 | tee sizes-dev.txt)
|
|
||||||
|
|
||||||
# Create comment
|
|
||||||
# TODO: add a summary, something like:
|
|
||||||
# - overall size difference (percent)
|
|
||||||
# - number of binaries that grew / shrank / remained the same
|
|
||||||
# - don't show the full diff when no binaries changed
|
|
||||||
- name: Calculate size diff
|
|
||||||
run: ./tools/sizediff drivers/sizes-dev.txt drivers/sizes-pr.txt | tee sizediff.txt
|
|
||||||
- name: Create comment
|
|
||||||
run: |
|
|
||||||
echo "Size difference with the dev branch:" > comment.txt
|
|
||||||
echo "<details><summary>Binary size difference</summary>" >> comment.txt
|
|
||||||
echo "<pre>" >> comment.txt
|
|
||||||
cat sizediff.txt >> comment.txt
|
|
||||||
echo "</pre></details>" >> comment.txt
|
|
||||||
- name: Comment contents
|
|
||||||
run: cat comment.txt
|
|
||||||
- name: Add comment
|
|
||||||
if: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
|
|
||||||
uses: thollander/actions-comment-pull-request@v2.3.1
|
|
||||||
with:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
filePath: comment.txt
|
|
||||||
comment_tag: sizediff
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# Extract the version string from the source code, to be stored in a variable.
|
|
||||||
grep 'const version' goenv/version.go | sed 's/^const version = "\(.*\)"$/version=\1/g'
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
name: Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- dev
|
|
||||||
- release
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-windows:
|
|
||||||
runs-on: windows-2022
|
|
||||||
outputs:
|
|
||||||
version: ${{ steps.version.outputs.version }}
|
|
||||||
steps:
|
|
||||||
- name: Configure pagefile
|
|
||||||
uses: al-cheb/configure-pagefile-action@v1.4
|
|
||||||
with:
|
|
||||||
minimum-size: 8GB
|
|
||||||
maximum-size: 24GB
|
|
||||||
disk-root: "C:"
|
|
||||||
- uses: MinoruSekine/setup-scoop@v4
|
|
||||||
- name: Install Dependencies
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
scoop config use_external_7zip true
|
|
||||||
scoop install ninja binaryen
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
submodules: true
|
|
||||||
- name: Extract TinyGo version
|
|
||||||
id: version
|
|
||||||
shell: bash
|
|
||||||
run: ./.github/workflows/tinygo-extract-version.sh | tee -a "$GITHUB_OUTPUT"
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.2'
|
|
||||||
cache: true
|
|
||||||
- name: Restore cached LLVM source
|
|
||||||
uses: actions/cache/restore@v5
|
|
||||||
id: cache-llvm-source
|
|
||||||
with:
|
|
||||||
key: llvm-source-20-windows-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: Save cached LLVM source
|
|
||||||
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/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
|
|
||||||
with:
|
|
||||||
key: llvm-build-20-windows-v2
|
|
||||||
path: llvm-build
|
|
||||||
- name: Build LLVM
|
|
||||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# fetch LLVM source
|
|
||||||
rm -rf llvm-project
|
|
||||||
make llvm-source
|
|
||||||
# build!
|
|
||||||
make llvm-build CCACHE=OFF
|
|
||||||
# Remove unnecessary object files (to reduce cache size).
|
|
||||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
|
||||||
- name: Save cached LLVM build
|
|
||||||
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 Go cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
|
|
||||||
path: |
|
|
||||||
C:/Users/runneradmin/AppData/Local/go-build
|
|
||||||
C:/Users/runneradmin/go/pkg/mod
|
|
||||||
- name: Install wasmtime
|
|
||||||
run: |
|
|
||||||
scoop config use_external_7zip true
|
|
||||||
scoop install wasmtime@29.0.1
|
|
||||||
- name: make gen-device
|
|
||||||
run: make -j3 gen-device
|
|
||||||
- name: Test TinyGo
|
|
||||||
shell: bash
|
|
||||||
run: make test GOTESTFLAGS="-only-current-os"
|
|
||||||
- name: Build TinyGo release tarball
|
|
||||||
shell: bash
|
|
||||||
run: make build/release -j4
|
|
||||||
- name: Make release artifact
|
|
||||||
shell: bash
|
|
||||||
working-directory: build/release
|
|
||||||
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
|
|
||||||
- name: Publish release artifact
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
|
|
||||||
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
|
|
||||||
archive: false
|
|
||||||
|
|
||||||
smoke-test-windows:
|
|
||||||
runs-on: windows-2022
|
|
||||||
needs: build-windows
|
|
||||||
steps:
|
|
||||||
- name: Configure pagefile
|
|
||||||
uses: al-cheb/configure-pagefile-action@v1.4
|
|
||||||
with:
|
|
||||||
minimum-size: 8GB
|
|
||||||
maximum-size: 24GB
|
|
||||||
disk-root: "C:"
|
|
||||||
- uses: 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.26.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
|
|
||||||
shell: bash
|
|
||||||
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
|
||||||
|
|
||||||
stdlib-test-windows:
|
|
||||||
runs-on: windows-2022
|
|
||||||
needs: build-windows
|
|
||||||
steps:
|
|
||||||
- name: Configure pagefile
|
|
||||||
uses: al-cheb/configure-pagefile-action@v1.4
|
|
||||||
with:
|
|
||||||
minimum-size: 8GB
|
|
||||||
maximum-size: 24GB
|
|
||||||
disk-root: "C:"
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@v6
|
|
||||||
with:
|
|
||||||
go-version: '1.26.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
|
|
||||||
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
|
||||||
|
|
||||||
stdlib-wasi-test-windows:
|
|
||||||
runs-on: windows-2022
|
|
||||||
needs: build-windows
|
|
||||||
steps:
|
|
||||||
- name: Configure pagefile
|
|
||||||
uses: al-cheb/configure-pagefile-action@v1.4
|
|
||||||
with:
|
|
||||||
minimum-size: 8GB
|
|
||||||
maximum-size: 24GB
|
|
||||||
disk-root: "C:"
|
|
||||||
- uses: 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.26.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
|
|
||||||
+3
-20
@@ -1,8 +1,4 @@
|
|||||||
.DS_Store
|
build
|
||||||
.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,26 +16,13 @@ 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
|
vendor
|
||||||
src/device/renesas/*.s
|
|
||||||
src/device/rp/*.go
|
|
||||||
src/device/rp/*.s
|
|
||||||
./vendor
|
|
||||||
llvm-build
|
llvm-build
|
||||||
llvm-project
|
llvm-project
|
||||||
build/*
|
|
||||||
|
|
||||||
# Ignore files generated by smoketest
|
# Ignore files generated by smoketest
|
||||||
test
|
|
||||||
test.bin
|
|
||||||
test.elf
|
|
||||||
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
|
|
||||||
+7
-25
@@ -9,35 +9,17 @@
|
|||||||
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/compiler-rt"]
|
||||||
|
path = lib/compiler-rt
|
||||||
|
url = https://github.com/llvm-mirror/compiler-rt.git
|
||||||
|
branch = release_80
|
||||||
[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"]
|
|
||||||
path = lib/musl
|
|
||||||
url = https://github.com/tinygo-org/musl-libc.git
|
|
||||||
[submodule "lib/binaryen"]
|
|
||||||
path = lib/binaryen
|
|
||||||
url = https://github.com/WebAssembly/binaryen.git
|
|
||||||
[submodule "lib/mingw-w64"]
|
|
||||||
path = lib/mingw-w64
|
|
||||||
url = https://github.com/mingw-w64/mingw-w64.git
|
|
||||||
[submodule "lib/macos-minimal-sdk"]
|
|
||||||
path = lib/macos-minimal-sdk
|
|
||||||
url = https://github.com/aykevl/macos-minimal-sdk.git
|
|
||||||
[submodule "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
|
|
||||||
|
|||||||
+9
-26
@@ -11,15 +11,19 @@ lld so that the binary can be easily moved between systems. It also shows how to
|
|||||||
build a release tarball that includes this binary and all necessary extra files.
|
build a release tarball that includes this binary and all necessary extra files.
|
||||||
|
|
||||||
**Note**: this documentation describes how to build a statically linked release
|
**Note**: this documentation describes how to build a statically linked release
|
||||||
tarball. If you want to help with development of TinyGo itself, you should follow the guide located at https://tinygo.org/docs/guides/build/
|
tarball. If you want to develop TinyGo, you will probably want to follow a
|
||||||
|
different guide:
|
||||||
|
|
||||||
|
* [Linux](https://tinygo.org/getting-started/linux/#source-install)
|
||||||
|
* [macOS](https://tinygo.org/getting-started/macos/#source-install)
|
||||||
|
* [Windows](https://tinygo.org/getting-started/windows/#source-install)
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||||
|
|
||||||
* Go (1.19+)
|
* Go (1.13+)
|
||||||
* GNU Make
|
|
||||||
* Standard build tools (gcc/clang)
|
* Standard build tools (gcc/clang)
|
||||||
* git
|
* git
|
||||||
* CMake
|
* CMake
|
||||||
@@ -28,21 +32,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 +74,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
|
||||||
|
|||||||
+3
-1941
File diff suppressed because it is too large
Load Diff
+52
-1
@@ -1 +1,52 @@
|
|||||||
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details. Thank you.
|
# How to contribute
|
||||||
|
|
||||||
|
Thank you for your interest in improving TinyGo.
|
||||||
|
|
||||||
|
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
|
||||||
|
|
||||||
|
### New to TinyGo
|
||||||
|
|
||||||
|
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||||
|
|
||||||
|
### Something in TinyGo is not working as you expect
|
||||||
|
|
||||||
|
Please open a Github issue with your problem, and we will be happy to assist.
|
||||||
|
|
||||||
|
### Something in Go that you want/need does not appear to be in TinyGo
|
||||||
|
|
||||||
|
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
|
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||||
|
|
||||||
|
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||||
|
|
||||||
|
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||||
|
|
||||||
|
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||||
|
|
||||||
|
Please start by opening a Github issue. We want to help you to help us to help you.
|
||||||
|
|
||||||
|
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||||
|
|
||||||
|
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||||
|
|
||||||
|
## How to use our Github repository
|
||||||
|
|
||||||
|
The `release` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||||
|
|
||||||
|
Here is how to contribute back some code or documentation:
|
||||||
|
|
||||||
|
- Fork repo
|
||||||
|
- Create a feature branch off of the `dev` branch
|
||||||
|
- Make some useful change
|
||||||
|
- Make sure the tests still pass
|
||||||
|
- Submit a pull request against the `dev` branch.
|
||||||
|
- Be kind
|
||||||
|
|
||||||
|
## How to run tests
|
||||||
|
|
||||||
|
To run the tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|||||||
+70
-34
@@ -1,43 +1,79 @@
|
|||||||
# tinygo-llvm stage obtains the llvm source for TinyGo
|
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
|
||||||
FROM golang:1.26 AS tinygo-llvm
|
FROM golang:1.15 AS tinygo-base
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||||
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
|
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
|
||||||
rm -rf \
|
apt-get update && \
|
||||||
/var/lib/apt/lists/* \
|
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
|
||||||
/var/log/* \
|
|
||||||
/var/tmp/* \
|
|
||||||
/tmp/*
|
|
||||||
|
|
||||||
COPY ./GNUmakefile /tinygo/GNUmakefile
|
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
|
||||||
make llvm-source
|
|
||||||
|
|
||||||
# tinygo-llvm-build stage build the custom llvm with xtensa support
|
|
||||||
FROM tinygo-llvm AS tinygo-llvm-build
|
|
||||||
|
|
||||||
RUN cd /tinygo/ && \
|
|
||||||
make llvm-build
|
|
||||||
|
|
||||||
# tinygo-compiler-build stage builds the compiler itself
|
|
||||||
FROM tinygo-llvm-build AS tinygo-compiler-build
|
|
||||||
|
|
||||||
COPY . /tinygo
|
COPY . /tinygo
|
||||||
|
|
||||||
# build the compiler and tools
|
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||||
|
# after copying the tinygo directory in the previous step.
|
||||||
RUN cd /tinygo/ && \
|
RUN cd /tinygo/ && \
|
||||||
git submodule update --init && \
|
rm -rf ./lib/* && \
|
||||||
make gen-device -j4 && \
|
git submodule sync && \
|
||||||
make build/release
|
git submodule update --init --recursive --force
|
||||||
|
|
||||||
# tinygo-compiler copies the compiler build over to a base Go container (without
|
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
|
||||||
# all the build tools etc).
|
|
||||||
FROM golang:1.26 AS tinygo-compiler
|
|
||||||
|
|
||||||
# Copy tinygo build.
|
RUN cd /tinygo/ && \
|
||||||
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
|
go install /tinygo/
|
||||||
|
|
||||||
|
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||||
|
FROM tinygo-base AS tinygo-wasm
|
||||||
|
|
||||||
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
|
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||||
|
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||||
|
|
||||||
|
RUN cd /tinygo/ && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y make clang-11 libllvm11 lld-11 && \
|
||||||
|
make wasi-libc
|
||||||
|
|
||||||
|
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||||
|
FROM tinygo-base AS tinygo-avr
|
||||||
|
|
||||||
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
|
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||||
|
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||||
|
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||||
|
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||||
|
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||||
|
|
||||||
|
RUN cd /tinygo/ && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
|
||||||
|
make gen-device-avr && \
|
||||||
|
apt-get autoremove -y && \
|
||||||
|
apt-get clean
|
||||||
|
|
||||||
|
# tinygo-arm stage installs the needed dependencies to compile TinyGo programs for ARM microcontrollers.
|
||||||
|
FROM tinygo-base AS tinygo-arm
|
||||||
|
|
||||||
|
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||||
|
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||||
|
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||||
|
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||||
|
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||||
|
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||||
|
|
||||||
|
RUN cd /tinygo/ && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y apt-utils make clang-11 && \
|
||||||
|
make gen-device-nrf && make gen-device-stm32
|
||||||
|
|
||||||
|
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||||
|
FROM tinygo-wasm AS tinygo-all
|
||||||
|
|
||||||
|
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||||
|
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||||
|
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||||
|
|
||||||
|
RUN cd /tinygo/ && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
|
||||||
|
make gen-device
|
||||||
|
|
||||||
# Configure the container.
|
|
||||||
ENV PATH="${PATH}:/tinygo/bin"
|
|
||||||
CMD ["tinygo"]
|
CMD ["tinygo"]
|
||||||
|
|||||||
-1220
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
Copyright (c) 2018-2026 The TinyGo Authors. All rights reserved.
|
Copyright (c) 2018-2021 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-2021 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.
|
||||||
|
|||||||
@@ -0,0 +1,468 @@
|
|||||||
|
|
||||||
|
# aliases
|
||||||
|
all: tinygo
|
||||||
|
|
||||||
|
# Default build and source directories, as created by `make llvm-build`.
|
||||||
|
LLVM_BUILDDIR ?= llvm-build
|
||||||
|
LLVM_PROJECTDIR ?= llvm-project
|
||||||
|
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
||||||
|
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
||||||
|
|
||||||
|
# Try to autodetect LLVM build tools.
|
||||||
|
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
|
||||||
|
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
|
||||||
|
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
|
||||||
|
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
|
||||||
|
|
||||||
|
# Go binary and GOROOT to select
|
||||||
|
GO ?= go
|
||||||
|
export GOROOT = $(shell $(GO) env GOROOT)
|
||||||
|
|
||||||
|
# md5sum binary
|
||||||
|
MD5SUM = md5sum
|
||||||
|
|
||||||
|
# tinygo binary for tests
|
||||||
|
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
||||||
|
|
||||||
|
# Use CCACHE for LLVM if possible
|
||||||
|
ifneq (, $(shell command -v ccache 2> /dev/null))
|
||||||
|
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Allow enabling LLVM assertions
|
||||||
|
ifeq (1, $(ASSERT))
|
||||||
|
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=ON'
|
||||||
|
else
|
||||||
|
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
||||||
|
endif
|
||||||
|
|
||||||
|
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
|
||||||
|
|
||||||
|
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||||
|
|
||||||
|
ifeq ($(OS),Windows_NT)
|
||||||
|
EXE = .exe
|
||||||
|
START_GROUP = -Wl,--start-group
|
||||||
|
END_GROUP = -Wl,--end-group
|
||||||
|
|
||||||
|
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
||||||
|
# Without this flag, linking results in errors like these:
|
||||||
|
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
||||||
|
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
|
||||||
|
|
||||||
|
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
|
||||||
|
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||||
|
CGO_LDFLAGS_EXTRA += -lversion
|
||||||
|
|
||||||
|
LIBCLANG_NAME = libclang
|
||||||
|
|
||||||
|
else ifeq ($(shell uname -s),Darwin)
|
||||||
|
MD5SUM = md5
|
||||||
|
LIBCLANG_NAME = clang
|
||||||
|
else ifeq ($(shell uname -s),FreeBSD)
|
||||||
|
MD5SUM = md5
|
||||||
|
LIBCLANG_NAME = clang
|
||||||
|
START_GROUP = -Wl,--start-group
|
||||||
|
END_GROUP = -Wl,--end-group
|
||||||
|
else
|
||||||
|
LIBCLANG_NAME = clang
|
||||||
|
START_GROUP = -Wl,--start-group
|
||||||
|
END_GROUP = -Wl,--end-group
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Libraries that should be linked in for the statically linked Clang.
|
||||||
|
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
||||||
|
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
||||||
|
|
||||||
|
# Libraries that should be linked in for the statically linked LLD.
|
||||||
|
LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML
|
||||||
|
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
|
||||||
|
|
||||||
|
# Other libraries that are needed to link TinyGo.
|
||||||
|
EXTRA_LIB_NAMES = LLVMInterpreter
|
||||||
|
|
||||||
|
# These build targets appear to be the only ones necessary to build all TinyGo
|
||||||
|
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
|
||||||
|
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
|
||||||
|
# library path (for ninja).
|
||||||
|
# This list also includes a few tools that are necessary as part of the full
|
||||||
|
# TinyGo build.
|
||||||
|
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIBCLANG_NAME) $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)))
|
||||||
|
|
||||||
|
# For static linking.
|
||||||
|
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||||
|
CGO_CPPFLAGS+=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||||
|
CGO_CXXFLAGS=-std=c++14
|
||||||
|
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -rf build
|
||||||
|
|
||||||
|
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||||
|
fmt:
|
||||||
|
@gofmt -l -w $(FMT_PATHS)
|
||||||
|
fmt-check:
|
||||||
|
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||||
|
|
||||||
|
|
||||||
|
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp
|
||||||
|
ifneq ($(STM32), 0)
|
||||||
|
gen-device: gen-device-stm32
|
||||||
|
endif
|
||||||
|
|
||||||
|
gen-device-avr:
|
||||||
|
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
||||||
|
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
||||||
|
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
||||||
|
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
||||||
|
@GO111MODULE=off $(GO) fmt ./src/device/avr
|
||||||
|
|
||||||
|
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||||
|
$(GO) build -o $@ ./tools/gen-device-svd/
|
||||||
|
|
||||||
|
gen-device-esp: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/esp
|
||||||
|
|
||||||
|
gen-device-nrf: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||||
|
|
||||||
|
gen-device-nxp: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/nxp
|
||||||
|
|
||||||
|
gen-device-sam: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||||
|
|
||||||
|
gen-device-sifive: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||||
|
|
||||||
|
gen-device-kendryte: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/kendryte
|
||||||
|
|
||||||
|
gen-device-stm32: build/gen-device-svd
|
||||||
|
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
|
||||||
|
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||||
|
|
||||||
|
|
||||||
|
# Get LLVM sources.
|
||||||
|
$(LLVM_PROJECTDIR)/llvm:
|
||||||
|
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||||
|
llvm-source: $(LLVM_PROJECTDIR)/llvm
|
||||||
|
|
||||||
|
# Configure LLVM.
|
||||||
|
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||||
|
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
||||||
|
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||||
|
|
||||||
|
# Build LLVM.
|
||||||
|
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||||
|
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
|
||||||
|
|
||||||
|
|
||||||
|
# Build wasi-libc sysroot
|
||||||
|
.PHONY: wasi-libc
|
||||||
|
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
|
||||||
|
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
|
||||||
|
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
||||||
|
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
||||||
|
|
||||||
|
|
||||||
|
# Build the Go compiler.
|
||||||
|
tinygo:
|
||||||
|
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
||||||
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
|
||||||
|
|
||||||
|
test: wasi-libc
|
||||||
|
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||||
|
|
||||||
|
# Test known-working standard library packages.
|
||||||
|
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||||
|
# implied -v flag).
|
||||||
|
.PHONY: tinygo-test
|
||||||
|
tinygo-test:
|
||||||
|
$(TINYGO) test container/heap
|
||||||
|
$(TINYGO) test container/list
|
||||||
|
$(TINYGO) test container/ring
|
||||||
|
$(TINYGO) test crypto/des
|
||||||
|
$(TINYGO) test encoding/ascii85
|
||||||
|
$(TINYGO) test encoding/base32
|
||||||
|
$(TINYGO) test encoding/hex
|
||||||
|
$(TINYGO) test hash/adler32
|
||||||
|
$(TINYGO) test hash/fnv
|
||||||
|
$(TINYGO) test hash/crc64
|
||||||
|
$(TINYGO) test math
|
||||||
|
$(TINYGO) test math/cmplx
|
||||||
|
$(TINYGO) test text/scanner
|
||||||
|
$(TINYGO) test unicode/utf8
|
||||||
|
|
||||||
|
.PHONY: smoketest
|
||||||
|
smoketest:
|
||||||
|
$(TINYGO) version
|
||||||
|
# test all examples (except pwm)
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinkm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/button2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/echo
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/systick
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
# test simulated boards on play.tinygo.org
|
||||||
|
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
|
||||||
|
@$(MD5SUM) test.wasm
|
||||||
|
# test all targets/boards
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=trinket-m0 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
||||||
|
@$(MD5SUM) test.gba
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pybadge examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=metro-m4-airlift examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pyportal examples/dac
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
# test pwm
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
ifneq ($(STM32), 0)
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
endif
|
||||||
|
ifneq ($(AVR), 0)
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
endif
|
||||||
|
ifneq ($(XTENSA), 0)
|
||||||
|
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
|
||||||
|
@$(MD5SUM) test.bin
|
||||||
|
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
|
||||||
|
@$(MD5SUM) test.bin
|
||||||
|
endif
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||||
|
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||||
|
# test various compiler flags
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
|
||||||
|
@$(MD5SUM) test.nro
|
||||||
|
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
|
||||||
|
@$(MD5SUM) test.hex
|
||||||
|
|
||||||
|
|
||||||
|
wasmtest:
|
||||||
|
$(GO) test ./tests/wasm
|
||||||
|
|
||||||
|
build/release: tinygo gen-device wasi-libc
|
||||||
|
@mkdir -p build/release/tinygo/bin
|
||||||
|
@mkdir -p build/release/tinygo/lib/clang/include
|
||||||
|
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
|
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||||
|
@mkdir -p build/release/tinygo/lib/nrfx
|
||||||
|
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@mkdir -p build/release/tinygo/lib/wasi-libc
|
||||||
|
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||||
|
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||||
|
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||||
|
@echo copying source files
|
||||||
|
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
|
||||||
|
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||||
|
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||||
|
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||||
|
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||||
|
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
||||||
|
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
||||||
|
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||||
|
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
|
||||||
|
@cp -rp lib/picolibc-include build/release/tinygo/lib
|
||||||
|
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
|
||||||
|
@cp -rp src build/release/tinygo/src
|
||||||
|
@cp -rp targets build/release/tinygo/targets
|
||||||
|
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
|
||||||
|
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
|
||||||
|
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
|
||||||
|
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
|
||||||
|
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
|
||||||
|
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
|
||||||
|
|
||||||
|
release: build/release
|
||||||
|
tar -czf build/release.tar.gz -C build/release tinygo
|
||||||
|
|
||||||
|
deb: build/release
|
||||||
|
@mkdir -p build/release-deb/usr/local/bin
|
||||||
|
@mkdir -p build/release-deb/usr/local/lib
|
||||||
|
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
|
||||||
|
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
|
||||||
|
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
|
||||||
@@ -1,16 +1,11 @@
|
|||||||
# TinyGo - Go compiler for small places
|
# TinyGo - Go compiler for small places
|
||||||
|
|
||||||
[](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/nix.yml)
|
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
|
||||||
|
|
||||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (wasm/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,81 @@ 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 57 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 STM32F405 Express](https://www.adafruit.com/product/4382)
|
||||||
|
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||||
|
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||||
|
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
|
||||||
|
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
|
||||||
|
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||||
|
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||||
|
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
|
||||||
|
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
||||||
|
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
|
||||||
|
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||||
|
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
||||||
|
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
||||||
|
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
||||||
|
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||||
|
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||||
|
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
|
||||||
|
* [BBC micro:bit](https://microbit.org/)
|
||||||
|
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
|
||||||
|
* [Digispark](http://digistump.com/products/1)
|
||||||
|
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
|
||||||
|
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
|
||||||
|
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
|
||||||
|
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||||
|
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||||
|
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
||||||
|
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
|
||||||
|
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
|
||||||
|
* [Nintendo Switch](https://www.nintendo.com/switch/)
|
||||||
|
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||||
|
* [Nordic Semiconductor 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)
|
||||||
|
* [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/)
|
||||||
|
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||||
|
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
||||||
|
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
|
||||||
|
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
||||||
|
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
||||||
|
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
|
||||||
|
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
|
||||||
|
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||||
|
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||||
|
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
|
||||||
|
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
|
||||||
|
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
|
||||||
|
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||||
|
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||||
|
|
||||||
### 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:
|
||||||
|
|
||||||
@@ -122,7 +128,7 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
|
|||||||
|
|
||||||
Your contributions are welcome!
|
Your contributions are welcome!
|
||||||
|
|
||||||
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details.
|
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
|
||||||
|
|
||||||
## Project Scope
|
## Project Scope
|
||||||
|
|
||||||
@@ -136,13 +142,14 @@ Goals:
|
|||||||
|
|
||||||
Non-goals:
|
Non-goals:
|
||||||
|
|
||||||
|
* Using more than one core.
|
||||||
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
||||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
||||||
* Be able to compile every Go program out there.
|
* Be able to compile every Go program out there.
|
||||||
|
|
||||||
## Why this project exists
|
## Why this project exists
|
||||||
|
|
||||||
> We never expected Go to be an embedded language, and so it’s 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)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||||
|
# be built anyway.
|
||||||
|
trigger:
|
||||||
|
- release
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
- job: Build
|
||||||
|
timeoutInMinutes: 240 # 4h
|
||||||
|
pool:
|
||||||
|
vmImage: 'VS2017-Win2016'
|
||||||
|
steps:
|
||||||
|
- task: GoTool@0
|
||||||
|
inputs:
|
||||||
|
version: '1.16'
|
||||||
|
- checkout: self
|
||||||
|
fetchDepth: 1
|
||||||
|
- task: Cache@2
|
||||||
|
displayName: Cache LLVM source
|
||||||
|
inputs:
|
||||||
|
key: llvm-source-11-windows-v1
|
||||||
|
path: llvm-project
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Download LLVM source
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: make llvm-source
|
||||||
|
- task: CacheBeta@0
|
||||||
|
displayName: Cache LLVM build
|
||||||
|
inputs:
|
||||||
|
key: llvm-build-11-windows-v4
|
||||||
|
path: llvm-build
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Build LLVM
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: |
|
||||||
|
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||||
|
then
|
||||||
|
# install dependencies
|
||||||
|
choco install ninja
|
||||||
|
# hack ninja to use fewer jobs
|
||||||
|
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
|
||||||
|
# build!
|
||||||
|
make llvm-build
|
||||||
|
fi
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Install QEMU
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: choco install qemu --version=2020.06.12
|
||||||
|
- task: CacheBeta@0
|
||||||
|
displayName: Cache wasi-libc sysroot
|
||||||
|
inputs:
|
||||||
|
key: wasi-libc-sysroot-v4
|
||||||
|
path: lib/wasi-libc/sysroot
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Build wasi-libc
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: PATH=/usr/bin:$PATH make wasi-libc
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Test TinyGo
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: |
|
||||||
|
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
|
make test
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Build TinyGo release tarball
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: |
|
||||||
|
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
|
make build/release -j4
|
||||||
|
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
|
||||||
|
displayName: Publish zip as artifact
|
||||||
|
artifact: tinygo
|
||||||
|
- task: Bash@3
|
||||||
|
displayName: Smoke tests
|
||||||
|
inputs:
|
||||||
|
targetType: inline
|
||||||
|
script: |
|
||||||
|
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||||
|
unset GOROOT
|
||||||
|
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
||||||
+52
-119
@@ -3,30 +3,31 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"debug/macho"
|
|
||||||
"debug/pe"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"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...>
|
||||||
func makeArchive(arfile *os.File, objs []string) error {
|
func makeArchive(archivePath string, objs []string) error {
|
||||||
// Open the archive file.
|
// Open the archive file.
|
||||||
arwriter := ar.NewWriter(arfile)
|
arfile, err := os.Create(archivePath)
|
||||||
err := arwriter.WriteGlobalHeader()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
|
return err
|
||||||
|
}
|
||||||
|
defer arfile.Close()
|
||||||
|
arwriter := ar.NewWriter(arfile)
|
||||||
|
err = arwriter.WriteGlobalHeader()
|
||||||
|
if err != nil {
|
||||||
|
return &os.PathError{"write ar header", archivePath, err}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open all object files and read the symbols for the symbol table.
|
// Open all object files and read the symbols for the symbol table.
|
||||||
@@ -34,96 +35,43 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||||||
name string // symbol name
|
name string // symbol name
|
||||||
fileIndex int // index into objfiles
|
fileIndex int // index into objfiles
|
||||||
}{}
|
}{}
|
||||||
archiveOffsets := make([]int32, len(objs))
|
objfiles := make([]struct {
|
||||||
|
file *os.File
|
||||||
|
archiveOffset int32
|
||||||
|
}, len(objs))
|
||||||
for i, objpath := range objs {
|
for i, objpath := range objs {
|
||||||
objfile, err := os.Open(objpath)
|
objfile, err := os.Open(objpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
objfiles[i].file = objfile
|
||||||
|
|
||||||
// Read the symbols and add them to the symbol table.
|
// Read the symbols and add them to the symbol table.
|
||||||
if dbg, err := elf.NewFile(objfile); err == nil {
|
dbg, err := elf.NewFile(objfile)
|
||||||
symbols, err := dbg.Symbols()
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
symbols, err := dbg.Symbols()
|
||||||
for _, symbol := range symbols {
|
if err != nil {
|
||||||
bind := elf.ST_BIND(symbol.Info)
|
return err
|
||||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
}
|
||||||
// Don't include local symbols (STB_LOCAL).
|
for _, symbol := range symbols {
|
||||||
continue
|
bind := elf.ST_BIND(symbol.Info)
|
||||||
}
|
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
|
// Don't include local symbols (STB_LOCAL).
|
||||||
// Not a function.
|
continue
|
||||||
continue
|
}
|
||||||
}
|
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
|
||||||
// Include in archive.
|
// Not a function.
|
||||||
symbolTable = append(symbolTable, struct {
|
// TODO: perhaps globals variables should also be included?
|
||||||
name string
|
continue
|
||||||
fileIndex int
|
}
|
||||||
}{symbol.Name, i})
|
// Include in archive.
|
||||||
}
|
symbolTable = append(symbolTable, struct {
|
||||||
} else if dbg, err := macho.NewFile(objfile); err == nil {
|
name string
|
||||||
for _, symbol := range dbg.Symtab.Syms {
|
fileIndex int
|
||||||
// See mach-o/nlist.h
|
}{symbol.Name, i})
|
||||||
if symbol.Type&0x0e != 0xe { // (symbol.Type & N_TYPE) != N_SECT
|
|
||||||
continue // undefined symbol
|
|
||||||
}
|
|
||||||
if symbol.Type&0x01 == 0 { // (symbol.Type & N_EXT) == 0
|
|
||||||
continue // internal symbol (static, etc)
|
|
||||||
}
|
|
||||||
symbolTable = append(symbolTable, struct {
|
|
||||||
name string
|
|
||||||
fileIndex int
|
|
||||||
}{symbol.Name, i})
|
|
||||||
}
|
|
||||||
} else if dbg, err := pe.NewFile(objfile); err == nil {
|
|
||||||
for _, symbol := range dbg.Symbols {
|
|
||||||
if symbol.StorageClass != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symbol.SectionNumber == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
symbolTable = append(symbolTable, struct {
|
|
||||||
name string
|
|
||||||
fileIndex int
|
|
||||||
}{symbol.Name, i})
|
|
||||||
}
|
|
||||||
} else if dbg, err := wasm.Parse(objfile); err == nil {
|
|
||||||
for _, s := range dbg.Sections {
|
|
||||||
switch section := s.(type) {
|
|
||||||
case *wasm.SectionLinking:
|
|
||||||
for _, symbol := range section.Symbols {
|
|
||||||
if symbol.Flags&wasm.LinkingSymbolFlagUndefined != 0 {
|
|
||||||
// Don't list undefined functions.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symbol.Flags&wasm.LinkingSymbolFlagBindingLocal != 0 {
|
|
||||||
// Don't include local symbols.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if symbol.Kind != wasm.LinkingSymbolKindFunction && symbol.Kind != wasm.LinkingSymbolKindData {
|
|
||||||
// Link functions and data symbols.
|
|
||||||
// Some data symbols need to be included, such as
|
|
||||||
// __log_data.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Include in the archive.
|
|
||||||
symbolTable = append(symbolTable, struct {
|
|
||||||
name string
|
|
||||||
fileIndex int
|
|
||||||
}{symbol.Name, i})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("failed to open file %s as WASM, ELF or PE/COFF: %w", objpath, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close file, to avoid issues with too many open files (especially on
|
|
||||||
// MacOS X).
|
|
||||||
objfile.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the symbol table buffer.
|
// Create the symbol table buffer.
|
||||||
@@ -165,7 +113,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
|
||||||
}
|
}
|
||||||
@@ -177,32 +125,25 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add all object files to the archive.
|
// Add all object files to the archive.
|
||||||
var copyBuf bytes.Buffer
|
for i, objfile := range objfiles {
|
||||||
for i, objpath := range objs {
|
|
||||||
objfile, err := os.Open(objpath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer objfile.Close()
|
|
||||||
|
|
||||||
// Store the start index, for when we'll update the symbol table with
|
// 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
|
||||||
}
|
}
|
||||||
if int64(int32(offset)) != offset {
|
if int64(int32(offset)) != offset {
|
||||||
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
|
return errors.New("large archives (4GB+) not supported: " + archivePath)
|
||||||
}
|
}
|
||||||
archiveOffsets[i] = int32(offset)
|
objfiles[i].archiveOffset = int32(offset)
|
||||||
|
|
||||||
// Write the file header.
|
// Write the file header.
|
||||||
st, err := objfile.Stat()
|
st, err := objfile.file.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = arwriter.WriteHeader(&ar.Header{
|
err = arwriter.WriteHeader(&ar.Header{
|
||||||
Name: filepath.Base(objfile.Name()),
|
Name: filepath.Base(objfile.file.Name()),
|
||||||
ModTime: time.Unix(0, 0),
|
ModTime: time.Unix(0, 0),
|
||||||
Uid: 0,
|
Uid: 0,
|
||||||
Gid: 0,
|
Gid: 0,
|
||||||
@@ -214,30 +155,22 @@ func makeArchive(arfile *os.File, objs []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy the file contents into the archive.
|
// Copy the file contents into the archive.
|
||||||
// First load all contents into a buffer, then write it all in one go to
|
n, err := io.Copy(arwriter, objfile.file)
|
||||||
// the archive file. This is a bit complicated, but is necessary because
|
|
||||||
// io.Copy can't deal with files that are of an odd size.
|
|
||||||
copyBuf.Reset()
|
|
||||||
n, err := io.Copy(©Buf, objfile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
if n != st.Size() {
|
if n != st.Size() {
|
||||||
return errors.New("file modified during ar creation: " + arfile.Name())
|
return errors.New("file modified during ar creation: " + archivePath)
|
||||||
}
|
|
||||||
_, err = arwriter.Write(copyBuf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// File is not needed anymore.
|
// File is not needed anymore.
|
||||||
objfile.Close()
|
objfile.file.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create symbol indices.
|
// Create symbol indices.
|
||||||
indicesBuf := &bytes.Buffer{}
|
indicesBuf := &bytes.Buffer{}
|
||||||
for _, sym := range symbolTable {
|
for _, sym := range symbolTable {
|
||||||
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
|
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
+314
-883
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||||
|
// for stale caches.
|
||||||
|
func cacheTimestamp(paths []string) (time.Time, error) {
|
||||||
|
var timestamp time.Time
|
||||||
|
for _, path := range paths {
|
||||||
|
st, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, err
|
||||||
|
}
|
||||||
|
if timestamp.IsZero() {
|
||||||
|
timestamp = st.ModTime()
|
||||||
|
} else if timestamp.Before(st.ModTime()) {
|
||||||
|
timestamp = st.ModTime()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return timestamp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to load a given file from the cache. Return "", nil if no cached file can
|
||||||
|
// be found (or the file is stale), return the absolute path if there is a cache
|
||||||
|
// and return an error on I/O errors.
|
||||||
|
func cacheLoad(name string, sourceFiles []string) (string, error) {
|
||||||
|
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||||
|
cacheStat, err := os.Stat(cachepath)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return "", nil // does not exist
|
||||||
|
} else if err != nil {
|
||||||
|
return "", err // cannot stat cache file
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceTimestamp, err := cacheTimestamp(sourceFiles)
|
||||||
|
if err != nil {
|
||||||
|
return "", err // cannot stat source files
|
||||||
|
}
|
||||||
|
|
||||||
|
if cacheStat.ModTime().After(sourceTimestamp) {
|
||||||
|
return cachepath, nil
|
||||||
|
} else {
|
||||||
|
os.Remove(cachepath)
|
||||||
|
// stale cache
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the file located at tmppath in the cache with the given name. The
|
||||||
|
// tmppath may or may not be gone afterwards.
|
||||||
|
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
||||||
|
// get the last modified time
|
||||||
|
if len(sourceFiles) == 0 {
|
||||||
|
panic("cache: no source files")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check the config key
|
||||||
|
|
||||||
|
dir := goenv.Get("GOCACHE")
|
||||||
|
err := os.MkdirAll(dir, 0777)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cachepath := filepath.Join(dir, name)
|
||||||
|
err = copyFile(tmppath, cachepath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cachepath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// copyFile copies the given file from src to dst. It can copy over
|
||||||
|
// a possibly already existing file at the destination.
|
||||||
|
func copyFile(src, dst string) error {
|
||||||
|
inf, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer inf.Close()
|
||||||
|
outpath := dst + ".tmp"
|
||||||
|
outf, err := os.Create(outpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(outf, inf)
|
||||||
|
if err != nil {
|
||||||
|
os.Remove(outpath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = outf.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(dst+".tmp", dst)
|
||||||
|
}
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"tinygo.org/x/go-llvm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Test whether the Clang generated "target-cpu" and "target-features"
|
|
||||||
// attributes match the CPU and Features property in TinyGo target files.
|
|
||||||
func TestClangAttributes(t *testing.T) {
|
|
||||||
var targetNames = []string{
|
|
||||||
// Please keep this list sorted!
|
|
||||||
"atmega328p",
|
|
||||||
"atmega1280",
|
|
||||||
"atmega1284p",
|
|
||||||
"atmega2560",
|
|
||||||
"attiny85",
|
|
||||||
"cortex-m0",
|
|
||||||
"cortex-m0plus",
|
|
||||||
"cortex-m3",
|
|
||||||
"cortex-m33",
|
|
||||||
"cortex-m4",
|
|
||||||
"cortex-m7",
|
|
||||||
"esp32c3",
|
|
||||||
"esp32s3",
|
|
||||||
"fe310",
|
|
||||||
"gameboy-advance",
|
|
||||||
"k210",
|
|
||||||
"nintendoswitch",
|
|
||||||
"riscv-qemu",
|
|
||||||
"tkey",
|
|
||||||
"wasip1",
|
|
||||||
"wasip2",
|
|
||||||
"wasm",
|
|
||||||
"wasm-unknown",
|
|
||||||
}
|
|
||||||
if hasBuiltinTools {
|
|
||||||
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
|
|
||||||
// which also implies it was built with Xtensa support.
|
|
||||||
targetNames = append(targetNames, "esp32", "esp8266")
|
|
||||||
}
|
|
||||||
for _, targetName := range targetNames {
|
|
||||||
targetName := targetName
|
|
||||||
t.Run(targetName, func(t *testing.T) {
|
|
||||||
testClangAttributes(t, &compileopts.Options{Target: targetName})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, options := range []*compileopts.Options{
|
|
||||||
{GOOS: "linux", GOARCH: "386"},
|
|
||||||
{GOOS: "linux", GOARCH: "amd64"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "5,softfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "6,softfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "7,softfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "5,hardfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "6,hardfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm", GOARM: "7,hardfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "arm64"},
|
|
||||||
{GOOS: "linux", GOARCH: "mips", GOMIPS: "hardfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "hardfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "mips", GOMIPS: "softfloat"},
|
|
||||||
{GOOS: "linux", GOARCH: "mipsle", GOMIPS: "softfloat"},
|
|
||||||
{GOOS: "darwin", GOARCH: "amd64"},
|
|
||||||
{GOOS: "darwin", GOARCH: "arm64"},
|
|
||||||
{GOOS: "windows", GOARCH: "386"},
|
|
||||||
{GOOS: "windows", GOARCH: "amd64"},
|
|
||||||
{GOOS: "windows", GOARCH: "arm64"},
|
|
||||||
} {
|
|
||||||
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
|
|
||||||
if options.GOARCH == "arm" {
|
|
||||||
name += ",GOARM=" + options.GOARM
|
|
||||||
}
|
|
||||||
if options.GOARCH == "mips" || options.GOARCH == "mipsle" {
|
|
||||||
name += ",GOMIPS=" + options.GOMIPS
|
|
||||||
}
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
testClangAttributes(t, options)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
|
||||||
testDir := t.TempDir()
|
|
||||||
|
|
||||||
ctx := llvm.NewContext()
|
|
||||||
defer ctx.Dispose()
|
|
||||||
|
|
||||||
target, err := compileopts.LoadTarget(options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not load target: %s", err)
|
|
||||||
}
|
|
||||||
config := compileopts.Config{
|
|
||||||
Options: options,
|
|
||||||
Target: target,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a very simple C input file.
|
|
||||||
srcpath := filepath.Join(testDir, "test.c")
|
|
||||||
err = os.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not write target file %s: %s", srcpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile this file using Clang.
|
|
||||||
outpath := filepath.Join(testDir, "test.bc")
|
|
||||||
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags(false)...)
|
|
||||||
if config.GOOS() == "darwin" {
|
|
||||||
// Silence some warnings that happen when testing GOOS=darwin on
|
|
||||||
// something other than MacOS.
|
|
||||||
flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot")
|
|
||||||
}
|
|
||||||
err = runCCompiler(flags...)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile %s: %s", srcpath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the resulting LLVM bitcode.
|
|
||||||
mod, err := ctx.ParseBitcodeFile(outpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not parse bitcode file %s: %s", outpath, err)
|
|
||||||
}
|
|
||||||
defer mod.Dispose()
|
|
||||||
|
|
||||||
// Check whether the LLVM target matches.
|
|
||||||
if mod.Target() != config.Triple() {
|
|
||||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the "target-cpu" and "target-features" string attribute of the add
|
|
||||||
// function.
|
|
||||||
add := mod.NamedFunction("add")
|
|
||||||
var cpu, features string
|
|
||||||
cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu")
|
|
||||||
featuresAttr := add.GetStringAttributeAtIndex(-1, "target-features")
|
|
||||||
if !cpuAttr.IsNil() {
|
|
||||||
cpu = cpuAttr.GetStringValue()
|
|
||||||
}
|
|
||||||
if !featuresAttr.IsNil() {
|
|
||||||
features = featuresAttr.GetStringValue()
|
|
||||||
}
|
|
||||||
if cpu != config.CPU() {
|
|
||||||
t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu)
|
|
||||||
}
|
|
||||||
if features != config.Features() {
|
|
||||||
if hasBuiltinTools || runtime.GOOS != "linux" {
|
|
||||||
// Skip this step when using an external Clang invocation on Linux.
|
|
||||||
// The reason is that Debian has patched Clang in a way that
|
|
||||||
// modifies the LLVM features string, changing lots of FPU/float
|
|
||||||
// related flags. We want to test vanilla Clang, not Debian Clang.
|
|
||||||
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This TestMain is necessary because TinyGo may also be invoked to run certain
|
|
||||||
// LLVM tools in a separate process. Not capturing these invocations would lead
|
|
||||||
// to recursive tests.
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
if len(os.Args) >= 2 {
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "clang", "ld.lld", "wasm-ld":
|
|
||||||
// Invoke a specific tool.
|
|
||||||
err := RunTool(os.Args[1], os.Args[2:]...)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run normal tests.
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"debug/elf"
|
|
||||||
"debug/macho"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReadBuildID reads the build ID from the currently running executable.
|
|
||||||
func ReadBuildID() ([]byte, error) {
|
|
||||||
executable, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err := os.Open(executable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
switch runtime.GOOS {
|
|
||||||
case "linux", "freebsd", "android":
|
|
||||||
// Read the GNU build id section. (Not sure about FreeBSD though...)
|
|
||||||
file, err := elf.NewFile(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var gnuID, goID []byte
|
|
||||||
for _, section := range file.Sections {
|
|
||||||
if section.Type != elf.SHT_NOTE ||
|
|
||||||
(section.Name != ".note.gnu.build-id" && section.Name != ".note.go.buildid") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf := make([]byte, section.Size)
|
|
||||||
n, err := section.ReadAt(buf, 0)
|
|
||||||
if uint64(n) != section.Size || err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read build id: %w", err)
|
|
||||||
}
|
|
||||||
if section.Name == ".note.gnu.build-id" {
|
|
||||||
gnuID = buf
|
|
||||||
} else {
|
|
||||||
goID = buf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if gnuID != nil {
|
|
||||||
return gnuID, nil
|
|
||||||
} else if goID != nil {
|
|
||||||
return goID, nil
|
|
||||||
}
|
|
||||||
case "darwin":
|
|
||||||
// Read the LC_UUID load command, which contains the equivalent of a
|
|
||||||
// build ID.
|
|
||||||
file, err := macho.NewFile(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, load := range file.Loads {
|
|
||||||
// Unfortunately, the debug/macho package doesn't support the
|
|
||||||
// LC_UUID command directly. So we have to read it from
|
|
||||||
// macho.LoadBytes.
|
|
||||||
load, ok := load.(macho.LoadBytes)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
raw := load.Raw()
|
|
||||||
command := binary.LittleEndian.Uint32(raw)
|
|
||||||
if command != 0x1b {
|
|
||||||
// Looking for the LC_UUID load command.
|
|
||||||
// LC_UUID is defined here as 0x1b:
|
|
||||||
// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return raw[4:], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normally we would have found a build ID by now. But not on Nix,
|
|
||||||
// unfortunately, because Nix adds -no_uuid for some reason:
|
|
||||||
// https://github.com/NixOS/nixpkgs/issues/178366
|
|
||||||
// Fall back to the same implementation that we use for Windows.
|
|
||||||
id, err := readRawGoBuildID(f, 32*1024)
|
|
||||||
if len(id) != 0 || err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// On other platforms (such as Windows) there isn't such a convenient
|
|
||||||
// build ID. Luckily, Go does have an equivalent of the build ID, which
|
|
||||||
// is stored as a special symbol named go.buildid. You can read it
|
|
||||||
// using `go tool buildid`, but the code below extracts it directly
|
|
||||||
// from the binary.
|
|
||||||
// Unfortunately, because of stripping with the -w flag, no symbol
|
|
||||||
// table might be available. Therefore, we have to scan the binary
|
|
||||||
// directly. Luckily the build ID is always at the start of the file.
|
|
||||||
// For details, see:
|
|
||||||
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
|
|
||||||
id, err := readRawGoBuildID(f, 4096)
|
|
||||||
if len(id) != 0 || err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("could not find build ID in %v", executable)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Go toolchain stores a build ID in the binary that we can use, as a
|
|
||||||
// fallback if binary file specific build IDs can't be obtained.
|
|
||||||
// This function reads that build ID from the binary.
|
|
||||||
func readRawGoBuildID(f *os.File, prefixSize int) ([]byte, error) {
|
|
||||||
fileStart := make([]byte, prefixSize)
|
|
||||||
_, err := io.ReadFull(f, fileStart)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read build id from %s: %v", f.Name(), err)
|
|
||||||
}
|
|
||||||
index := bytes.Index(fileStart, []byte("\xff Go build ID: \""))
|
|
||||||
if index < 0 || index > len(fileStart)-103 {
|
|
||||||
return nil, fmt.Errorf("could not find build id in %s", f.Name())
|
|
||||||
}
|
|
||||||
buf := fileStart[index : index+103]
|
|
||||||
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
|
|
||||||
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
+18
-93
@@ -1,22 +1,17 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"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 +36,13 @@ var genericBuiltins = []string{
|
|||||||
"divdf3.c",
|
"divdf3.c",
|
||||||
"divdi3.c",
|
"divdi3.c",
|
||||||
"divmoddi4.c",
|
"divmoddi4.c",
|
||||||
//"divmodsi4.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",
|
||||||
@@ -76,7 +72,6 @@ var genericBuiltins = []string{
|
|||||||
"floatunsisf.c",
|
"floatunsisf.c",
|
||||||
"floatuntidf.c",
|
"floatuntidf.c",
|
||||||
"floatuntisf.c",
|
"floatuntisf.c",
|
||||||
"fp_mode.c",
|
|
||||||
//"int_util.c",
|
//"int_util.c",
|
||||||
"lshrdi3.c",
|
"lshrdi3.c",
|
||||||
"lshrti3.c",
|
"lshrti3.c",
|
||||||
@@ -92,6 +87,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 +107,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 +122,7 @@ var genericBuiltins = []string{
|
|||||||
"ucmpti2.c",
|
"ucmpti2.c",
|
||||||
"udivdi3.c",
|
"udivdi3.c",
|
||||||
"udivmoddi4.c",
|
"udivmoddi4.c",
|
||||||
//"udivmodsi4.c",
|
"udivmodsi4.c",
|
||||||
"udivmodti4.c",
|
"udivmodti4.c",
|
||||||
"udivsi3.c",
|
"udivsi3.c",
|
||||||
"udivti3.c",
|
"udivti3.c",
|
||||||
@@ -133,38 +131,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",
|
||||||
@@ -183,63 +149,22 @@ var aeabiBuiltins = []string{
|
|||||||
"arm/aeabi_memset.S",
|
"arm/aeabi_memset.S",
|
||||||
"arm/aeabi_uidivmod.S",
|
"arm/aeabi_uidivmod.S",
|
||||||
"arm/aeabi_uldivmod.S",
|
"arm/aeabi_uldivmod.S",
|
||||||
|
|
||||||
// These two are not technically EABI builtins but are used by them and only
|
|
||||||
// seem to be used on ARM. LLVM seems to use __divsi3 and __modsi3 on most
|
|
||||||
// other architectures.
|
|
||||||
// Most importantly, they have a different calling convention on AVR so
|
|
||||||
// should not be used on AVR.
|
|
||||||
"divmodsi4.c",
|
|
||||||
"udivmodsi4.c",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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", // also _alloca
|
|
||||||
}
|
|
||||||
|
|
||||||
// libCompilerRT is a library with symbols required by programs compiled with
|
|
||||||
// LLVM. These symbols are for operations that cannot be emitted with a single
|
|
||||||
// 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() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
|
||||||
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
|
sourceDir: "lib/compiler-rt/lib/builtins",
|
||||||
},
|
sources: func(target string) []string {
|
||||||
sourceDir: func() string {
|
|
||||||
llvmDir := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project/compiler-rt/lib/builtins")
|
|
||||||
if _, err := os.Stat(llvmDir); err == nil {
|
|
||||||
// Release build.
|
|
||||||
return llvmDir
|
|
||||||
}
|
|
||||||
// Development build.
|
|
||||||
return filepath.Join(goenv.Get("TINYGOROOT"), "lib/compiler-rt-builtins")
|
|
||||||
},
|
|
||||||
librarySources: func(target string, _ bool) ([]string, error) {
|
|
||||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||||
switch compileopts.CanonicalArchName(target) {
|
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||||
case "arm":
|
|
||||||
builtins = append(builtins, aeabiBuiltins...)
|
builtins = append(builtins, aeabiBuiltins...)
|
||||||
case "avr":
|
|
||||||
builtins = append(builtins, avrBuiltins...)
|
|
||||||
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
|
|
||||||
builtins = append(builtins, genericBuiltins128...)
|
|
||||||
case "i386":
|
|
||||||
if strings.Split(target, "-")[2] == "windows" {
|
|
||||||
builtins = append(builtins, windowsI386Builtins...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return builtins, nil
|
return builtins
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-51
@@ -10,7 +10,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -33,40 +33,36 @@ import (
|
|||||||
// Because of this complexity, every file has in fact two cached build outputs:
|
// Because of this complexity, every file has in fact two cached build outputs:
|
||||||
// the file itself, and the list of dependencies. Its operation is as follows:
|
// the file itself, and the list of dependencies. Its operation is as follows:
|
||||||
//
|
//
|
||||||
// depfile = hash(path, compiler, cflags, ...)
|
// depfile = hash(path, compiler, cflags, ...)
|
||||||
// if depfile exists:
|
// if depfile exists:
|
||||||
// outfile = hash of all files and depfile name
|
// outfile = hash of all files and depfile name
|
||||||
// if outfile exists:
|
// if outfile exists:
|
||||||
// # cache hit
|
// # cache hit
|
||||||
// return outfile
|
// return outfile
|
||||||
// # cache miss
|
// # cache miss
|
||||||
// tmpfile = compile file
|
// tmpfile = compile file
|
||||||
// read dependencies (side effect of compile)
|
// read dependencies (side effect of compile)
|
||||||
// write depfile
|
// write depfile
|
||||||
// outfile = hash of all files and depfile name
|
// outfile = hash of all files and depfile name
|
||||||
// rename tmpfile to outfile
|
// rename tmpfile to outfile
|
||||||
//
|
//
|
||||||
// There are a few edge cases that are not handled:
|
// There are a few edge cases that are not handled:
|
||||||
// - If a file is added to an include path, that file may be included instead of
|
// - If a file is added to an include path, that file may be included instead of
|
||||||
// some other file. This would be fixed by also including lookup failures in the
|
// some other file. This would be fixed by also including lookup failures in the
|
||||||
// dependencies file, but I'm not aware of a compiler which does that.
|
// dependencies file, but I'm not aware of a compiler which does that.
|
||||||
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
||||||
// details.
|
// details.
|
||||||
// - A header file may be changed to add/remove an include. This invalidates the
|
// - A header file may be changed to add/remove an include. This invalidates the
|
||||||
// depfile but without invalidating its name. For this reason, the depfile is
|
// 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, printCommands bool) (string, error) {
|
||||||
// Hash input file.
|
// Hash input file.
|
||||||
fileHash, err := hashFile(abspath)
|
fileHash, err := hashFile(abspath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Acquire a lock (if supported).
|
|
||||||
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
|
|
||||||
defer unlock()
|
|
||||||
|
|
||||||
// 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
|
||||||
@@ -88,7 +84,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
|
|||||||
// Load dependencies file, if possible.
|
// Load dependencies file, if possible.
|
||||||
depfileName := "dep-" + depfileNameHash + ".json"
|
depfileName := "dep-" + depfileNameHash + ".json"
|
||||||
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
||||||
depfileBuf, err := os.ReadFile(depfileCachePath)
|
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
|
||||||
var dependencies []string // sorted list of dependency paths
|
var dependencies []string // sorted list of dependency paths
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// There is a dependency file, that's great!
|
// There is a dependency file, that's great!
|
||||||
@@ -103,27 +99,27 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
|
|||||||
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
|
||||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
} else if !os.IsNotExist(err) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
} else if !os.IsNotExist(err) {
|
||||||
// expected either nil or IsNotExist
|
// expected either nil or IsNotExist
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
objTmpFile, err := os.CreateTemp(goenv.Get("GOCACHE"), "tmp-*.bc")
|
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
objTmpFile.Close()
|
objTmpFile.Close()
|
||||||
depTmpFile, err := os.CreateTemp(tmpdir, "dep-*.d")
|
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
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
|
||||||
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
|
||||||
@@ -132,8 +128,8 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
|
|||||||
// flags (for the assembler) is a compiler error.
|
// flags (for the assembler) is a compiler error.
|
||||||
flags = append(flags, "-Qunused-arguments")
|
flags = append(flags, "-Qunused-arguments")
|
||||||
}
|
}
|
||||||
if printCommands != nil {
|
if printCommands {
|
||||||
printCommands("clang", flags...)
|
fmt.Printf("clang %s\n", strings.Join(flags, " "))
|
||||||
}
|
}
|
||||||
err = runCCompiler(flags...)
|
err = runCCompiler(flags...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,11 +154,7 @@ func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands
|
|||||||
sort.Strings(dependencySlice)
|
sort.Strings(dependencySlice)
|
||||||
|
|
||||||
// Write dependencies file.
|
// Write dependencies file.
|
||||||
f, err := os.CreateTemp(filepath.Dir(depfileCachePath), depfileName)
|
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err) // shouldn't happen
|
panic(err) // shouldn't happen
|
||||||
@@ -221,7 +213,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+".o")
|
||||||
return outpath, nil
|
return outpath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,14 +236,13 @@ func hashFile(path string) (string, error) {
|
|||||||
// file is assumed to have a single target named deps.
|
// file is assumed to have a single target named deps.
|
||||||
//
|
//
|
||||||
// There are roughly three make syntax variants:
|
// There are roughly three make syntax variants:
|
||||||
// - BSD make, which doesn't support any escaping. This means that many special
|
// - BSD make, which doesn't support any escaping. This means that many special
|
||||||
// characters are not supported in file names.
|
// characters are not supported in file names.
|
||||||
// - GNU make, which supports escaping using a backslash but when it fails to
|
// - GNU make, which supports escaping using a backslash but when it fails to
|
||||||
// find a file it tries to fall back with the literal path name (to match BSD
|
// find a file it tries to fall back with the literal path name (to match BSD
|
||||||
// make).
|
// make).
|
||||||
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
||||||
// any weird characters.
|
// any weird characters.
|
||||||
//
|
|
||||||
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
||||||
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
||||||
// is at least somewhat sane. This last format isn't perfect either: it does not
|
// is at least somewhat sane. This last format isn't perfect either: it does not
|
||||||
@@ -259,7 +250,7 @@ func hashFile(path string) (string, error) {
|
|||||||
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
||||||
// it's the most sane of any of the formats so readDepFile will use that format.
|
// it's the most sane of any of the formats so readDepFile will use that format.
|
||||||
func readDepFile(filename string) ([]string, error) {
|
func readDepFile(filename string) ([]string, error) {
|
||||||
buf, err := os.ReadFile(filename)
|
buf, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-125
@@ -1,11 +1,4 @@
|
|||||||
//go:build byollvm
|
// +build byollvm
|
||||||
|
|
||||||
// Source: https://github.com/llvm/llvm-project/blob/main/clang/tools/driver/cc1as_main.cpp
|
|
||||||
// This file needs to be updated each LLVM release.
|
|
||||||
// There are a few small modifications to make, like:
|
|
||||||
// * ExecuteAssembler is made non-static.
|
|
||||||
// * The struct AssemblerImplementation is moved to cc1as.h so it can be
|
|
||||||
// included elsewhere.
|
|
||||||
|
|
||||||
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
|
//===-- cc1as.cpp - Clang Assembler --------------------------------------===//
|
||||||
//
|
//
|
||||||
@@ -28,8 +21,8 @@
|
|||||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||||
#include "clang/Frontend/Utils.h"
|
#include "clang/Frontend/Utils.h"
|
||||||
#include "llvm/ADT/STLExtras.h"
|
#include "llvm/ADT/STLExtras.h"
|
||||||
#include "llvm/ADT/StringExtras.h"
|
|
||||||
#include "llvm/ADT/StringSwitch.h"
|
#include "llvm/ADT/StringSwitch.h"
|
||||||
|
#include "llvm/ADT/Triple.h"
|
||||||
#include "llvm/IR/DataLayout.h"
|
#include "llvm/IR/DataLayout.h"
|
||||||
#include "llvm/MC/MCAsmBackend.h"
|
#include "llvm/MC/MCAsmBackend.h"
|
||||||
#include "llvm/MC/MCAsmInfo.h"
|
#include "llvm/MC/MCAsmInfo.h"
|
||||||
@@ -45,7 +38,6 @@
|
|||||||
#include "llvm/MC/MCStreamer.h"
|
#include "llvm/MC/MCStreamer.h"
|
||||||
#include "llvm/MC/MCSubtargetInfo.h"
|
#include "llvm/MC/MCSubtargetInfo.h"
|
||||||
#include "llvm/MC/MCTargetOptions.h"
|
#include "llvm/MC/MCTargetOptions.h"
|
||||||
#include "llvm/MC/TargetRegistry.h"
|
|
||||||
#include "llvm/Option/Arg.h"
|
#include "llvm/Option/Arg.h"
|
||||||
#include "llvm/Option/ArgList.h"
|
#include "llvm/Option/ArgList.h"
|
||||||
#include "llvm/Option/OptTable.h"
|
#include "llvm/Option/OptTable.h"
|
||||||
@@ -53,18 +45,17 @@
|
|||||||
#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"
|
||||||
#include "llvm/Support/Signals.h"
|
#include "llvm/Support/Signals.h"
|
||||||
#include "llvm/Support/SourceMgr.h"
|
#include "llvm/Support/SourceMgr.h"
|
||||||
|
#include "llvm/Support/TargetRegistry.h"
|
||||||
#include "llvm/Support/TargetSelect.h"
|
#include "llvm/Support/TargetSelect.h"
|
||||||
#include "llvm/Support/Timer.h"
|
#include "llvm/Support/Timer.h"
|
||||||
#include "llvm/Support/raw_ostream.h"
|
#include "llvm/Support/raw_ostream.h"
|
||||||
#include "llvm/TargetParser/Host.h"
|
|
||||||
#include "llvm/TargetParser/Triple.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
using namespace clang;
|
using namespace clang;
|
||||||
using namespace clang::driver;
|
using namespace clang::driver;
|
||||||
@@ -82,10 +73,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
// Parse the arguments.
|
// Parse the arguments.
|
||||||
const OptTable &OptTbl = getDriverOptTable();
|
const OptTable &OptTbl = getDriverOptTable();
|
||||||
|
|
||||||
llvm::opt::Visibility VisibilityMask(options::CC1AsOption);
|
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
|
||||||
unsigned MissingArgIndex, MissingArgCount;
|
unsigned MissingArgIndex, MissingArgCount;
|
||||||
InputArgList Args =
|
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
||||||
OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount, VisibilityMask);
|
IncludedFlagsBitmask);
|
||||||
|
|
||||||
// Check for missing argument error.
|
// Check for missing argument error.
|
||||||
if (MissingArgCount) {
|
if (MissingArgCount) {
|
||||||
@@ -98,7 +89,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
||||||
auto ArgString = A->getAsString(Args);
|
auto ArgString = A->getAsString(Args);
|
||||||
std::string Nearest;
|
std::string Nearest;
|
||||||
if (OptTbl.findNearest(ArgString, Nearest, VisibilityMask) > 1)
|
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
||||||
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
||||||
else
|
else
|
||||||
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
||||||
@@ -110,17 +101,6 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
|
|
||||||
// Target Options
|
// Target Options
|
||||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||||
if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
|
|
||||||
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
|
|
||||||
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) {
|
|
||||||
VersionTuple Version;
|
|
||||||
if (Version.tryParse(A->getValue()))
|
|
||||||
Diags.Report(diag::err_drv_invalid_value)
|
|
||||||
<< A->getAsString(Args) << A->getValue();
|
|
||||||
else
|
|
||||||
Opts.DarwinTargetVariantSDKVersion = Version;
|
|
||||||
}
|
|
||||||
|
|
||||||
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
||||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||||
|
|
||||||
@@ -135,30 +115,35 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
|||||||
// Any DebugInfoKind implies GenDwarfForAssembly.
|
// Any DebugInfoKind implies GenDwarfForAssembly.
|
||||||
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
||||||
|
|
||||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections_EQ)) {
|
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
|
||||||
Opts.CompressDebugSections =
|
OPT_compress_debug_sections_EQ)) {
|
||||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
if (A->getOption().getID() == OPT_compress_debug_sections) {
|
||||||
.Case("none", llvm::DebugCompressionType::None)
|
// TODO: be more clever about the compression type auto-detection
|
||||||
.Case("zlib", llvm::DebugCompressionType::Zlib)
|
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
|
||||||
.Case("zstd", llvm::DebugCompressionType::Zstd)
|
} else {
|
||||||
.Default(llvm::DebugCompressionType::None);
|
Opts.CompressDebugSections =
|
||||||
|
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
||||||
|
.Case("none", llvm::DebugCompressionType::None)
|
||||||
|
.Case("zlib", llvm::DebugCompressionType::Z)
|
||||||
|
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
||||||
|
.Default(llvm::DebugCompressionType::None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32))
|
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||||
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);
|
||||||
Opts.DwarfDebugFlags =
|
Opts.DwarfDebugFlags =
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
||||||
Opts.DwarfDebugProducer =
|
Opts.DwarfDebugProducer =
|
||||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
||||||
if (const Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ,
|
Opts.DebugCompilationDir =
|
||||||
options::OPT_fdebug_compilation_dir_EQ))
|
std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir));
|
||||||
Opts.DebugCompilationDir = A->getValue();
|
|
||||||
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
||||||
|
|
||||||
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 +190,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 +207,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +219,7 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
|||||||
|
|
||||||
std::error_code EC;
|
std::error_code EC;
|
||||||
auto Out = std::make_unique<raw_fd_ostream>(
|
auto Out = std::make_unique<raw_fd_ostream>(
|
||||||
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_TextWithCRLF));
|
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
|
||||||
if (EC) {
|
if (EC) {
|
||||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -261,8 +228,7 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
|||||||
return Out;
|
return Out;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||||
DiagnosticsEngine &Diags) {
|
|
||||||
// Get the target specific parser.
|
// Get the target specific parser.
|
||||||
std::string Error;
|
std::string Error;
|
||||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
||||||
@@ -270,11 +236,11 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
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);
|
||||||
|
|
||||||
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 +256,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())
|
||||||
@@ -320,15 +277,11 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
if (!Opts.SplitDwarfOutput.empty())
|
if (!Opts.SplitDwarfOutput.empty())
|
||||||
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
||||||
|
|
||||||
// Build up the feature string from the target feature list.
|
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
||||||
std::string FS = llvm::join(Opts.Features, ",");
|
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||||
|
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
||||||
|
|
||||||
std::unique_ptr<MCSubtargetInfo> STI(
|
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
|
||||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
|
||||||
assert(STI && "Unable to create subtarget info!");
|
|
||||||
|
|
||||||
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
|
|
||||||
&MCOptions);
|
|
||||||
|
|
||||||
bool PIC = false;
|
bool PIC = false;
|
||||||
if (Opts.RelocationModel == "static") {
|
if (Opts.RelocationModel == "static") {
|
||||||
@@ -341,12 +294,9 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
PIC = false;
|
PIC = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
|
||||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
if (Opts.SaveTemporaryLabels)
|
||||||
std::unique_ptr<MCObjectFileInfo> MOFI(
|
Ctx.setAllowTemporaryLabels(false);
|
||||||
TheTarget->createMCObjectFileInfo(Ctx, PIC));
|
|
||||||
Ctx.setObjectFileInfo(MOFI.get());
|
|
||||||
|
|
||||||
if (Opts.GenDwarfForAssembly)
|
if (Opts.GenDwarfForAssembly)
|
||||||
Ctx.setGenDwarfForAssembly(true);
|
Ctx.setGenDwarfForAssembly(true);
|
||||||
if (!Opts.DwarfDebugFlags.empty())
|
if (!Opts.DwarfDebugFlags.empty())
|
||||||
@@ -366,26 +316,25 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
||||||
if (!Opts.MainFileName.empty())
|
if (!Opts.MainFileName.empty())
|
||||||
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
||||||
Ctx.setDwarfFormat(Opts.Dwarf64 ? dwarf::DWARF64 : dwarf::DWARF32);
|
|
||||||
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
||||||
if (Opts.GenDwarfForAssembly)
|
if (Opts.GenDwarfForAssembly)
|
||||||
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
||||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||||
|
|
||||||
|
// Build up the feature string from the target feature list.
|
||||||
|
std::string FS = llvm::join(Opts.Features, ",");
|
||||||
|
|
||||||
std::unique_ptr<MCStreamer> Str;
|
std::unique_ptr<MCStreamer> Str;
|
||||||
|
|
||||||
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
||||||
assert(MCII && "Unable to create instruction info!");
|
std::unique_ptr<MCSubtargetInfo> STI(
|
||||||
|
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||||
|
|
||||||
raw_pwrite_stream *Out = FDOS.get();
|
raw_pwrite_stream *Out = FDOS.get();
|
||||||
std::unique_ptr<buffer_ostream> BOS;
|
std::unique_ptr<buffer_ostream> BOS;
|
||||||
|
|
||||||
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.
|
||||||
@@ -395,13 +344,15 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
|
|
||||||
std::unique_ptr<MCCodeEmitter> CE;
|
std::unique_ptr<MCCodeEmitter> CE;
|
||||||
if (Opts.ShowEncoding)
|
if (Opts.ShowEncoding)
|
||||||
CE.reset(TheTarget->createMCCodeEmitter(*MCII, Ctx));
|
CE.reset(TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||||
std::unique_ptr<MCAsmBackend> MAB(
|
std::unique_ptr<MCAsmBackend> MAB(
|
||||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||||
|
|
||||||
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), 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,37 +364,34 @@ 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!");
|
|
||||||
|
|
||||||
std::unique_ptr<MCObjectWriter> OW =
|
std::unique_ptr<MCObjectWriter> OW =
|
||||||
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,
|
||||||
Str.get()->initSections(Opts.NoExecStack, *STI);
|
Opts.RelaxAll, Opts.IncrementalLinkerCompatible,
|
||||||
if (T.isOSBinFormatMachO() && T.isOSDarwin()) {
|
/*DWARFMustBeAtTheEnd*/ true));
|
||||||
Triple *TVT = Opts.DarwinTargetVariantTriple
|
Str.get()->InitSections(Opts.NoExecStack);
|
||||||
? &*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
|
||||||
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
||||||
if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) {
|
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
|
||||||
|
MCObjectFileInfo::IsMachO) {
|
||||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
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(
|
||||||
@@ -471,12 +419,12 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
|||||||
Failed = Parser->Run(Opts.NoInitialTextSection);
|
Failed = Parser->Run(Opts.NoInitialTextSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Failed;
|
// Close Streamer first.
|
||||||
}
|
// It might have a reference to the output stream.
|
||||||
|
Str.reset();
|
||||||
bool ExecuteAssembler(AssemblerInvocation &Opts,
|
// Close the output stream early.
|
||||||
DiagnosticsEngine &Diags) {
|
BOS.reset();
|
||||||
bool Failed = ExecuteAssemblerImpl(Opts, Diags);
|
FDOS.reset();
|
||||||
|
|
||||||
// Delete output file if there were errors.
|
// Delete output file if there were errors.
|
||||||
if (Failed) {
|
if (Failed) {
|
||||||
@@ -489,7 +437,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts,
|
|||||||
return Failed;
|
return Failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LLVMErrorHandler(void *UserData, const char *Message,
|
static void LLVMErrorHandler(void *UserData, const std::string &Message,
|
||||||
bool GenCrashDiag) {
|
bool GenCrashDiag) {
|
||||||
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
|
DiagnosticsEngine &Diags = *static_cast<DiagnosticsEngine*>(UserData);
|
||||||
|
|
||||||
@@ -524,12 +472,11 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
|||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
if (Asm.ShowHelp) {
|
if (Asm.ShowHelp) {
|
||||||
getDriverOptTable().printHelp(
|
getDriverOptTable().PrintHelp(
|
||||||
llvm::outs(), "clang -cc1as [options] file...",
|
llvm::outs(), "clang -cc1as [options] file...",
|
||||||
"Clang Integrated Assembler", /*ShowHidden=*/false,
|
"Clang Integrated Assembler",
|
||||||
/*ShowAllAliases=*/false,
|
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||||
llvm::opt::Visibility(driver::options::CC1AsOption));
|
/*ShowAllAliases=*/false);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-57
@@ -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.
|
||||||
@@ -38,19 +35,15 @@ 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 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 +62,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 +70,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,16 +91,6 @@ struct AssemblerInvocation {
|
|||||||
/// otherwise.
|
/// otherwise.
|
||||||
std::string TargetABI;
|
std::string TargetABI;
|
||||||
|
|
||||||
/// Darwin target variant triple, the variant of the deployment target
|
|
||||||
/// for which the code is being compiled.
|
|
||||||
std::optional<llvm::Triple> DarwinTargetVariantTriple;
|
|
||||||
|
|
||||||
/// The version of the darwin target variant SDK which was used during the
|
|
||||||
/// compilation
|
|
||||||
llvm::VersionTuple DarwinTargetVariantSDKVersion;
|
|
||||||
|
|
||||||
/// The name of a file to use with \c .secure_log_unique directives.
|
|
||||||
std::string AsSecureLogFile;
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -154,17 +107,9 @@ public:
|
|||||||
NoExecStack = 0;
|
NoExecStack = 0;
|
||||||
FatalWarnings = 0;
|
FatalWarnings = 0;
|
||||||
NoWarn = 0;
|
NoWarn = 0;
|
||||||
NoTypeCheck = 0;
|
|
||||||
IncrementalLinkerCompatible = 0;
|
IncrementalLinkerCompatible = 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,
|
||||||
|
|||||||
+3
-3
@@ -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;
|
||||||
@@ -60,7 +60,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the actual diagnostics engine.
|
// Create the actual diagnostics engine.
|
||||||
Clang->createDiagnostics(*llvm::vfs::getRealFileSystem());
|
Clang->createDiagnostics();
|
||||||
if (!Clang->hasDiagnostics()) {
|
if (!Clang->hasDiagnostics()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-25
@@ -2,7 +2,7 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -21,24 +21,13 @@ func init() {
|
|||||||
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"}
|
||||||
commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
|
|
||||||
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
||||||
// manually set $PATH).
|
// manually set $PATH).
|
||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
var prefix string
|
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
||||||
switch runtime.GOARCH {
|
|
||||||
case "amd64":
|
|
||||||
prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
|
||||||
case "arm64":
|
|
||||||
prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/"
|
|
||||||
default:
|
|
||||||
// unknown GOARCH
|
|
||||||
panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH))
|
|
||||||
}
|
|
||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
||||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
|
||||||
}
|
}
|
||||||
// Add the path for when LLVM was installed with the installer from
|
// Add the path for when LLVM was installed with the installer from
|
||||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
||||||
@@ -47,7 +36,6 @@ func init() {
|
|||||||
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
||||||
commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
|
|
||||||
}
|
}
|
||||||
// Add the path to LLVM installed from ports.
|
// Add the path to LLVM installed from ports.
|
||||||
if runtime.GOOS == "freebsd" {
|
if runtime.GOOS == "freebsd" {
|
||||||
@@ -55,23 +43,23 @@ func init() {
|
|||||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
||||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupCommand looks up the executable name for a given LLVM tool such as
|
func execCommand(cmdNames []string, args ...string) error {
|
||||||
// clang or wasm-ld. It returns the (relative) command that can be used to
|
for _, cmdName := range cmdNames {
|
||||||
// invoke the tool or an error if it could not be found.
|
cmd := exec.Command(cmdName, args...)
|
||||||
func LookupCommand(name string) (string, error) {
|
cmd.Stdout = os.Stdout
|
||||||
for _, cmdName := range commands[name] {
|
cmd.Stderr = os.Stderr
|
||||||
_, err := exec.LookPath(cmdName)
|
err := cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Unwrap(err) == exec.ErrNotFound {
|
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
|
||||||
|
// this command was not found, try the next
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return cmdName, err
|
return err
|
||||||
}
|
}
|
||||||
return cmdName, nil
|
return nil
|
||||||
}
|
}
|
||||||
return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
|
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-36
@@ -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"
|
||||||
@@ -13,51 +13,27 @@ import (
|
|||||||
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
||||||
// version to use.
|
// version to use.
|
||||||
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||||
spec, err := compileopts.LoadTarget(options)
|
spec, err := compileopts.LoadTarget(options.Target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
goroot := goenv.Get("GOROOT")
|
||||||
if options.OpenOCDCommands != nil {
|
if goroot == "" {
|
||||||
// Override the OpenOCDCommands from the target spec if specified on
|
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||||
// the command-line
|
|
||||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
|
||||||
}
|
}
|
||||||
|
major, minor, err := goenv.GetGorootVersion(goroot)
|
||||||
// Version range supported by TinyGo.
|
|
||||||
const minorMin = 19
|
|
||||||
const minorMax = 26
|
|
||||||
|
|
||||||
// Check that we support this Go toolchain version.
|
|
||||||
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 < 13 || minor > 16 {
|
||||||
if options.GoCompatibility {
|
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||||
// 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,58 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create a job that builds a Darwin libSystem.dylib stub library. This library
|
|
||||||
// contains all the symbols needed so that we can link against it, but it
|
|
||||||
// doesn't contain any real symbol implementations.
|
|
||||||
func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJob {
|
|
||||||
return &compileJob{
|
|
||||||
description: "compile Darwin libSystem.dylib",
|
|
||||||
run: func(job *compileJob) (err error) {
|
|
||||||
arch := strings.Split(config.Triple(), "-")[0]
|
|
||||||
job.result = filepath.Join(tmpdir, "libSystem.dylib")
|
|
||||||
objpath := filepath.Join(tmpdir, "libSystem.o")
|
|
||||||
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
|
|
||||||
|
|
||||||
// Compile assembly file to object file.
|
|
||||||
flags := []string{
|
|
||||||
"-nostdlib",
|
|
||||||
"--target=" + config.Triple(),
|
|
||||||
"-c",
|
|
||||||
"-o", objpath,
|
|
||||||
inpath,
|
|
||||||
}
|
|
||||||
if config.Options.PrintCommands != nil {
|
|
||||||
config.Options.PrintCommands("clang", flags...)
|
|
||||||
}
|
|
||||||
err = runCCompiler(flags...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Link object file to dynamic library.
|
|
||||||
platformVersion := strings.TrimPrefix(strings.Split(config.Triple(), "-")[2], "macosx")
|
|
||||||
flags = []string{
|
|
||||||
"-flavor", "darwin",
|
|
||||||
"-demangle",
|
|
||||||
"-dynamic",
|
|
||||||
"-dylib",
|
|
||||||
"-arch", arch,
|
|
||||||
"-platform_version", "macos", platformVersion, platformVersion,
|
|
||||||
"-install_name", "/usr/lib/libSystem.B.dylib",
|
|
||||||
"-o", job.result,
|
|
||||||
objpath,
|
|
||||||
}
|
|
||||||
if config.Options.PrintCommands != nil {
|
|
||||||
config.Options.PrintCommands("ld.lld", flags...)
|
|
||||||
}
|
|
||||||
return link("ld.lld", flags...)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"debug/elf"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
|
|
||||||
elfFile, err := elf.Open(executable)
|
|
||||||
if err != nil {
|
|
||||||
return nil, elf.FileHeader{}, err
|
|
||||||
}
|
|
||||||
defer elfFile.Close()
|
|
||||||
|
|
||||||
section := elfFile.Section(sectionName)
|
|
||||||
if section == nil {
|
|
||||||
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := section.Data()
|
|
||||||
|
|
||||||
return data, elfFile.FileHeader, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceElfSection(executable string, sectionName string, data []byte) error {
|
|
||||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fp.Close()
|
|
||||||
|
|
||||||
elfFile, err := elf.Open(executable)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer elfFile.Close()
|
|
||||||
|
|
||||||
section := elfFile.Section(sectionName)
|
|
||||||
if section == nil {
|
|
||||||
return fmt.Errorf("could not find %s section", sectionName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implicitly check for compressed sections
|
|
||||||
if section.Size != section.FileSize {
|
|
||||||
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only permit complete replacement of section
|
|
||||||
if section.Size != uint64(len(data)) {
|
|
||||||
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the replacement section data
|
|
||||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||||
|
// multiple locations, which should make it find the directory when installed in
|
||||||
|
// various ways.
|
||||||
|
func getClangHeaderPath(TINYGOROOT string) string {
|
||||||
|
// Check whether we're running from the source directory.
|
||||||
|
path := filepath.Join(TINYGOROOT, "llvm-project", "clang", "lib", "Headers")
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether we're running from the installation directory.
|
||||||
|
path = filepath.Join(TINYGOROOT, "lib", "clang", "include")
|
||||||
|
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// It looks like we are built with a system-installed LLVM. Do a last
|
||||||
|
// attempt: try to use Clang headers relative to the clang binary.
|
||||||
|
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||||
|
for _, cmdName := range commands["clang"] {
|
||||||
|
binpath, err := exec.LookPath(cmdName)
|
||||||
|
if err == nil {
|
||||||
|
// This should be the command that will also be used by
|
||||||
|
// execCommand. To avoid inconsistencies, make sure we use the
|
||||||
|
// headers relative to this command.
|
||||||
|
binpath, err = filepath.EvalSymlinks(binpath)
|
||||||
|
if err != nil {
|
||||||
|
// Unexpected.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// Example executable:
|
||||||
|
// /usr/lib/llvm-9/bin/clang
|
||||||
|
// Example include path:
|
||||||
|
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
|
||||||
|
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
||||||
|
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
|
||||||
|
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
|
||||||
|
// Example include path:
|
||||||
|
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||||
|
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
|
||||||
|
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
|
||||||
|
if err64 != nil && err32 != nil {
|
||||||
|
// Unexpected.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dirnames := make([]string, len(dirs64)+len(dirs32))
|
||||||
|
dirCount := 0
|
||||||
|
for _, d := range dirs32 {
|
||||||
|
name := d.Name()
|
||||||
|
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||||
|
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
|
||||||
|
dirCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, d := range dirs64 {
|
||||||
|
name := d.Name()
|
||||||
|
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||||
|
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
|
||||||
|
dirCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Strings(dirnames)
|
||||||
|
// Check for the highest version first.
|
||||||
|
for i := dirCount - 1; i >= 0; i-- {
|
||||||
|
path := filepath.Join(dirnames[i], "include")
|
||||||
|
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||||
|
if err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Could not find it.
|
||||||
|
return ""
|
||||||
|
}
|
||||||
+5
-7
@@ -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}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-52
@@ -13,9 +13,8 @@ import (
|
|||||||
"debug/elf"
|
"debug/elf"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type espImageSegment struct {
|
type espImageSegment struct {
|
||||||
@@ -23,7 +22,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 +30,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
|
||||||
@@ -79,38 +78,11 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
|||||||
// 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.
|
||||||
outf := &bytes.Buffer{}
|
outf := &bytes.Buffer{}
|
||||||
|
|
||||||
// Separate esp32 and esp32-img. The -img suffix indicates we should make an
|
|
||||||
// image, not just a binary to be flashed at 0x1000 for example.
|
|
||||||
chip := format
|
|
||||||
makeImage := false
|
|
||||||
if strings.HasSuffix(format, "-img") {
|
|
||||||
makeImage = true
|
|
||||||
chip = format[:len(format)-len("-img")]
|
|
||||||
}
|
|
||||||
|
|
||||||
if makeImage {
|
|
||||||
// The bootloader starts at 0x1000, or 4096.
|
|
||||||
// TinyGo doesn't use a separate bootloader and runs the entire
|
|
||||||
// application in the bootloader location.
|
|
||||||
outf.Write(make([]byte, 4096))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chip IDs. Source:
|
|
||||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
|
|
||||||
chip_id := map[string]uint16{
|
|
||||||
"esp32": 0x0000,
|
|
||||||
"esp32c3": 0x0005,
|
|
||||||
"esp32s3": 0x0009,
|
|
||||||
}[chip]
|
|
||||||
|
|
||||||
// Image header.
|
// Image header.
|
||||||
switch chip {
|
switch format {
|
||||||
case "esp32", "esp32c3", "esp32s3":
|
case "esp32":
|
||||||
// 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/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
|
||||||
// Note: not adding a SHA256 hash as the binary is modified by
|
|
||||||
// esptool.py while flashing and therefore the hash won't be valid
|
|
||||||
// anymore.
|
|
||||||
binary.Write(outf, binary.LittleEndian, struct {
|
binary.Write(outf, binary.LittleEndian, struct {
|
||||||
magic uint8
|
magic uint8
|
||||||
segment_count uint8
|
segment_count uint8
|
||||||
@@ -119,18 +91,15 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
|||||||
entry_addr uint32
|
entry_addr uint32
|
||||||
wp_pin uint8
|
wp_pin uint8
|
||||||
spi_pin_drv [3]uint8
|
spi_pin_drv [3]uint8
|
||||||
chip_id uint16
|
reserved [11]uint8
|
||||||
min_chip_rev uint8
|
|
||||||
reserved [8]uint8
|
|
||||||
hash_appended bool
|
hash_appended bool
|
||||||
}{
|
}{
|
||||||
magic: 0xE9,
|
magic: 0xE9,
|
||||||
segment_count: byte(len(segments)),
|
segment_count: byte(len(segments)),
|
||||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
||||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
|
||||||
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,
|
|
||||||
hash_appended: true, // add a SHA256 hash
|
hash_appended: true, // add a SHA256 hash
|
||||||
})
|
})
|
||||||
case "esp8266":
|
case "esp8266":
|
||||||
@@ -173,22 +142,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
|||||||
outf.Write(make([]byte, 15-outf.Len()%16))
|
outf.Write(make([]byte, 15-outf.Len()%16))
|
||||||
outf.WriteByte(checksum)
|
outf.WriteByte(checksum)
|
||||||
|
|
||||||
if chip != "esp8266" {
|
if format == "esp32" {
|
||||||
// SHA256 hash (to protect against image corruption, not for security).
|
// SHA256 hash (to protect against image corruption, not for security).
|
||||||
hash := sha256.Sum256(outf.Bytes())
|
hash := sha256.Sum256(outf.Bytes())
|
||||||
outf.Write(hash[:])
|
outf.Write(hash[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
|
|
||||||
// image to be a certain size.
|
|
||||||
if makeImage {
|
|
||||||
// Use a default image size of 4MB.
|
|
||||||
grow := 4096*1024 - outf.Len()
|
|
||||||
if grow > 0 {
|
|
||||||
outf.Write(make([]byte, grow))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the image to the output file.
|
// Write the image to the output file.
|
||||||
return os.WriteFile(outfile, outf.Bytes(), 0666)
|
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-139
@@ -4,12 +4,8 @@ package builder
|
|||||||
// parallel while taking care of dependencies.
|
// parallel while taking care of dependencies.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/heap"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,6 +13,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.
|
||||||
@@ -25,6 +29,7 @@ type compileJob struct {
|
|||||||
dependencies []*compileJob
|
dependencies []*compileJob
|
||||||
result string // result (path)
|
result string // result (path)
|
||||||
run func(*compileJob) (err error)
|
run func(*compileJob) (err error)
|
||||||
|
state jobState
|
||||||
err error // error if finished
|
err error // error if finished
|
||||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
duration time.Duration // how long it took to run this job (only set after finishing)
|
||||||
}
|
}
|
||||||
@@ -39,121 +44,91 @@ func dummyCompileJob(result string) *compileJob {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runJobs runs the indicated job and all its dependencies. For every job, all
|
// readyToRun returns whether this job is ready to run: it is itself not yet
|
||||||
// the dependencies are run first. It returns the error of the first job that
|
// started and all dependencies are finished.
|
||||||
// fails.
|
func (job *compileJob) readyToRun() bool {
|
||||||
// It runs all jobs in the order of the dependencies slice, depth-first.
|
if job.state != jobStateQueued {
|
||||||
// Therefore, if some jobs are preferred to run before others, they should be
|
// Already running or finished, so shouldn't be run again.
|
||||||
// ordered as such in the job dependencies.
|
return false
|
||||||
func runJobs(job *compileJob, sema chan struct{}) error {
|
|
||||||
if sema == nil {
|
|
||||||
// Have a default, if the semaphore isn't set. This is useful for tests.
|
|
||||||
sema = make(chan struct{}, runtime.NumCPU())
|
|
||||||
}
|
|
||||||
if cap(sema) == 0 {
|
|
||||||
return errors.New("cannot run 0 jobs at a time")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a slice of jobs to run, where all dependencies are run in order.
|
// Check dependencies.
|
||||||
jobs := []*compileJob{}
|
for _, dep := range job.dependencies {
|
||||||
addedJobs := map[*compileJob]struct{}{}
|
if dep.state != jobStateFinished {
|
||||||
var addJobs func(*compileJob)
|
// A dependency is not finished, so this job has to wait until it
|
||||||
addJobs = func(job *compileJob) {
|
// is.
|
||||||
if _, ok := addedJobs[job]; ok {
|
return false
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, dep := range job.dependencies {
|
|
||||||
addJobs(dep)
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
addedJobs[job] = struct{}{}
|
|
||||||
}
|
|
||||||
addJobs(job)
|
|
||||||
|
|
||||||
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
|
|
||||||
dependents := make(map[*compileJob][]*compileJob, len(jobs))
|
|
||||||
compileJobs := make(map[*compileJob]int)
|
|
||||||
var ready intHeap
|
|
||||||
for i, job := range jobs {
|
|
||||||
compileJobs[job] = i
|
|
||||||
if len(job.dependencies) == 0 {
|
|
||||||
// This job is ready to run.
|
|
||||||
ready.Push(i)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a map for dependencies which the job is currently waiting on.
|
|
||||||
waitDeps := make(map[*compileJob]struct{})
|
|
||||||
waiting[job] = waitDeps
|
|
||||||
|
|
||||||
// Add the job to the dependents list of each dependency.
|
|
||||||
for _, dep := range job.dependencies {
|
|
||||||
dependents[dep] = append(dependents[dep], job)
|
|
||||||
waitDeps[dep] = struct{}{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a channel to accept notifications of completion.
|
// All conditions are satisfied.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// runJobs runs all the jobs indicated in the jobs slice and returns the error
|
||||||
|
// of the first job that fails to run.
|
||||||
|
// It runs all jobs in the order of the slice, as long as all dependencies have
|
||||||
|
// already run. Therefore, if some jobs are preferred to run before others, they
|
||||||
|
// should be ordered as such in this slice.
|
||||||
|
func runJobs(jobs []*compileJob) error {
|
||||||
|
// Create channels to communicate with the workers.
|
||||||
doneChan := make(chan *compileJob)
|
doneChan := make(chan *compileJob)
|
||||||
|
workerChan := make(chan *compileJob)
|
||||||
|
defer close(workerChan)
|
||||||
|
|
||||||
// Send each job in the jobs slice to a worker, taking care of job dependencies.
|
// Start a number of workers.
|
||||||
|
for i := 0; i < runtime.NumCPU(); i++ {
|
||||||
|
if jobRunnerDebug {
|
||||||
|
fmt.Println("## starting worker", i)
|
||||||
|
}
|
||||||
|
go jobWorker(workerChan, doneChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send each job in the jobs slice to a worker, taking care of job
|
||||||
|
// dependencies.
|
||||||
numRunningJobs := 0
|
numRunningJobs := 0
|
||||||
var totalTime time.Duration
|
var totalTime time.Duration
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
for len(ready.IntSlice) > 0 || numRunningJobs != 0 {
|
for {
|
||||||
var completed *compileJob
|
// If there are free workers, try starting a new job (if one is
|
||||||
if len(ready.IntSlice) > 0 {
|
// available). If it succeeds, try again to fill the entire worker pool.
|
||||||
select {
|
if numRunningJobs < runtime.NumCPU() {
|
||||||
case sema <- struct{}{}:
|
jobToRun := nextJob(jobs)
|
||||||
// Start a job.
|
if jobToRun != nil {
|
||||||
job := jobs[heap.Pop(&ready).(int)]
|
// Start job.
|
||||||
if jobRunnerDebug {
|
if jobRunnerDebug {
|
||||||
fmt.Println("## start: ", job.description)
|
fmt.Println("## start: ", jobToRun.description)
|
||||||
}
|
}
|
||||||
go runJob(job, doneChan)
|
jobToRun.state = jobStateRunning
|
||||||
|
workerChan <- jobToRun
|
||||||
numRunningJobs++
|
numRunningJobs++
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case completed = <-doneChan:
|
|
||||||
// A job completed.
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Wait for a job to complete.
|
|
||||||
completed = <-doneChan
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When there are no jobs running, all jobs in the jobs slice must have
|
||||||
|
// been finished. Therefore, the work is done.
|
||||||
|
if numRunningJobs == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until a job is finished.
|
||||||
|
job := <-doneChan
|
||||||
|
job.state = jobStateFinished
|
||||||
numRunningJobs--
|
numRunningJobs--
|
||||||
<-sema
|
totalTime += job.duration
|
||||||
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 job.err != nil {
|
||||||
// Wait for any current jobs to finish.
|
// Wait for running jobs to finish.
|
||||||
for numRunningJobs != 0 {
|
for numRunningJobs != 0 {
|
||||||
<-doneChan
|
<-doneChan
|
||||||
numRunningJobs--
|
numRunningJobs--
|
||||||
}
|
}
|
||||||
|
// Return error of first failing job.
|
||||||
// The build failed.
|
return job.err
|
||||||
return completed.err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total run time.
|
|
||||||
totalTime += completed.duration
|
|
||||||
|
|
||||||
// Update dependent jobs.
|
|
||||||
for _, j := range dependents[completed] {
|
|
||||||
wait := waiting[j]
|
|
||||||
delete(wait, completed)
|
|
||||||
if len(wait) == 0 {
|
|
||||||
// This job is now ready to run.
|
|
||||||
ready.Push(compileJobs[j])
|
|
||||||
delete(waiting, j)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(waiting) != 0 {
|
|
||||||
// There is a dependency cycle preventing some jobs from running.
|
|
||||||
return errDependencyCycle{waiting}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some statistics, if debugging.
|
// Some statistics, if debugging.
|
||||||
@@ -170,50 +145,29 @@ func runJobs(job *compileJob, sema chan struct{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type errDependencyCycle struct {
|
// nextJob returns the first ready-to-run job.
|
||||||
waiting map[*compileJob]map[*compileJob]struct{}
|
// This is an implementation detail of runJobs.
|
||||||
}
|
func nextJob(jobs []*compileJob) *compileJob {
|
||||||
|
for _, job := range jobs {
|
||||||
func (err errDependencyCycle) Error() string {
|
if job.readyToRun() {
|
||||||
waits := make([]string, 0, len(err.waiting))
|
return job
|
||||||
for j, wait := range err.waiting {
|
|
||||||
deps := make([]string, 0, len(wait))
|
|
||||||
for dep := range wait {
|
|
||||||
deps = append(deps, dep.description)
|
|
||||||
}
|
|
||||||
sort.Strings(deps)
|
|
||||||
|
|
||||||
waits = append(waits, fmt.Sprintf("\t%s is waiting for [%s]",
|
|
||||||
j.description, strings.Join(deps, ", "),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
sort.Strings(waits)
|
|
||||||
return "deadlock:\n" + strings.Join(waits, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
type intHeap struct {
|
|
||||||
sort.IntSlice
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *intHeap) Push(x interface{}) {
|
|
||||||
h.IntSlice = append(h.IntSlice, x.(int))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *intHeap) Pop() interface{} {
|
|
||||||
x := h.IntSlice[len(h.IntSlice)-1]
|
|
||||||
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// runJob runs a compile job and notifies doneChan of completion.
|
|
||||||
func runJob(job *compileJob, doneChan chan *compileJob) {
|
|
||||||
start := time.Now()
|
|
||||||
if job.run != nil {
|
|
||||||
err := job.run(job)
|
|
||||||
if err != nil {
|
|
||||||
job.err = err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
job.duration = time.Since(start)
|
return nil
|
||||||
doneChan <- job
|
}
|
||||||
|
|
||||||
|
// jobWorker is the goroutine that runs received jobs.
|
||||||
|
// This is an implementation detail of runJobs.
|
||||||
|
func jobWorker(workerChan, doneChan chan *compileJob) {
|
||||||
|
for job := range workerChan {
|
||||||
|
start := time.Now()
|
||||||
|
if job.run != nil {
|
||||||
|
err := job.run(job)
|
||||||
|
if err != nil {
|
||||||
|
job.err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
job.duration = time.Since(start)
|
||||||
|
doneChan <- job
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-236
@@ -1,314 +1,149 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
// makeHeaders creates a header include dir for the library
|
cflags func() []string
|
||||||
makeHeaders func(target, includeDir string) error
|
|
||||||
|
|
||||||
// cflags returns the C flags specific to this library
|
// The source directory, relative to TINYGOROOT.
|
||||||
cflags func(target, headerPath string) []string
|
sourceDir string
|
||||||
|
|
||||||
// cflagsForFile returns additional C flags for a particular source file.
|
|
||||||
cflagsForFile func(path string) []string
|
|
||||||
|
|
||||||
// needsLibc is set to true if this library needs libc headers.
|
|
||||||
needsLibc bool
|
|
||||||
|
|
||||||
// The source directory.
|
|
||||||
sourceDir func() string
|
|
||||||
|
|
||||||
// The source files, relative to sourceDir.
|
// The source files, relative to sourceDir.
|
||||||
librarySources func(target string, libcNeedsMalloc bool) ([]string, error)
|
sources func(target string) []string
|
||||||
|
}
|
||||||
|
|
||||||
// The source code for the crt1.o file, relative to sourceDir.
|
// fullPath returns the full path to the source directory.
|
||||||
crt1Source string
|
func (l *Library) fullPath() string {
|
||||||
|
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sourcePaths returns a slice with the full paths to the source files.
|
||||||
|
func (l *Library) sourcePaths(target string) []string {
|
||||||
|
sources := l.sources(target)
|
||||||
|
paths := make([]string, len(sources))
|
||||||
|
for i, name := range sources {
|
||||||
|
paths[i] = filepath.Join(l.fullPath(), name)
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the library archive, possibly generating and caching it if needed.
|
||||||
|
// The resulting file is stored in the provided tmpdir, which is expected to be
|
||||||
|
// removed after the Load call.
|
||||||
|
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
||||||
|
job, err := l.load(target, "", tmpdir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||||
|
err = runJobs(jobs)
|
||||||
|
return 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 if the job and
|
||||||
// been run.
|
// job.dependencies have been run.
|
||||||
// The provided tmpdir will be used to store intermediary files and possibly the
|
// The provided tmpdir will be used to store intermediary files and possibly the
|
||||||
// output archive file, it is expected to be removed after use.
|
// output archive file, it is expected to be removed after use.
|
||||||
// As a side effect, this call creates the library header files if they didn't
|
func (l *Library) load(target, cpu, tmpdir string) (job *compileJob, err error) {
|
||||||
// exist yet.
|
// Try to load a precompiled library.
|
||||||
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
|
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
||||||
outdir := config.LibraryPath(l.name)
|
if _, err := os.Stat(precompiledPath); err == nil {
|
||||||
archiveFilePath := filepath.Join(outdir, "lib.a")
|
// Found a precompiled library for this OS/architecture. Return the path
|
||||||
|
// directly.
|
||||||
|
return dummyCompileJob(precompiledPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Create a lock on the output (if supported).
|
var outfile string
|
||||||
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
|
if cpu != "" {
|
||||||
outname := filepath.Base(outdir)
|
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
||||||
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), outname+".lock"))
|
} else {
|
||||||
var ok bool
|
outfile = l.name + "-" + target + ".a"
|
||||||
defer func() {
|
}
|
||||||
if !ok {
|
|
||||||
unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Try to fetch this library from the cache.
|
// Try to fetch this library from the cache.
|
||||||
if _, err := os.Stat(archiveFilePath); err == nil {
|
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
||||||
return dummyCompileJob(archiveFilePath), func() {}, nil
|
// Cache hit.
|
||||||
|
return dummyCompileJob(path), nil
|
||||||
}
|
}
|
||||||
// Cache miss, build it now.
|
// Cache miss, build it now.
|
||||||
|
|
||||||
// Create the destination directory where the components of this library
|
|
||||||
// (lib.a file, include directory) are placed.
|
|
||||||
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
|
|
||||||
if err != nil {
|
|
||||||
// Could not create directory (and not because it already exists).
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make headers if needed.
|
|
||||||
headerPath := filepath.Join(outdir, "include")
|
|
||||||
target := config.Triple()
|
|
||||||
if l.makeHeaders != nil {
|
|
||||||
if _, err = os.Stat(headerPath); err != nil {
|
|
||||||
temporaryHeaderPath, err := os.MkdirTemp(outdir, "include.tmp*")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(temporaryHeaderPath)
|
|
||||||
err = l.makeHeaders(target, temporaryHeaderPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
err = os.Chmod(temporaryHeaderPath, 0o755) // TempDir uses 0o700 by default
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
err = os.Rename(temporaryHeaderPath, headerPath)
|
|
||||||
if err != nil {
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, fs.ErrExist):
|
|
||||||
// Another invocation of TinyGo also seems to have already created the headers.
|
|
||||||
|
|
||||||
case runtime.GOOS == "windows" && errors.Is(err, fs.ErrPermission):
|
|
||||||
// On Windows, a rename with a destination directory that already
|
|
||||||
// exists does not result in an IsExist error, but rather in an
|
|
||||||
// access denied error. To be sure, check for this case by checking
|
|
||||||
// whether the target directory exists.
|
|
||||||
if _, err := os.Stat(headerPath); err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
||||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
||||||
err = os.Mkdir(dir, 0777)
|
err = os.Mkdir(dir, 0777)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Precalculate the flags to the compiler invocation.
|
// Precalculate the flags to the compiler invocation.
|
||||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||||
// reproducible. Otherwise the temporary directory is stored in the archive
|
// reproducible. Otherwise the temporary directory is stored in the archive
|
||||||
// itself, which varies each run.
|
// itself, which varies each run.
|
||||||
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
args := append(l.cflags(), "-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()
|
|
||||||
if cpu != "" {
|
if cpu != "" {
|
||||||
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
|
args = append(args, "-mcpu="+cpu)
|
||||||
// However, ARM has not done this.
|
|
||||||
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
|
|
||||||
args = append(args, "-march="+cpu)
|
|
||||||
} else if strings.HasPrefix(target, "avr") {
|
|
||||||
args = append(args, "-mmcu="+cpu)
|
|
||||||
} else {
|
|
||||||
args = append(args, "-mcpu="+cpu)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if config.ABI() != "" {
|
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||||
args = append(args, "-mabi="+config.ABI())
|
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
||||||
}
|
}
|
||||||
switch compileopts.CanonicalArchName(target) {
|
if strings.HasPrefix(target, "riscv32-") {
|
||||||
case "arm":
|
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||||
if strings.Split(target, "-")[2] == "linux" {
|
|
||||||
args = append(args, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
|
|
||||||
} else {
|
|
||||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
|
|
||||||
}
|
|
||||||
case "avr":
|
|
||||||
// AVR defaults to C float and double both being 32-bit. This deviates
|
|
||||||
// from what most code (and certainly compiler-rt) expects. So we need
|
|
||||||
// to force the compiler to use 64-bit floating point numbers for
|
|
||||||
// double.
|
|
||||||
args = append(args, "-mdouble=64")
|
|
||||||
case "riscv32":
|
|
||||||
args = append(args, "-march="+riscvMarch(config, "rv32imac"), "-fforce-enable-int128")
|
|
||||||
case "riscv64":
|
|
||||||
args = append(args, "-march="+riscvMarch(config, "rv64gc"))
|
|
||||||
case "mips":
|
|
||||||
args = append(args, "-fno-pic")
|
|
||||||
}
|
}
|
||||||
if config.Target.SoftFloat {
|
if strings.HasPrefix(target, "riscv64-") {
|
||||||
// Use softfloat instead of floating point instructions. This is
|
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
||||||
// 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 {
|
|
||||||
args = append(args, config.LibcCFlags()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
|
|
||||||
// Create job to put all the object files in a single archive. This archive
|
// Create job to put all the object files in a single archive. This archive
|
||||||
// file is the (static) library file.
|
// file is the (static) library file.
|
||||||
var objs []string
|
var objs []string
|
||||||
|
arpath := filepath.Join(dir, l.name+".a")
|
||||||
job = &compileJob{
|
job = &compileJob{
|
||||||
description: "ar " + l.name + "/lib.a",
|
description: "ar " + l.name + ".a",
|
||||||
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
|
result: arpath,
|
||||||
run: func(*compileJob) error {
|
run: func(*compileJob) error {
|
||||||
defer once.Do(unlock)
|
|
||||||
|
|
||||||
// Create an archive of all object files.
|
// Create an archive of all object files.
|
||||||
f, err := os.CreateTemp(outdir, "libc.a.tmp*")
|
err := makeArchive(arpath, objs)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = makeArchive(f, objs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = f.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = os.Chmod(f.Name(), 0o644) // TempFile uses 0o600 by default
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Store this archive in the cache.
|
// Store this archive in the cache.
|
||||||
return os.Rename(f.Name(), archiveFilePath)
|
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
|
||||||
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceDir := l.sourceDir()
|
|
||||||
|
|
||||||
// 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 _, srcpath := range l.sourcePaths(target) {
|
||||||
if err != nil {
|
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
|
||||||
return nil, nil, err
|
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
||||||
}
|
|
||||||
for _, path := range paths {
|
|
||||||
// Strip leading "../" parts off the path.
|
|
||||||
path := path
|
|
||||||
cleanpath := path
|
|
||||||
for strings.HasPrefix(cleanpath, "../") {
|
|
||||||
cleanpath = cleanpath[3:]
|
|
||||||
}
|
|
||||||
srcpath := filepath.Join(sourceDir, path)
|
|
||||||
objpath := filepath.Join(dir, cleanpath+".o")
|
|
||||||
os.MkdirAll(filepath.Dir(objpath), 0o777)
|
|
||||||
objs = append(objs, objpath)
|
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.
|
return job, nil
|
||||||
// Add this as a (fake) dependency to the ar file so it gets compiled.
|
|
||||||
// (It could be done in parallel with creating the ar file, but it probably
|
|
||||||
// won't make much of a difference in speed).
|
|
||||||
if l.crt1Source != "" {
|
|
||||||
srcpath := filepath.Join(sourceDir, l.crt1Source)
|
|
||||||
crt1Job := &compileJob{
|
|
||||||
description: "compile " + srcpath,
|
|
||||||
run: func(*compileJob) error {
|
|
||||||
var compileArgs []string
|
|
||||||
compileArgs = append(compileArgs, args...)
|
|
||||||
tmpfile, err := os.CreateTemp(outdir, "crt1.o.tmp*")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
tmpfile.Close()
|
|
||||||
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
|
|
||||||
if config.Options.PrintCommands != nil {
|
|
||||||
config.Options.PrintCommands("clang", compileArgs...)
|
|
||||||
}
|
|
||||||
err = runCCompiler(compileArgs...)
|
|
||||||
if err != nil {
|
|
||||||
return &commandError{"failed to build", srcpath, err}
|
|
||||||
}
|
|
||||||
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
job.dependencies = append(job.dependencies, crt1Job)
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
return job, func() {
|
|
||||||
once.Do(unlock)
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// riscvMarch returns the -march value for RISC-V library compilation.
|
|
||||||
// It extracts the value from the target's cflags if present, otherwise
|
|
||||||
// falls back to the provided default. This ensures libraries are compiled
|
|
||||||
// with the correct ISA extensions for each target (e.g. rv32imc for
|
|
||||||
// ESP32-C3 which lacks the atomic extension).
|
|
||||||
func riscvMarch(config *compileopts.Config, defaultMarch string) string {
|
|
||||||
for _, flag := range config.Target.CFlags {
|
|
||||||
if strings.HasPrefix(flag, "-march=") {
|
|
||||||
return flag[len("-march="):]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultMarch
|
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-21
@@ -1,32 +1,19 @@
|
|||||||
//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, false, llvm::outs(), llvm::errs());
|
||||||
return !r.retCode;
|
}
|
||||||
|
|
||||||
|
bool tinygo_link_wasm(int argc, char **argv) {
|
||||||
|
std::vector<const char*> args(argv, argv + argc);
|
||||||
|
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // external "C"
|
} // external "C"
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var libMinGW = Library{
|
|
||||||
name: "mingw-w64",
|
|
||||||
makeHeaders: func(target, includeDir string) error {
|
|
||||||
// copy _mingw.h
|
|
||||||
srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
|
|
||||||
outf, err := os.Create(includeDir + "/_mingw.h")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer outf.Close()
|
|
||||||
inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(outf, inf)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64") },
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
mingwDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/mingw-w64")
|
|
||||||
flags := []string{
|
|
||||||
"-nostdlibinc",
|
|
||||||
"-isystem", mingwDir + "/mingw-w64-crt/include",
|
|
||||||
"-isystem", mingwDir + "/mingw-w64-headers/crt",
|
|
||||||
"-isystem", mingwDir + "/mingw-w64-headers/include",
|
|
||||||
"-I", mingwDir + "/mingw-w64-headers/defaults/include",
|
|
||||||
"-I" + headerPath,
|
|
||||||
}
|
|
||||||
if strings.Split(target, "-")[0] == "i386" {
|
|
||||||
flags = append(flags,
|
|
||||||
"-D__MSVCRT_VERSION__=0x700", // Microsoft Visual C++ .NET 2002
|
|
||||||
"-D_WIN32_WINNT=0x0501", // target Windows XP
|
|
||||||
"-D_CRTBLD",
|
|
||||||
"-Wno-pragma-pack",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return flags
|
|
||||||
},
|
|
||||||
librarySources: func(target string, _ bool) ([]string, error) {
|
|
||||||
// These files are needed so that printf and the like are supported.
|
|
||||||
var sources []string
|
|
||||||
if strings.Split(target, "-")[0] == "i386" {
|
|
||||||
// Old 32-bit x86 systems use msvcrt.dll.
|
|
||||||
sources = []string{
|
|
||||||
"mingw-w64-crt/crt/pseudo-reloc.c",
|
|
||||||
"mingw-w64-crt/gdtoa/dmisc.c",
|
|
||||||
"mingw-w64-crt/gdtoa/gdtoa.c",
|
|
||||||
"mingw-w64-crt/gdtoa/gmisc.c",
|
|
||||||
"mingw-w64-crt/gdtoa/misc.c",
|
|
||||||
"mingw-w64-crt/math/x86/exp2.S",
|
|
||||||
"mingw-w64-crt/math/x86/trunc.S",
|
|
||||||
"mingw-w64-crt/misc/___mb_cur_max_func.c",
|
|
||||||
"mingw-w64-crt/misc/lc_locale_func.c",
|
|
||||||
"mingw-w64-crt/misc/mbrtowc.c",
|
|
||||||
"mingw-w64-crt/misc/strnlen.c",
|
|
||||||
"mingw-w64-crt/misc/wcrtomb.c",
|
|
||||||
"mingw-w64-crt/misc/wcsnlen.c",
|
|
||||||
"mingw-w64-crt/stdio/acrt_iob_func.c",
|
|
||||||
"mingw-w64-crt/stdio/mingw_lock.c",
|
|
||||||
"mingw-w64-crt/stdio/mingw_pformat.c",
|
|
||||||
"mingw-w64-crt/stdio/mingw_vfprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/mingw_vsnprintf.c",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Anything somewhat modern (amd64, arm64) uses UCRT.
|
|
||||||
sources = []string{
|
|
||||||
"mingw-w64-crt/stdio/ucrt_fprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_fwprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_printf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_snprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_sprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_vfprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_vprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_vsnprintf.c",
|
|
||||||
"mingw-w64-crt/stdio/ucrt_vsprintf.c",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources, nil
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
|
|
||||||
// libraries. This is done by converting input .def files to .lib files which
|
|
||||||
// can then be linked as usual.
|
|
||||||
//
|
|
||||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
|
||||||
// compile these files.
|
|
||||||
func makeMinGWExtraLibs(tmpdir, goarch string) []*compileJob {
|
|
||||||
var jobs []*compileJob
|
|
||||||
root := goenv.Get("TINYGOROOT")
|
|
||||||
var libs []string
|
|
||||||
if goarch == "386" {
|
|
||||||
libs = []string{
|
|
||||||
// x86 uses msvcrt.dll instead of UCRT for compatibility with old
|
|
||||||
// Windows versions.
|
|
||||||
"advapi32.def.in",
|
|
||||||
"kernel32.def.in",
|
|
||||||
"msvcrt.def.in",
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Use the modernized UCRT on new systems.
|
|
||||||
// Normally all the api-ms-win-crt-*.def files are all compiled to a
|
|
||||||
// single .lib file. But to simplify things, we're going to leave them
|
|
||||||
// as separate files.
|
|
||||||
libs = []string{
|
|
||||||
"advapi32.def.in",
|
|
||||||
"kernel32.def.in",
|
|
||||||
"api-ms-win-crt-conio-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-convert-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-environment-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-filesystem-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-heap-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-locale-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-math-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-multibyte-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-private-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-process-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-runtime-l1-1-0.def.in",
|
|
||||||
"api-ms-win-crt-stdio-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-string-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-time-l1-1-0.def",
|
|
||||||
"api-ms-win-crt-utility-l1-1-0.def",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, name := range libs {
|
|
||||||
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
|
|
||||||
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
|
|
||||||
job := &compileJob{
|
|
||||||
description: "create lib file " + inpath,
|
|
||||||
result: outpath,
|
|
||||||
run: func(job *compileJob) error {
|
|
||||||
defpath := inpath
|
|
||||||
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") {
|
|
||||||
// .in files need to be preprocessed by a preprocessor (-E)
|
|
||||||
// first.
|
|
||||||
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/")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return link("ld.lld", "-m", emulation, "-o", outpath, defpath)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
jobs = append(jobs, job)
|
|
||||||
}
|
|
||||||
return jobs
|
|
||||||
}
|
|
||||||
-209
@@ -1,209 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create the alltypes.h file from the appropriate alltypes.h.in files.
|
|
||||||
func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
|
|
||||||
// Create the file alltypes.h.
|
|
||||||
f, err := os.Create(filepath.Join(outputBitsDir, "alltypes.h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
infiles := []string{
|
|
||||||
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
|
|
||||||
filepath.Join(muslDir, "include", "alltypes.h.in"),
|
|
||||||
}
|
|
||||||
for _, infile := range infiles {
|
|
||||||
data, err := os.ReadFile(infile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.HasPrefix(line, "TYPEDEF ") {
|
|
||||||
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
|
|
||||||
value := matches[1]
|
|
||||||
name := matches[2]
|
|
||||||
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(line, "STRUCT ") {
|
|
||||||
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
|
|
||||||
name := matches[1]
|
|
||||||
value := matches[2]
|
|
||||||
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
|
|
||||||
}
|
|
||||||
_, err := f.WriteString(line + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
var libMusl = Library{
|
|
||||||
name: "musl",
|
|
||||||
makeHeaders: func(target, includeDir string) error {
|
|
||||||
bits := filepath.Join(includeDir, "bits")
|
|
||||||
err := os.Mkdir(bits, 0777)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
|
|
||||||
|
|
||||||
err = buildMuslAllTypes(arch, muslDir, bits)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the file syscall.h.
|
|
||||||
f, err := os.Create(filepath.Join(bits, "syscall.h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := os.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Close()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
cflags: func(target, headerPath string) []string {
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
|
|
||||||
cflags := []string{
|
|
||||||
"-std=c99", // same as in musl
|
|
||||||
"-D_XOPEN_SOURCE=700", // same as in musl
|
|
||||||
// Musl triggers some warnings and we don't want to show any
|
|
||||||
// warnings while compiling (only errors or silence), so disable
|
|
||||||
// specific warnings that are triggered in musl.
|
|
||||||
"-Werror",
|
|
||||||
"-Wno-logical-op-parentheses",
|
|
||||||
"-Wno-bitwise-op-parentheses",
|
|
||||||
"-Wno-shift-op-parentheses",
|
|
||||||
"-Wno-ignored-attributes",
|
|
||||||
"-Wno-string-plus-int",
|
|
||||||
"-Wno-ignored-pragmas",
|
|
||||||
"-Wno-tautological-constant-out-of-range-compare",
|
|
||||||
"-Wno-deprecated-non-prototype",
|
|
||||||
"-Wno-format",
|
|
||||||
"-Wno-parentheses",
|
|
||||||
"-Qunused-arguments",
|
|
||||||
// Select include dirs. Don't include standard library includes
|
|
||||||
// (that would introduce host dependencies and other complications),
|
|
||||||
// but do include all the include directories expected by musl.
|
|
||||||
"-nostdlibinc",
|
|
||||||
"-I" + muslDir + "/arch/" + arch,
|
|
||||||
"-I" + muslDir + "/arch/generic",
|
|
||||||
"-I" + muslDir + "/src/include",
|
|
||||||
"-I" + muslDir + "/src/internal",
|
|
||||||
"-I" + headerPath,
|
|
||||||
"-I" + muslDir + "/include",
|
|
||||||
"-fno-stack-protector",
|
|
||||||
}
|
|
||||||
return cflags
|
|
||||||
},
|
|
||||||
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl/src") },
|
|
||||||
librarySources: func(target string, _ bool) ([]string, error) {
|
|
||||||
arch := compileopts.MuslArchitecture(target)
|
|
||||||
globs := []string{
|
|
||||||
"conf/*.c",
|
|
||||||
"ctype/*.c",
|
|
||||||
"env/*.c",
|
|
||||||
"errno/*.c",
|
|
||||||
"exit/*.c",
|
|
||||||
"fcntl/*.c",
|
|
||||||
"internal/defsysinfo.c",
|
|
||||||
"internal/intscan.c",
|
|
||||||
"internal/libc.c",
|
|
||||||
"internal/shgetc.c",
|
|
||||||
"internal/syscall_ret.c",
|
|
||||||
"internal/vdso.c",
|
|
||||||
"legacy/*.c",
|
|
||||||
"locale/*.c",
|
|
||||||
"linux/*.c",
|
|
||||||
"locale/*.c",
|
|
||||||
"malloc/*.c",
|
|
||||||
"malloc/mallocng/*.c",
|
|
||||||
"mman/*.c",
|
|
||||||
"math/*.c",
|
|
||||||
"misc/*.c",
|
|
||||||
"multibyte/*.c",
|
|
||||||
"sched/*.c",
|
|
||||||
"signal/" + arch + "/*.s",
|
|
||||||
"signal/*.c",
|
|
||||||
"stdio/*.c",
|
|
||||||
"stdlib/*.c",
|
|
||||||
"string/*.c",
|
|
||||||
"thread/" + arch + "/*.s",
|
|
||||||
"thread/*.c",
|
|
||||||
"time/*.c",
|
|
||||||
"unistd/*.c",
|
|
||||||
"process/*.c",
|
|
||||||
}
|
|
||||||
|
|
||||||
if arch == "arm" {
|
|
||||||
// These files need to be added to the start for some reason.
|
|
||||||
globs = append([]string{"thread/arm/*.c"}, globs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if arch != "aarch64" && arch != "mips" {
|
|
||||||
//aarch64 and mips have no architecture specific code, either they
|
|
||||||
// are not supported or don't need any?
|
|
||||||
globs = append([]string{"process/" + arch + "/*.s"}, globs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var sources []string
|
|
||||||
seenSources := map[string]struct{}{}
|
|
||||||
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
|
|
||||||
for _, pattern := range globs {
|
|
||||||
matches, err := filepath.Glob(basepath + pattern)
|
|
||||||
if err != nil {
|
|
||||||
// From the documentation:
|
|
||||||
// > Glob ignores file system errors such as I/O errors reading
|
|
||||||
// > directories. The only possible returned error is
|
|
||||||
// > ErrBadPattern, when pattern is malformed.
|
|
||||||
// So the only possible error is when the (statically defined)
|
|
||||||
// pattern is wrong. In other words, a programming bug.
|
|
||||||
return nil, fmt.Errorf("musl: could not glob source dirs: %w", err)
|
|
||||||
}
|
|
||||||
if len(matches) == 0 {
|
|
||||||
return nil, fmt.Errorf("musl: did not find any files for pattern %#v", pattern)
|
|
||||||
}
|
|
||||||
for _, match := range matches {
|
|
||||||
relpath, err := filepath.Rel(basepath, match)
|
|
||||||
if err != nil {
|
|
||||||
// Not sure if this is even possible.
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Make sure architecture specific files override generic files.
|
|
||||||
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
|
|
||||||
if _, ok := seenSources[id]; ok {
|
|
||||||
// Already seen this file, skipping this (generic) file.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seenSources[id] = struct{}{}
|
|
||||||
sources = append(sources, relpath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sources, nil
|
|
||||||
},
|
|
||||||
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
|
|
||||||
}
|
|
||||||
@@ -1,117 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"archive/zip"
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sigurn/crc16"
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Structure of the manifest.json file.
|
|
||||||
type jsonManifest struct {
|
|
||||||
Manifest struct {
|
|
||||||
Application struct {
|
|
||||||
BinaryFile string `json:"bin_file"`
|
|
||||||
DataFile string `json:"dat_file"`
|
|
||||||
InitPacketData nrfInitPacket `json:"init_packet_data"`
|
|
||||||
} `json:"application"`
|
|
||||||
DFUVersion float64 `json:"dfu_version"` // yes, this is a JSON number, not a string
|
|
||||||
} `json:"manifest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Structure of the init packet.
|
|
||||||
// Source:
|
|
||||||
// https://github.com/adafruit/Adafruit_nRF52_Bootloader/blob/master/lib/sdk11/components/libraries/bootloader_dfu/dfu_init.h#L47-L57
|
|
||||||
type nrfInitPacket struct {
|
|
||||||
ApplicationVersion uint32 `json:"application_version"`
|
|
||||||
DeviceRevision uint16 `json:"device_revision"`
|
|
||||||
DeviceType uint16 `json:"device_type"`
|
|
||||||
FirmwareCRC16 uint16 `json:"firmware_crc16"`
|
|
||||||
SoftDeviceRequired []uint16 `json:"softdevice_req"` // this is actually a variable length array
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the init packet (the contents of application.dat).
|
|
||||||
func (p nrfInitPacket) createInitPacket() []byte {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
binary.Write(buf, binary.LittleEndian, p.DeviceType) // uint16_t device_type;
|
|
||||||
binary.Write(buf, binary.LittleEndian, p.DeviceRevision) // uint16_t device_rev;
|
|
||||||
binary.Write(buf, binary.LittleEndian, p.ApplicationVersion) // uint32_t app_version;
|
|
||||||
binary.Write(buf, binary.LittleEndian, uint16(len(p.SoftDeviceRequired))) // uint16_t softdevice_len;
|
|
||||||
binary.Write(buf, binary.LittleEndian, p.SoftDeviceRequired) // uint16_t softdevice[1];
|
|
||||||
binary.Write(buf, binary.LittleEndian, p.FirmwareCRC16)
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make a Nordic DFU firmware image from an ELF file.
|
|
||||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
|
||||||
// Read ELF file as input and convert it to a binary image file.
|
|
||||||
_, data, err := extractROM(infile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the zip file in memory.
|
|
||||||
// It won't be very large anyway.
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
w := zip.NewWriter(buf)
|
|
||||||
|
|
||||||
// Write the application binary to the zip file.
|
|
||||||
binw, err := w.Create("application.bin")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = binw.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the init packet.
|
|
||||||
initPacket := nrfInitPacket{
|
|
||||||
ApplicationVersion: 0xffff_ffff, // appears to be unused by the Adafruit bootloader
|
|
||||||
DeviceRevision: 0xffff, // DFU_DEVICE_REVISION_EMPTY
|
|
||||||
DeviceType: 0x0052, // ADAFRUIT_DEVICE_TYPE
|
|
||||||
FirmwareCRC16: crc16.Checksum(data, crc16.MakeTable(crc16.CRC16_CCITT_FALSE)),
|
|
||||||
SoftDeviceRequired: []uint16{0xfffe}, // DFU_SOFTDEVICE_ANY
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the init packet to the zip file.
|
|
||||||
datw, err := w.Create("application.dat")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = datw.Write(initPacket.createInitPacket())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the JSON manifest.
|
|
||||||
manifest := &jsonManifest{}
|
|
||||||
manifest.Manifest.Application.BinaryFile = "application.bin"
|
|
||||||
manifest.Manifest.Application.DataFile = "application.dat"
|
|
||||||
manifest.Manifest.Application.InitPacketData = initPacket
|
|
||||||
manifest.Manifest.DFUVersion = 0.5
|
|
||||||
|
|
||||||
// Write the JSON manifest to the file.
|
|
||||||
jsonw, err := w.Create("manifest.json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
enc := json.NewEncoder(jsonw)
|
|
||||||
enc.SetIndent("", " ")
|
|
||||||
err = enc.Encode(manifest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish the zip file.
|
|
||||||
err = w.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return os.WriteFile(outfile, buf.Bytes(), 0o666)
|
|
||||||
}
|
|
||||||
+2
-2
@@ -2,7 +2,7 @@ package builder
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
|||||||
// Pad the difference
|
// Pad the difference
|
||||||
rom = append(rom, make([]byte, diff)...)
|
rom = append(rom, make([]byte, diff)...)
|
||||||
}
|
}
|
||||||
data, err := io.ReadAll(prog.Open())
|
data, err := ioutil.ReadAll(prog.Open())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
return 0, nil, objcopyError{"failed to extract segment from ELF file: " + path, err}
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-428
@@ -1,444 +1,127 @@
|
|||||||
package builder
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"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 {
|
cflags: func() []string {
|
||||||
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
|
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
|
||||||
if err != nil {
|
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
|
||||||
return err
|
|
||||||
}
|
|
||||||
return f.Close()
|
|
||||||
},
|
},
|
||||||
cflags: func(target, headerPath string) []string {
|
sourceDir: "lib/picolibc/newlib/libc",
|
||||||
newlibDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib")
|
sources: func(target string) []string {
|
||||||
return []string{
|
return picolibcSources
|
||||||
"-Werror",
|
|
||||||
"-Wall",
|
|
||||||
"-std=gnu11",
|
|
||||||
"-D_COMPILING_NEWLIB",
|
|
||||||
"-D_HAVE_ALIAS_ATTRIBUTE",
|
|
||||||
"-DTINY_STDIO",
|
|
||||||
"-DPOSIX_IO",
|
|
||||||
"-DFORMAT_DEFAULT_INTEGER", // use __i_vfprintf and __i_vfscanf by default
|
|
||||||
"-D_IEEE_LIBM",
|
|
||||||
"-D__OBSOLETE_MATH_FLOAT=1", // use old math code that doesn't expect a FPU
|
|
||||||
"-D__OBSOLETE_MATH_DOUBLE=0",
|
|
||||||
"-D_WANT_IO_C99_FORMATS",
|
|
||||||
"-D__PICOLIBC_ERRNO_FUNCTION=__errno_location",
|
|
||||||
"-nostdlibinc",
|
|
||||||
"-isystem", newlibDir + "/libc/include",
|
|
||||||
"-I" + newlibDir + "/libc/tinystdio",
|
|
||||||
"-I" + newlibDir + "/libm/common",
|
|
||||||
"-I" + headerPath,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sourceDir: func() string { return filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib") },
|
|
||||||
librarySources: func(target string, _ bool) ([]string, error) {
|
|
||||||
sources := append([]string(nil), picolibcSources...)
|
|
||||||
if !strings.HasPrefix(target, "avr") {
|
|
||||||
// Small chips without long jumps can't compile many files (printf,
|
|
||||||
// pow, etc). Therefore exclude those source files for those chips.
|
|
||||||
// Unfortunately it's difficult to exclude only some chips, so this
|
|
||||||
// excludes those files on all AVR chips for now.
|
|
||||||
// More information:
|
|
||||||
// https://github.com/llvm/llvm-project/issues/67042
|
|
||||||
sources = append(sources, picolibcSourcesLarge...)
|
|
||||||
}
|
|
||||||
return sources, nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var picolibcSources = []string{
|
var picolibcSources = []string{
|
||||||
"../../picolibc-stdio.c",
|
"string/bcmp.c",
|
||||||
|
"string/bcopy.c",
|
||||||
"libc/string/bcmp.c",
|
"string/bzero.c",
|
||||||
"libc/string/bcopy.c",
|
"string/explicit_bzero.c",
|
||||||
"libc/string/bzero.c",
|
"string/ffsl.c",
|
||||||
"libc/string/explicit_bzero.c",
|
"string/ffsll.c",
|
||||||
"libc/string/ffsl.c",
|
"string/fls.c",
|
||||||
"libc/string/ffsll.c",
|
"string/flsl.c",
|
||||||
"libc/string/fls.c",
|
"string/flsll.c",
|
||||||
"libc/string/flsl.c",
|
"string/gnu_basename.c",
|
||||||
"libc/string/flsll.c",
|
"string/index.c",
|
||||||
"libc/string/gnu_basename.c",
|
"string/memccpy.c",
|
||||||
"libc/string/index.c",
|
"string/memchr.c",
|
||||||
"libc/string/memccpy.c",
|
"string/memcmp.c",
|
||||||
"libc/string/memchr.c",
|
"string/memcpy.c",
|
||||||
"libc/string/memcmp.c",
|
"string/memmem.c",
|
||||||
"libc/string/memcpy.c",
|
"string/memmove.c",
|
||||||
"libc/string/memmem.c",
|
"string/mempcpy.c",
|
||||||
"libc/string/memmove.c",
|
"string/memrchr.c",
|
||||||
"libc/string/mempcpy.c",
|
"string/memset.c",
|
||||||
"libc/string/memrchr.c",
|
"string/rawmemchr.c",
|
||||||
"libc/string/memset.c",
|
"string/rindex.c",
|
||||||
"libc/string/rawmemchr.c",
|
"string/stpcpy.c",
|
||||||
"libc/string/rindex.c",
|
"string/stpncpy.c",
|
||||||
"libc/string/stpcpy.c",
|
"string/strcasecmp.c",
|
||||||
"libc/string/stpncpy.c",
|
"string/strcasecmp_l.c",
|
||||||
"libc/string/strcasecmp.c",
|
"string/strcasestr.c",
|
||||||
"libc/string/strcasecmp_l.c",
|
"string/strcat.c",
|
||||||
"libc/string/strcasestr.c",
|
"string/strchr.c",
|
||||||
"libc/string/strcat.c",
|
"string/strchrnul.c",
|
||||||
"libc/string/strchr.c",
|
"string/strcmp.c",
|
||||||
"libc/string/strchrnul.c",
|
"string/strcoll.c",
|
||||||
"libc/string/strcmp.c",
|
"string/strcoll_l.c",
|
||||||
"libc/string/strcoll.c",
|
"string/strcpy.c",
|
||||||
"libc/string/strcoll_l.c",
|
"string/strcspn.c",
|
||||||
"libc/string/strcpy.c",
|
"string/strdup.c",
|
||||||
"libc/string/strcspn.c",
|
"string/strerror.c",
|
||||||
"libc/string/strdup.c",
|
"string/strerror_r.c",
|
||||||
"libc/string/strerror.c",
|
"string/strlcat.c",
|
||||||
"libc/string/strerror_r.c",
|
"string/strlcpy.c",
|
||||||
"libc/string/strlcat.c",
|
"string/strlen.c",
|
||||||
"libc/string/strlcpy.c",
|
"string/strlwr.c",
|
||||||
"libc/string/strlen.c",
|
"string/strncasecmp.c",
|
||||||
"libc/string/strlwr.c",
|
"string/strncasecmp_l.c",
|
||||||
"libc/string/strncasecmp.c",
|
"string/strncat.c",
|
||||||
"libc/string/strncasecmp_l.c",
|
"string/strncmp.c",
|
||||||
"libc/string/strncat.c",
|
"string/strncpy.c",
|
||||||
"libc/string/strncmp.c",
|
"string/strndup.c",
|
||||||
"libc/string/strncpy.c",
|
"string/strnlen.c",
|
||||||
"libc/string/strndup.c",
|
"string/strnstr.c",
|
||||||
"libc/string/strnlen.c",
|
"string/strpbrk.c",
|
||||||
"libc/string/strnstr.c",
|
"string/strrchr.c",
|
||||||
"libc/string/strpbrk.c",
|
"string/strsep.c",
|
||||||
"libc/string/strrchr.c",
|
"string/strsignal.c",
|
||||||
"libc/string/strsep.c",
|
"string/strspn.c",
|
||||||
"libc/string/strsignal.c",
|
"string/strstr.c",
|
||||||
"libc/string/strspn.c",
|
"string/strtok.c",
|
||||||
"libc/string/strstr.c",
|
"string/strtok_r.c",
|
||||||
"libc/string/strtok.c",
|
"string/strupr.c",
|
||||||
"libc/string/strtok_r.c",
|
"string/strverscmp.c",
|
||||||
"libc/string/strupr.c",
|
"string/strxfrm.c",
|
||||||
"libc/string/strverscmp.c",
|
"string/strxfrm_l.c",
|
||||||
"libc/string/strxfrm.c",
|
"string/swab.c",
|
||||||
"libc/string/strxfrm_l.c",
|
"string/timingsafe_bcmp.c",
|
||||||
"libc/string/swab.c",
|
"string/timingsafe_memcmp.c",
|
||||||
"libc/string/timingsafe_bcmp.c",
|
"string/u_strerr.c",
|
||||||
"libc/string/timingsafe_memcmp.c",
|
"string/wcpcpy.c",
|
||||||
"libc/string/u_strerr.c",
|
"string/wcpncpy.c",
|
||||||
"libc/string/wcpcpy.c",
|
"string/wcscasecmp.c",
|
||||||
"libc/string/wcpncpy.c",
|
"string/wcscasecmp_l.c",
|
||||||
"libc/string/wcscasecmp.c",
|
"string/wcscat.c",
|
||||||
"libc/string/wcscasecmp_l.c",
|
"string/wcschr.c",
|
||||||
"libc/string/wcscat.c",
|
"string/wcscmp.c",
|
||||||
"libc/string/wcschr.c",
|
"string/wcscoll.c",
|
||||||
"libc/string/wcscmp.c",
|
"string/wcscoll_l.c",
|
||||||
"libc/string/wcscoll.c",
|
"string/wcscpy.c",
|
||||||
"libc/string/wcscoll_l.c",
|
"string/wcscspn.c",
|
||||||
"libc/string/wcscpy.c",
|
"string/wcsdup.c",
|
||||||
"libc/string/wcscspn.c",
|
"string/wcslcat.c",
|
||||||
"libc/string/wcsdup.c",
|
"string/wcslcpy.c",
|
||||||
"libc/string/wcslcat.c",
|
"string/wcslen.c",
|
||||||
"libc/string/wcslcpy.c",
|
"string/wcsncasecmp.c",
|
||||||
"libc/string/wcslen.c",
|
"string/wcsncasecmp_l.c",
|
||||||
"libc/string/wcsncasecmp.c",
|
"string/wcsncat.c",
|
||||||
"libc/string/wcsncasecmp_l.c",
|
"string/wcsncmp.c",
|
||||||
"libc/string/wcsncat.c",
|
"string/wcsncpy.c",
|
||||||
"libc/string/wcsncmp.c",
|
"string/wcsnlen.c",
|
||||||
"libc/string/wcsncpy.c",
|
"string/wcspbrk.c",
|
||||||
"libc/string/wcsnlen.c",
|
"string/wcsrchr.c",
|
||||||
"libc/string/wcspbrk.c",
|
"string/wcsspn.c",
|
||||||
"libc/string/wcsrchr.c",
|
"string/wcsstr.c",
|
||||||
"libc/string/wcsspn.c",
|
"string/wcstok.c",
|
||||||
"libc/string/wcsstr.c",
|
"string/wcswidth.c",
|
||||||
"libc/string/wcstok.c",
|
"string/wcsxfrm.c",
|
||||||
"libc/string/wcswidth.c",
|
"string/wcsxfrm_l.c",
|
||||||
"libc/string/wcsxfrm.c",
|
"string/wcwidth.c",
|
||||||
"libc/string/wcsxfrm_l.c",
|
"string/wmemchr.c",
|
||||||
"libc/string/wcwidth.c",
|
"string/wmemcmp.c",
|
||||||
"libc/string/wmemchr.c",
|
"string/wmemcpy.c",
|
||||||
"libc/string/wmemcmp.c",
|
"string/wmemmove.c",
|
||||||
"libc/string/wmemcpy.c",
|
"string/wmempcpy.c",
|
||||||
"libc/string/wmemmove.c",
|
"string/wmemset.c",
|
||||||
"libc/string/wmempcpy.c",
|
"string/xpg_strerror_r.c",
|
||||||
"libc/string/wmemset.c",
|
|
||||||
"libc/string/xpg_strerror_r.c",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parts of picolibc that are too large for small AVRs.
|
|
||||||
var picolibcSourcesLarge = []string{
|
|
||||||
// srcs_tinystdio
|
|
||||||
"libc/tinystdio/asprintf.c",
|
|
||||||
"libc/tinystdio/bufio.c",
|
|
||||||
"libc/tinystdio/clearerr.c",
|
|
||||||
"libc/tinystdio/ecvt_r.c",
|
|
||||||
"libc/tinystdio/ecvt.c",
|
|
||||||
"libc/tinystdio/ecvtf_r.c",
|
|
||||||
"libc/tinystdio/ecvtf.c",
|
|
||||||
"libc/tinystdio/fcvt.c",
|
|
||||||
"libc/tinystdio/fcvt_r.c",
|
|
||||||
"libc/tinystdio/fcvtf.c",
|
|
||||||
"libc/tinystdio/fcvtf_r.c",
|
|
||||||
"libc/tinystdio/gcvt.c",
|
|
||||||
"libc/tinystdio/gcvtf.c",
|
|
||||||
"libc/tinystdio/fclose.c",
|
|
||||||
"libc/tinystdio/fdevopen.c",
|
|
||||||
"libc/tinystdio/feof.c",
|
|
||||||
"libc/tinystdio/ferror.c",
|
|
||||||
"libc/tinystdio/fflush.c",
|
|
||||||
"libc/tinystdio/fgetc.c",
|
|
||||||
"libc/tinystdio/fgets.c",
|
|
||||||
"libc/tinystdio/fileno.c",
|
|
||||||
"libc/tinystdio/filestrget.c",
|
|
||||||
"libc/tinystdio/filestrput.c",
|
|
||||||
"libc/tinystdio/filestrputalloc.c",
|
|
||||||
"libc/tinystdio/fmemopen.c",
|
|
||||||
"libc/tinystdio/fprintf.c",
|
|
||||||
"libc/tinystdio/fputc.c",
|
|
||||||
"libc/tinystdio/fputs.c",
|
|
||||||
"libc/tinystdio/fread.c",
|
|
||||||
//"libc/tinystdio/freopen.c", // crashes with AVR, see: https://github.com/picolibc/picolibc/pull/369
|
|
||||||
"libc/tinystdio/fscanf.c",
|
|
||||||
"libc/tinystdio/fseek.c",
|
|
||||||
"libc/tinystdio/fseeko.c",
|
|
||||||
"libc/tinystdio/ftell.c",
|
|
||||||
"libc/tinystdio/ftello.c",
|
|
||||||
"libc/tinystdio/fwrite.c",
|
|
||||||
"libc/tinystdio/getchar.c",
|
|
||||||
"libc/tinystdio/gets.c",
|
|
||||||
"libc/tinystdio/matchcaseprefix.c",
|
|
||||||
"libc/tinystdio/mktemp.c",
|
|
||||||
"libc/tinystdio/perror.c",
|
|
||||||
"libc/tinystdio/printf.c",
|
|
||||||
"libc/tinystdio/putchar.c",
|
|
||||||
"libc/tinystdio/puts.c",
|
|
||||||
"libc/tinystdio/rewind.c",
|
|
||||||
"libc/tinystdio/scanf.c",
|
|
||||||
"libc/tinystdio/setbuf.c",
|
|
||||||
"libc/tinystdio/setbuffer.c",
|
|
||||||
"libc/tinystdio/setlinebuf.c",
|
|
||||||
"libc/tinystdio/setvbuf.c",
|
|
||||||
"libc/tinystdio/snprintf.c",
|
|
||||||
"libc/tinystdio/sprintf.c",
|
|
||||||
"libc/tinystdio/snprintfd.c",
|
|
||||||
"libc/tinystdio/snprintff.c",
|
|
||||||
"libc/tinystdio/sprintff.c",
|
|
||||||
"libc/tinystdio/sprintfd.c",
|
|
||||||
"libc/tinystdio/sscanf.c",
|
|
||||||
"libc/tinystdio/strfromf.c",
|
|
||||||
"libc/tinystdio/strfromd.c",
|
|
||||||
"libc/tinystdio/strtof.c",
|
|
||||||
"libc/tinystdio/strtof_l.c",
|
|
||||||
"libc/tinystdio/strtod.c",
|
|
||||||
"libc/tinystdio/strtod_l.c",
|
|
||||||
"libc/tinystdio/ungetc.c",
|
|
||||||
"libc/tinystdio/vasprintf.c",
|
|
||||||
"libc/tinystdio/vfiprintf.c",
|
|
||||||
"libc/tinystdio/vfprintf.c",
|
|
||||||
"libc/tinystdio/vfprintff.c",
|
|
||||||
"libc/tinystdio/vfscanf.c",
|
|
||||||
"libc/tinystdio/vfiscanf.c",
|
|
||||||
"libc/tinystdio/vfscanff.c",
|
|
||||||
"libc/tinystdio/vprintf.c",
|
|
||||||
"libc/tinystdio/vscanf.c",
|
|
||||||
"libc/tinystdio/vsscanf.c",
|
|
||||||
"libc/tinystdio/vsnprintf.c",
|
|
||||||
"libc/tinystdio/vsprintf.c",
|
|
||||||
|
|
||||||
"libm/common/sf_finite.c",
|
|
||||||
"libm/common/sf_copysign.c",
|
|
||||||
"libm/common/sf_modf.c",
|
|
||||||
"libm/common/sf_scalbn.c",
|
|
||||||
"libm/common/sf_cbrt.c",
|
|
||||||
"libm/common/sf_exp10.c",
|
|
||||||
"libm/common/sf_expm1.c",
|
|
||||||
"libm/common/sf_ilogb.c",
|
|
||||||
"libm/common/sf_infinity.c",
|
|
||||||
"libm/common/sf_isinf.c",
|
|
||||||
"libm/common/sf_isinff.c",
|
|
||||||
"libm/common/sf_isnan.c",
|
|
||||||
"libm/common/sf_isnanf.c",
|
|
||||||
"libm/common/sf_issignaling.c",
|
|
||||||
"libm/common/sf_log1p.c",
|
|
||||||
"libm/common/sf_nan.c",
|
|
||||||
"libm/common/sf_nextafter.c",
|
|
||||||
"libm/common/sf_pow10.c",
|
|
||||||
"libm/common/sf_rint.c",
|
|
||||||
"libm/common/sf_logb.c",
|
|
||||||
"libm/common/sf_fdim.c",
|
|
||||||
"libm/common/sf_fma.c",
|
|
||||||
"libm/common/sf_fmax.c",
|
|
||||||
"libm/common/sf_fmin.c",
|
|
||||||
"libm/common/sf_fpclassify.c",
|
|
||||||
"libm/common/sf_lrint.c",
|
|
||||||
"libm/common/sf_llrint.c",
|
|
||||||
"libm/common/sf_lround.c",
|
|
||||||
"libm/common/sf_llround.c",
|
|
||||||
"libm/common/sf_nearbyint.c",
|
|
||||||
"libm/common/sf_remquo.c",
|
|
||||||
"libm/common/sf_round.c",
|
|
||||||
"libm/common/sf_scalbln.c",
|
|
||||||
"libm/common/sf_trunc.c",
|
|
||||||
"libm/common/sf_exp.c",
|
|
||||||
"libm/common/sf_exp2.c",
|
|
||||||
"libm/common/sf_exp2_data.c",
|
|
||||||
"libm/common/sf_log.c",
|
|
||||||
"libm/common/sf_log_data.c",
|
|
||||||
"libm/common/sf_log2.c",
|
|
||||||
"libm/common/sf_log2_data.c",
|
|
||||||
"libm/common/sf_pow_log2_data.c",
|
|
||||||
"libm/common/sf_pow.c",
|
|
||||||
|
|
||||||
"libm/common/s_finite.c",
|
|
||||||
"libm/common/s_copysign.c",
|
|
||||||
"libm/common/s_modf.c",
|
|
||||||
"libm/common/s_scalbn.c",
|
|
||||||
"libm/common/s_cbrt.c",
|
|
||||||
"libm/common/s_exp10.c",
|
|
||||||
"libm/common/s_expm1.c",
|
|
||||||
"libm/common/s_ilogb.c",
|
|
||||||
"libm/common/s_infinity.c",
|
|
||||||
"libm/common/s_iseqsig.c",
|
|
||||||
"libm/common/s_isinf.c",
|
|
||||||
"libm/common/s_isinfd.c",
|
|
||||||
"libm/common/s_isnan.c",
|
|
||||||
"libm/common/s_isnand.c",
|
|
||||||
"libm/common/s_issignaling.c",
|
|
||||||
"libm/common/s_log1p.c",
|
|
||||||
"libm/common/s_nan.c",
|
|
||||||
"libm/common/s_nextafter.c",
|
|
||||||
"libm/common/s_pow10.c",
|
|
||||||
"libm/common/s_rint.c",
|
|
||||||
"libm/common/s_logb.c",
|
|
||||||
"libm/common/s_log2.c",
|
|
||||||
"libm/common/s_fdim.c",
|
|
||||||
"libm/common/s_fma.c",
|
|
||||||
"libm/common/s_fmax.c",
|
|
||||||
"libm/common/s_fmin.c",
|
|
||||||
"libm/common/s_fpclassify.c",
|
|
||||||
"libm/common/s_getpayload.c",
|
|
||||||
"libm/common/s_lrint.c",
|
|
||||||
"libm/common/s_llrint.c",
|
|
||||||
"libm/common/s_lround.c",
|
|
||||||
"libm/common/s_llround.c",
|
|
||||||
"libm/common/s_nearbyint.c",
|
|
||||||
"libm/common/s_remquo.c",
|
|
||||||
"libm/common/s_round.c",
|
|
||||||
"libm/common/s_scalbln.c",
|
|
||||||
"libm/common/s_signbit.c",
|
|
||||||
"libm/common/s_trunc.c",
|
|
||||||
"libm/common/exp.c",
|
|
||||||
"libm/common/exp2.c",
|
|
||||||
"libm/common/exp_data.c",
|
|
||||||
"libm/common/math_err_with_errno.c",
|
|
||||||
"libm/common/math_err_uflow.c",
|
|
||||||
"libm/common/math_err_oflow.c",
|
|
||||||
"libm/common/math_err_divzero.c",
|
|
||||||
"libm/common/math_err_invalid.c",
|
|
||||||
"libm/common/math_err_may_uflow.c",
|
|
||||||
"libm/common/math_err_check_uflow.c",
|
|
||||||
"libm/common/math_err_check_oflow.c",
|
|
||||||
"libm/common/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_data.c",
|
|
||||||
"libm/common/log2.c",
|
|
||||||
"libm/common/log2_data.c",
|
|
||||||
"libm/common/pow.c",
|
|
||||||
"libm/common/pow_log_data.c",
|
|
||||||
|
|
||||||
"libm/math/k_cos.c",
|
|
||||||
"libm/math/k_rem_pio2.c",
|
|
||||||
"libm/math/k_sin.c",
|
|
||||||
"libm/math/k_tan.c",
|
|
||||||
"libm/math/kf_cos.c",
|
|
||||||
"libm/math/kf_rem_pio2.c",
|
|
||||||
"libm/math/kf_sin.c",
|
|
||||||
"libm/math/kf_tan.c",
|
|
||||||
"libm/math/s_acos.c",
|
|
||||||
"libm/math/s_acosh.c",
|
|
||||||
"libm/math/s_asin.c",
|
|
||||||
"libm/math/s_asinh.c",
|
|
||||||
"libm/math/s_atan.c",
|
|
||||||
"libm/math/s_atan2.c",
|
|
||||||
"libm/math/s_atanh.c",
|
|
||||||
"libm/math/s_ceil.c",
|
|
||||||
"libm/math/s_cos.c",
|
|
||||||
"libm/math/s_cosh.c",
|
|
||||||
"libm/math/s_drem.c",
|
|
||||||
"libm/math/s_erf.c",
|
|
||||||
"libm/math/s_exp.c",
|
|
||||||
"libm/math/s_exp2.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_atan.c",
|
|
||||||
"libm/math/sf_atan2.c",
|
|
||||||
"libm/math/sf_atanh.c",
|
|
||||||
"libm/math/sf_ceil.c",
|
|
||||||
"libm/math/sf_cos.c",
|
|
||||||
"libm/math/sf_cosh.c",
|
|
||||||
"libm/math/sf_drem.c",
|
|
||||||
"libm/math/sf_erf.c",
|
|
||||||
"libm/math/sf_exp.c",
|
|
||||||
"libm/math/sf_exp2.c",
|
|
||||||
"libm/math/sf_fabs.c",
|
|
||||||
"libm/math/sf_floor.c",
|
|
||||||
"libm/math/sf_fmod.c",
|
|
||||||
"libm/math/sf_frexp.c",
|
|
||||||
"libm/math/sf_gamma.c",
|
|
||||||
"libm/math/sf_hypot.c",
|
|
||||||
"libm/math/sf_j0.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_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_tanh.c",
|
|
||||||
"libm/math/sf_tgamma.c",
|
|
||||||
"libm/math/sr_lgamma.c",
|
|
||||||
"libm/math/srf_lgamma.c",
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
+101
-922
File diff suppressed because it is too large
Load Diff
@@ -1,140 +0,0 @@
|
|||||||
package builder
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
)
|
|
||||||
|
|
||||||
var sema = make(chan struct{}, runtime.NumCPU())
|
|
||||||
|
|
||||||
type sizeTest struct {
|
|
||||||
target string
|
|
||||||
path string
|
|
||||||
codeSize uint64
|
|
||||||
rodataSize uint64
|
|
||||||
dataSize uint64
|
|
||||||
bssSize uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test whether code and data size is as expected for the given targets.
|
|
||||||
// This tests both the logic of loadProgramSize and checks that code size
|
|
||||||
// doesn't change unintentionally.
|
|
||||||
//
|
|
||||||
// If you find that code or data size is reduced, then great! You can reduce the
|
|
||||||
// number in this test.
|
|
||||||
// If you find that the code or data size is increased, take a look as to why
|
|
||||||
// this is. It could be due to an update (LLVM version, Go version, etc) which
|
|
||||||
// is fine, but it could also mean that a recent change introduced this size
|
|
||||||
// increase. If so, please consider whether this new feature is indeed worth the
|
|
||||||
// size increase for all users.
|
|
||||||
func TestBinarySize(t *testing.T) {
|
|
||||||
if runtime.GOOS == "linux" && !hasBuiltinTools {
|
|
||||||
// Debian LLVM packages are modified a bit and tend to produce
|
|
||||||
// different machine code. Ideally we'd fix this (with some attributes
|
|
||||||
// or something?), but for now skip it.
|
|
||||||
t.Skip("Skip: using external LLVM version so binary size might differ")
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a small number of very diverse targets that we want to test.
|
|
||||||
tests := []sizeTest{
|
|
||||||
// microcontrollers
|
|
||||||
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
|
|
||||||
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
|
||||||
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
|
|
||||||
|
|
||||||
// TODO: also check wasm. Right now this is difficult, because
|
|
||||||
// wasm binaries are run through wasm-opt and therefore the
|
|
||||||
// output varies by binaryen version.
|
|
||||||
}
|
|
||||||
for _, tc := range tests {
|
|
||||||
tc := tc
|
|
||||||
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Build the binary.
|
|
||||||
result := buildBinary(t, tc.target, tc.path)
|
|
||||||
|
|
||||||
// Check whether the size of the binary matches the expected size.
|
|
||||||
sizes, err := loadProgramSize(result.Executable, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not read program size:", err)
|
|
||||||
}
|
|
||||||
if sizes.Code != tc.codeSize || sizes.ROData != tc.rodataSize || sizes.Data != tc.dataSize || sizes.BSS != tc.bssSize {
|
|
||||||
t.Errorf("Unexpected code size when compiling: -target=%s %s", tc.target, tc.path)
|
|
||||||
t.Errorf(" code rodata data bss")
|
|
||||||
t.Errorf("expected: %6d %6d %6d %6d", tc.codeSize, tc.rodataSize, tc.dataSize, tc.bssSize)
|
|
||||||
t.Errorf("actual: %6d %6d %6d %6d", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the -size=full flag attributes binary size to the correct package
|
|
||||||
// without filesystem paths and things like that.
|
|
||||||
func TestSizeFull(t *testing.T) {
|
|
||||||
tests := []string{
|
|
||||||
"microbit",
|
|
||||||
"wasip1",
|
|
||||||
}
|
|
||||||
|
|
||||||
libMatch := regexp.MustCompile(`^C [a-z -]+$`) // example: "C interrupt vector"
|
|
||||||
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
|
|
||||||
|
|
||||||
for _, target := range tests {
|
|
||||||
target := target
|
|
||||||
t.Run(target, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Build the binary.
|
|
||||||
result := buildBinary(t, target, "examples/serial")
|
|
||||||
|
|
||||||
// Check whether the binary doesn't contain any unexpected package
|
|
||||||
// names.
|
|
||||||
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not read program size:", err)
|
|
||||||
}
|
|
||||||
for _, pkg := range sizes.sortedPackageNames() {
|
|
||||||
if pkg == "(padding)" || pkg == "(unknown)" {
|
|
||||||
// TODO: correctly attribute all unknown binary size.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if libMatch.MatchString(pkg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if pkgMatch.MatchString(pkg) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t.Error("unexpected package name in size output:", pkg)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildBinary(t *testing.T, targetString, pkgName string) BuildResult {
|
|
||||||
options := compileopts.Options{
|
|
||||||
Target: targetString,
|
|
||||||
Opt: "z",
|
|
||||||
Semaphore: sema,
|
|
||||||
InterpTimeout: 60 * time.Second,
|
|
||||||
Debug: true,
|
|
||||||
VerifyIR: true,
|
|
||||||
}
|
|
||||||
target, err := compileopts.LoadTarget(&options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not load target:", err)
|
|
||||||
}
|
|
||||||
config := &compileopts.Config{
|
|
||||||
Options: &options,
|
|
||||||
Target: target,
|
|
||||||
}
|
|
||||||
result, err := Build(pkgName, "", t.TempDir(), config)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not build:", err)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build byollvm
|
// +build byollvm
|
||||||
|
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
@@ -12,7 +12,8 @@ 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_wasm(int argc, char **argv);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
@@ -23,12 +24,12 @@ 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...)
|
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 +40,10 @@ 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))
|
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
||||||
|
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,4 +1,4 @@
|
|||||||
//go:build !byollvm
|
// +build !byollvm
|
||||||
|
|
||||||
package builder
|
package builder
|
||||||
|
|
||||||
|
|||||||
+27
-172
@@ -1,195 +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(commands["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 cmdNames, ok := commands[linker]; ok {
|
||||||
cmd = exec.Command(os.Args[0], append([]string{linker}, flags...)...)
|
return execCommand(cmdNames, 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.Split(message, "\n") {
|
|
||||||
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
|
|
||||||
if matches != nil {
|
|
||||||
parsedError = true
|
|
||||||
line, _ := strconv.Atoi(matches[3])
|
|
||||||
// TODO: detect common mistakes like -gc=none?
|
|
||||||
msg := "linker could not find symbol " + symbolName
|
|
||||||
if symbolName == "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
|
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-5
@@ -10,7 +10,7 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func convertELFFileToUF2File(infile, outfile string, uf2FamilyID string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return os.WriteFile(outfile, output, 0644)
|
return ioutil.WriteFile(outfile, output, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
// convertBinToUF2 converts the binary bytes in input to UF2 formatted data.
|
||||||
@@ -141,13 +141,11 @@ func split(input []byte, limit int) [][]byte {
|
|||||||
var block []byte
|
var block []byte
|
||||||
output := make([][]byte, 0, len(input)/limit+1)
|
output := make([][]byte, 0, len(input)/limit+1)
|
||||||
for len(input) >= limit {
|
for len(input) >= limit {
|
||||||
// add all blocks
|
|
||||||
block, input = input[:limit], input[limit:]
|
block, input = input[:limit], input[limit:]
|
||||||
output = append(output, block)
|
output = append(output, block)
|
||||||
}
|
}
|
||||||
if len(input) > 0 {
|
if len(input) > 0 {
|
||||||
// add remaining block (that isn't full sized)
|
output = append(output, input[:len(input)])
|
||||||
output = append(output, input)
|
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
`
|
|
||||||
+714
-797
File diff suppressed because it is too large
Load Diff
@@ -1,17 +0,0 @@
|
|||||||
//go:build go1.22
|
|
||||||
|
|
||||||
package cgo
|
|
||||||
|
|
||||||
// Code specifically for Go 1.22.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"go/ast"
|
|
||||||
"go/token"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
setASTFileFields = func(f *ast.File, start, end token.Pos) {
|
|
||||||
f.FileStart = start
|
|
||||||
f.FileEnd = end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+38
-132
@@ -5,12 +5,12 @@ import (
|
|||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/build"
|
||||||
"go/format"
|
"go/format"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/scanner"
|
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -23,30 +23,39 @@ 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")
|
||||||
|
|
||||||
// This changed to 'undefined:', in Go 1.20.
|
// Make sure all functions are wrapped, even those that would otherwise be
|
||||||
result = strings.ReplaceAll(result, ": undeclared name:", ": undefined:")
|
// single-line functions. This is necessary because Go 1.14 changed the way
|
||||||
// Go 1.20 added a bit more detail
|
// such functions are wrapped and it's important to have consistent test
|
||||||
result = regexp.MustCompile(`(unknown field z in struct literal).*`).ReplaceAllString(result, "$1")
|
// results.
|
||||||
|
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
|
||||||
|
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
|
||||||
|
|
||||||
return result
|
return actual
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCGo(t *testing.T) {
|
func TestCGo(t *testing.T) {
|
||||||
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"}
|
var cflags = []string{"--target=armv6m-none-eabi"}
|
||||||
|
|
||||||
for _, name := range []string{
|
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||||
"basic",
|
|
||||||
"errors",
|
|
||||||
"types",
|
|
||||||
"symbols",
|
|
||||||
"flags",
|
|
||||||
"const",
|
|
||||||
} {
|
|
||||||
name := name // avoid a race condition
|
name := name // avoid a race condition
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
// Skip tests that require specific Go version.
|
||||||
|
if name == "errors" {
|
||||||
|
ok := false
|
||||||
|
for _, version := range build.Default.ReleaseTags {
|
||||||
|
if version == "go1.16" {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
t.Skip("Results for errors test are only valid for Go 1.16+")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Read the AST in memory.
|
// Read the AST in memory.
|
||||||
path := filepath.Join("testdata", name+".go")
|
path := filepath.Join("testdata", name+".go")
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
@@ -56,7 +65,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", fset, cflags)
|
||||||
|
|
||||||
// Check the AST for type errors.
|
// Check the AST for type errors.
|
||||||
var typecheckErrors []error
|
var typecheckErrors []error
|
||||||
@@ -64,10 +73,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).
|
||||||
@@ -92,15 +101,15 @@ 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(string(buf.Bytes()))
|
||||||
|
|
||||||
// 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")
|
||||||
expectedBytes, err := os.ReadFile(outfile)
|
expectedBytes, err := ioutil.ReadFile(outfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("could not read expected output: %v", err)
|
t.Fatalf("could not read expected output: %v", err)
|
||||||
}
|
}
|
||||||
@@ -111,7 +120,7 @@ func TestCGo(t *testing.T) {
|
|||||||
// It is not. Test failed.
|
// It is not. Test failed.
|
||||||
if *flagUpdate {
|
if *flagUpdate {
|
||||||
// Update the file with the expected data.
|
// Update the file with the expected data.
|
||||||
err := os.WriteFile(outfile, []byte(actual), 0666)
|
err := ioutil.WriteFile(outfile, []byte(actual), 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("could not write updated output file:", err)
|
t.Error("could not write updated output file:", err)
|
||||||
}
|
}
|
||||||
@@ -123,112 +132,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:
|
||||||
@@ -236,16 +148,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/")
|
||||||
|
|||||||
+77
-336
@@ -11,326 +11,105 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
|
|
||||||
precedences = map[token.Token]int{
|
|
||||||
token.OR: precedenceOr,
|
|
||||||
token.XOR: precedenceXor,
|
|
||||||
token.AND: precedenceAnd,
|
|
||||||
token.SHL: precedenceShift,
|
|
||||||
token.SHR: precedenceShift,
|
|
||||||
token.ADD: precedenceAdd,
|
|
||||||
token.SUB: precedenceAdd,
|
|
||||||
token.MUL: precedenceMul,
|
|
||||||
token.QUO: precedenceMul,
|
|
||||||
token.REM: precedenceMul,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// See: https://en.cppreference.com/w/c/language/operator_precedence
|
|
||||||
const (
|
|
||||||
precedenceLowest = iota + 1
|
|
||||||
precedenceOr
|
|
||||||
precedenceXor
|
|
||||||
precedenceAnd
|
|
||||||
precedenceShift
|
|
||||||
precedenceAdd
|
|
||||||
precedenceMul
|
|
||||||
precedencePrefix
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This must be done in an init function to avoid an initialization order
|
|
||||||
// failure.
|
|
||||||
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
|
|
||||||
token.IDENT: parseIdent,
|
|
||||||
token.INT: parseBasicLit,
|
|
||||||
token.FLOAT: parseBasicLit,
|
|
||||||
token.STRING: parseBasicLit,
|
|
||||||
token.CHAR: parseBasicLit,
|
|
||||||
token.LPAREN: parseParenExpr,
|
|
||||||
token.SUB: parseUnaryExpr,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseConst parses the given string as a C constant.
|
// 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)
|
||||||
|
expr, err := parseConstExpr(t)
|
||||||
// If params is non-nil (could be a zero length slice), this const is
|
if t.token != token.EOF {
|
||||||
// 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)
|
|
||||||
t.Next()
|
|
||||||
if t.curToken != token.EOF {
|
|
||||||
return nil, &scanner.Error{
|
return nil, &scanner.Error{
|
||||||
Pos: t.fset.Position(t.curPos),
|
Pos: t.fset.Position(t.pos),
|
||||||
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
|
Msg: "unexpected token " + t.token.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return expr, err
|
return expr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseConstExpr parses a stream of C tokens to a Go expression.
|
// parseConstExpr parses a stream of C tokens to a Go expression.
|
||||||
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
|
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||||
if t.curToken == token.EOF {
|
switch t.token {
|
||||||
|
case token.LPAREN:
|
||||||
|
lparen := t.pos
|
||||||
|
t.Next()
|
||||||
|
x, err := parseConstExpr(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if t.token != token.RPAREN {
|
||||||
|
return nil, unexpectedToken(t, token.RPAREN)
|
||||||
|
}
|
||||||
|
expr := &ast.ParenExpr{
|
||||||
|
Lparen: lparen,
|
||||||
|
X: x,
|
||||||
|
Rparen: t.pos,
|
||||||
|
}
|
||||||
|
t.Next()
|
||||||
|
return expr, nil
|
||||||
|
case token.INT, token.FLOAT, token.STRING, token.CHAR:
|
||||||
|
expr := &ast.BasicLit{
|
||||||
|
ValuePos: t.pos,
|
||||||
|
Kind: t.token,
|
||||||
|
Value: t.value,
|
||||||
|
}
|
||||||
|
t.Next()
|
||||||
|
return expr, nil
|
||||||
|
case token.IDENT:
|
||||||
|
expr := &ast.Ident{
|
||||||
|
NamePos: t.pos,
|
||||||
|
Name: "C." + t.value,
|
||||||
|
}
|
||||||
|
t.Next()
|
||||||
|
return expr, nil
|
||||||
|
case token.EOF:
|
||||||
return nil, &scanner.Error{
|
return nil, &scanner.Error{
|
||||||
Pos: t.fset.Position(t.curPos),
|
Pos: t.fset.Position(t.pos),
|
||||||
Msg: "empty constant",
|
Msg: "empty constant",
|
||||||
}
|
}
|
||||||
}
|
default:
|
||||||
prefix := prefixParseFns[t.curToken]
|
|
||||||
if prefix == nil {
|
|
||||||
return nil, &scanner.Error{
|
return nil, &scanner.Error{
|
||||||
Pos: t.fset.Position(t.curPos),
|
Pos: t.fset.Position(t.pos),
|
||||||
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
|
Msg: fmt.Sprintf("unexpected token %s", t.token),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
leftExpr, err := prefix(t)
|
|
||||||
|
|
||||||
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
|
|
||||||
switch t.peekToken {
|
|
||||||
case token.OR, token.XOR, token.AND, token.SHL, token.SHR, token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
|
|
||||||
t.Next()
|
|
||||||
leftExpr, err = parseBinaryExpr(t, leftExpr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return leftExpr, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
// If the identifier is one of the parameters of this function-like macro,
|
|
||||||
// use the parameter value.
|
|
||||||
if val, ok := t.params[t.curValue]; ok {
|
|
||||||
return val, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.f != nil {
|
|
||||||
// Check whether this identifier is actually a macro "call" with
|
|
||||||
// parameters. In that case, we should parse the parameters and pass it
|
|
||||||
// on to a new invocation of parseConst.
|
|
||||||
if t.peekToken == token.LPAREN {
|
|
||||||
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
|
|
||||||
// We know the current and peek tokens (the peek one is the '('
|
|
||||||
// token). So skip ahead until the current token is the first
|
|
||||||
// unknown token.
|
|
||||||
t.Next()
|
|
||||||
t.Next()
|
|
||||||
|
|
||||||
// Parse the list of parameters until ')' (rparen) is found.
|
|
||||||
params := []ast.Expr{}
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
if i == 0 && t.curToken == token.RPAREN {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
x, err := parseConstExpr(t, precedenceLowest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
params = append(params, x)
|
|
||||||
t.Next()
|
|
||||||
if t.curToken == token.COMMA {
|
|
||||||
t.Next()
|
|
||||||
} else if t.curToken == token.RPAREN {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
return nil, &scanner.Error{
|
|
||||||
Pos: t.fset.Position(t.curPos),
|
|
||||||
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the macro value and use it as the identifier value.
|
|
||||||
rparen := t.curPos
|
|
||||||
pos, text := t.f.getMacro(cursor)
|
|
||||||
return parseConst(pos, t.fset, text, params, rparen, t.f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normally the name is something defined in the file (like another
|
|
||||||
// macro) which we get the declaration from using getASTDeclName.
|
|
||||||
// This ensures that names that are only referenced inside a macro are
|
|
||||||
// still getting defined.
|
|
||||||
if cursor, ok := t.f.names[t.curValue]; ok {
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: t.curPos,
|
|
||||||
Name: t.f.getASTDeclName(t.curValue, cursor, false),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// t.f is nil during testing. This is a fallback.
|
|
||||||
return &ast.Ident{
|
|
||||||
NamePos: t.curPos,
|
|
||||||
Name: "C." + t.curValue,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
return &ast.BasicLit{
|
|
||||||
ValuePos: t.curPos,
|
|
||||||
Kind: t.curToken,
|
|
||||||
Value: t.curValue,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
lparen := t.curPos
|
|
||||||
t.Next()
|
|
||||||
x, err := parseConstExpr(t, precedenceLowest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.Next()
|
|
||||||
if t.curToken != token.RPAREN {
|
|
||||||
return nil, unexpectedToken(t, token.RPAREN)
|
|
||||||
}
|
|
||||||
expr := &ast.ParenExpr{
|
|
||||||
Lparen: lparen,
|
|
||||||
X: x,
|
|
||||||
Rparen: t.curPos,
|
|
||||||
}
|
|
||||||
return expr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
|
|
||||||
expression := &ast.BinaryExpr{
|
|
||||||
X: left,
|
|
||||||
Op: t.curToken,
|
|
||||||
OpPos: t.curPos,
|
|
||||||
}
|
|
||||||
precedence := precedences[t.curToken]
|
|
||||||
t.Next()
|
|
||||||
right, err := parseConstExpr(t, precedence)
|
|
||||||
expression.Y = right
|
|
||||||
return expression, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
|
||||||
expression := &ast.UnaryExpr{
|
|
||||||
OpPos: t.curPos,
|
|
||||||
Op: t.curToken,
|
|
||||||
}
|
|
||||||
t.Next()
|
|
||||||
x, err := parseConstExpr(t, precedencePrefix)
|
|
||||||
expression.X = x
|
|
||||||
return expression, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
||||||
// BAR".
|
// BAR".
|
||||||
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
||||||
return &scanner.Error{
|
return &scanner.Error{
|
||||||
Pos: t.fset.Position(t.curPos),
|
Pos: t.fset.Position(t.pos),
|
||||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
|
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
pos token.Pos
|
||||||
curPos, peekPos token.Pos
|
fset *token.FileSet
|
||||||
fset *token.FileSet
|
token token.Token
|
||||||
curToken, peekToken token.Token
|
value 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,
|
pos: start,
|
||||||
peekPos: start,
|
fset: fset,
|
||||||
fset: fset,
|
buf: buf,
|
||||||
buf: buf,
|
token: token.ILLEGAL,
|
||||||
peekToken: token.ILLEGAL,
|
|
||||||
params: make(map[string]ast.Expr),
|
|
||||||
}
|
}
|
||||||
// Parse the first two tokens (cur and peek).
|
t.Next() // Parse the first token.
|
||||||
t.Next()
|
|
||||||
t.Next()
|
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next consumes the next token in the stream. There is no return value, read
|
// Next consumes the next token in the stream. There is no return value, read
|
||||||
// the next token from the pos, token and value properties.
|
// the next token from the pos, token and value properties.
|
||||||
func (t *tokenizer) Next() {
|
func (t *tokenizer) Next() {
|
||||||
// The previous peek is now the current token.
|
t.pos += token.Pos(len(t.value))
|
||||||
t.curPos = t.peekPos
|
|
||||||
t.curToken = t.peekToken
|
|
||||||
t.curValue = t.peekValue
|
|
||||||
|
|
||||||
// Parse the next peek token.
|
|
||||||
if t.peekPos != token.NoPos {
|
|
||||||
t.peekPos += token.Pos(len(t.curValue))
|
|
||||||
}
|
|
||||||
for {
|
for {
|
||||||
if len(t.buf) == 0 {
|
if len(t.buf) == 0 {
|
||||||
t.peekToken = token.EOF
|
t.token = token.EOF
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c := t.buf[0]
|
c := t.buf[0]
|
||||||
@@ -339,55 +118,17 @@ 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.pos++
|
||||||
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 == ')':
|
||||||
// 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.
|
|
||||||
switch c {
|
switch c {
|
||||||
case '(':
|
case '(':
|
||||||
t.peekToken = token.LPAREN
|
t.token = token.LPAREN
|
||||||
case ')':
|
case ')':
|
||||||
t.peekToken = token.RPAREN
|
t.token = token.RPAREN
|
||||||
case ',':
|
|
||||||
t.peekToken = token.COMMA
|
|
||||||
case '+':
|
|
||||||
t.peekToken = token.ADD
|
|
||||||
case '-':
|
|
||||||
t.peekToken = token.SUB
|
|
||||||
case '*':
|
|
||||||
t.peekToken = token.MUL
|
|
||||||
case '/':
|
|
||||||
t.peekToken = token.QUO
|
|
||||||
case '%':
|
|
||||||
t.peekToken = token.REM
|
|
||||||
case '&':
|
|
||||||
t.peekToken = token.AND
|
|
||||||
case '|':
|
|
||||||
t.peekToken = token.OR
|
|
||||||
case '^':
|
|
||||||
t.peekToken = token.XOR
|
|
||||||
}
|
}
|
||||||
t.peekValue = t.buf[:1]
|
t.value = t.buf[:1]
|
||||||
t.buf = t.buf[1:]
|
t.buf = t.buf[1:]
|
||||||
return
|
return
|
||||||
case c >= '0' && c <= '9':
|
case c >= '0' && c <= '9':
|
||||||
@@ -405,17 +146,17 @@ func (t *tokenizer) Next() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.peekValue = t.buf[:tokenLen]
|
t.value = t.buf[:tokenLen]
|
||||||
t.buf = t.buf[tokenLen:]
|
t.buf = t.buf[tokenLen:]
|
||||||
if hasDot {
|
if hasDot {
|
||||||
// Integer constants are more complicated than this but this is
|
// Integer constants are more complicated than this but this is
|
||||||
// a close approximation.
|
// a close approximation.
|
||||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||||
t.peekToken = token.FLOAT
|
t.token = token.FLOAT
|
||||||
t.peekValue = strings.TrimRight(t.peekValue, "f")
|
t.value = strings.TrimRight(t.value, "f")
|
||||||
} else {
|
} else {
|
||||||
t.peekToken = token.INT
|
t.token = token.INT
|
||||||
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
|
t.value = strings.TrimRight(t.value, "uUlL")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
||||||
@@ -429,9 +170,9 @@ func (t *tokenizer) Next() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.peekValue = t.buf[:tokenLen]
|
t.value = t.buf[:tokenLen]
|
||||||
t.buf = t.buf[tokenLen:]
|
t.buf = t.buf[tokenLen:]
|
||||||
t.peekToken = token.IDENT
|
t.token = token.IDENT
|
||||||
return
|
return
|
||||||
case c == '"':
|
case c == '"':
|
||||||
// String constant. Find the first '"' character that is not
|
// String constant. Find the first '"' character that is not
|
||||||
@@ -447,8 +188,8 @@ func (t *tokenizer) Next() {
|
|||||||
escape = c == '\\'
|
escape = c == '\\'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.peekToken = token.STRING
|
t.token = token.STRING
|
||||||
t.peekValue = t.buf[:tokenLen]
|
t.value = t.buf[:tokenLen]
|
||||||
t.buf = t.buf[tokenLen:]
|
t.buf = t.buf[tokenLen:]
|
||||||
return
|
return
|
||||||
case c == '\'':
|
case c == '\'':
|
||||||
@@ -465,12 +206,12 @@ func (t *tokenizer) Next() {
|
|||||||
escape = c == '\\'
|
escape = c == '\\'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.peekToken = token.CHAR
|
t.token = token.CHAR
|
||||||
t.peekValue = t.buf[:tokenLen]
|
t.value = t.buf[:tokenLen]
|
||||||
t.buf = t.buf[tokenLen:]
|
t.buf = t.buf[tokenLen:]
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
t.peekToken = token.ILLEGAL
|
t.token = token.ILLEGAL
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-28
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
|
|||||||
{`(5)`, `(5)`},
|
{`(5)`, `(5)`},
|
||||||
{`(((5)))`, `(5)`},
|
{`(((5)))`, `(5)`},
|
||||||
{`)`, `error: 1:1: unexpected token )`},
|
{`)`, `error: 1:1: unexpected token )`},
|
||||||
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
|
{`5)`, `error: 1:2: unexpected token )`},
|
||||||
{" \t)", `error: 1:4: unexpected token )`},
|
{" \t)", `error: 1:4: unexpected token )`},
|
||||||
{`5.8f`, `5.8`},
|
{`5.8f`, `5.8`},
|
||||||
{`foo`, `C.foo`},
|
{`foo`, `C.foo`},
|
||||||
@@ -30,36 +30,11 @@ func TestParseConst(t *testing.T) {
|
|||||||
{`'a'`, `'a'`},
|
{`'a'`, `'a'`},
|
||||||
{`0b10`, `0b10`},
|
{`0b10`, `0b10`},
|
||||||
{`0x1234_5678`, `0x1234_5678`},
|
{`0x1234_5678`, `0x1234_5678`},
|
||||||
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
|
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
|
||||||
// Binary operators.
|
|
||||||
{`5+5`, `5 + 5`},
|
|
||||||
{`5-5`, `5 - 5`},
|
|
||||||
{`5*5`, `5 * 5`},
|
|
||||||
{`5/5`, `5 / 5`},
|
|
||||||
{`5%5`, `5 % 5`},
|
|
||||||
{`5&5`, `5 & 5`},
|
|
||||||
{`5|5`, `5 | 5`},
|
|
||||||
{`5^5`, `5 ^ 5`},
|
|
||||||
{`5<<5`, `5 << 5`},
|
|
||||||
{`5>>5`, `5 >> 5`},
|
|
||||||
{`5>>5 + 3`, `5 >> (5 + 3)`},
|
|
||||||
{`5>>5 ^ 3`, `5>>5 ^ 3`},
|
|
||||||
{`5||5`, `error: 1:2: unexpected token ||, expected end of expression`}, // logical binops aren't supported yet
|
|
||||||
{`(5/5)`, `(5 / 5)`},
|
|
||||||
{`1 - 2`, `1 - 2`},
|
|
||||||
{`1 - 2 + 3`, `1 - 2 + 3`},
|
|
||||||
{`1 - 2 * 3`, `1 - 2*3`},
|
|
||||||
{`(1 - 2) * 3`, `(1 - 2) * 3`},
|
|
||||||
{`1 * 2 - 3`, `1*2 - 3`},
|
|
||||||
{`1 * (2 - 3)`, `1 * (2 - 3)`},
|
|
||||||
// Unary operators.
|
|
||||||
{`-5`, `-5`},
|
|
||||||
{`-5-2`, `-5 - 2`},
|
|
||||||
{`5 - - 2`, `5 - -2`},
|
|
||||||
} {
|
} {
|
||||||
fset := token.NewFileSet()
|
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: ") {
|
||||||
|
|||||||
+249
-579
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
|||||||
|
// +build !byollvm
|
||||||
|
// +build !llvm10
|
||||||
|
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
||||||
|
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
|
||||||
|
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
||||||
|
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
||||||
|
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
||||||
|
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// +build !byollvm
|
||||||
|
// +build llvm10
|
||||||
|
|
||||||
|
package cgo
|
||||||
|
|
||||||
|
/*
|
||||||
|
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
|
||||||
|
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
|
||||||
|
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
|
||||||
|
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
|
||||||
|
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
|
||||||
|
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
|
||||||
|
*/
|
||||||
|
import "C"
|
||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -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"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
|
|
||||||
|
|
||||||
package cgo
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo linux CFLAGS: -I/usr/include/llvm-20 -I/usr/include/llvm-c-20 -I/usr/lib/llvm-20/include -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"
|
|
||||||
+2
-22
@@ -3,7 +3,7 @@
|
|||||||
// are slightly different from the ones defined in libclang.go, but they
|
// are slightly different from the ones defined in libclang.go, but they
|
||||||
// should be ABI compatible.
|
// should be ABI compatible.
|
||||||
|
|
||||||
#include <clang-c/Index.h> // If this fails, libclang headers aren't available. Please take a look here: https://tinygo.org/docs/guides/build/
|
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
|
||||||
|
|
||||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||||
return clang_getTranslationUnitCursor(tu);
|
return clang_getTranslationUnitCursor(tu);
|
||||||
@@ -17,14 +17,6 @@ CXString tinygo_clang_getCursorSpelling(CXCursor c) {
|
|||||||
return clang_getCursorSpelling(c);
|
return clang_getCursorSpelling(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
CXString tinygo_clang_getCursorPrettyPrinted(CXCursor c, CXPrintingPolicy policy) {
|
|
||||||
return clang_getCursorPrettyPrinted(c, policy);
|
|
||||||
}
|
|
||||||
|
|
||||||
CXPrintingPolicy tinygo_clang_getCursorPrintingPolicy(CXCursor c) {
|
|
||||||
return clang_getCursorPrintingPolicy(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
|
enum CXCursorKind tinygo_clang_getCursorKind(CXCursor c) {
|
||||||
return clang_getCursorKind(c);
|
return clang_getCursorKind(c);
|
||||||
}
|
}
|
||||||
@@ -53,10 +45,6 @@ CXCursor tinygo_clang_Cursor_getArgument(CXCursor c, unsigned i) {
|
|||||||
return clang_Cursor_getArgument(c, i);
|
return clang_Cursor_getArgument(c, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CX_StorageClass tinygo_clang_Cursor_getStorageClass(CXCursor c) {
|
|
||||||
return clang_Cursor_getStorageClass(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
CXSourceLocation tinygo_clang_getCursorLocation(CXCursor c) {
|
||||||
return clang_getCursorLocation(c);
|
return clang_getCursorLocation(c);
|
||||||
}
|
}
|
||||||
@@ -77,14 +65,6 @@ CXType tinygo_clang_getEnumDeclIntegerType(CXCursor c) {
|
|||||||
return clang_getEnumDeclIntegerType(c);
|
return clang_getEnumDeclIntegerType(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
|
|
||||||
return clang_Cursor_isAnonymous(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
||||||
return clang_Cursor_isBitField(c);
|
return clang_Cursor_isBitField(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
|
|
||||||
return clang_Cursor_isMacroFunctionLike(c);
|
|
||||||
}
|
|
||||||
@@ -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=([^@\-].*)`),
|
||||||
|
|||||||
@@ -108,7 +108,6 @@ var goodLinkerFlags = [][]string{
|
|||||||
{"-Fbar"},
|
{"-Fbar"},
|
||||||
{"-lbar"},
|
{"-lbar"},
|
||||||
{"-Lbar"},
|
{"-Lbar"},
|
||||||
{"--export=my_symbol"},
|
|
||||||
{"-fpic"},
|
{"-fpic"},
|
||||||
{"-fno-pic"},
|
{"-fno-pic"},
|
||||||
{"-fPIC"},
|
{"-fPIC"},
|
||||||
|
|||||||
Vendored
+20
-48
@@ -1,54 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
//go:linkname _Cgo_CString runtime.cgo_CString
|
type C.int16_t = int16
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
type C.int8_t = int8
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
type C.uint64_t = uint64
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
type C.char uint8
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
type C.int int32
|
||||||
}
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
type C.schar int8
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
type C.uint uint32
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
type C.ulong uint32
|
||||||
}
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
//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 (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
|
|||||||
Vendored
-16
@@ -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
|
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
+22
-55
@@ -1,62 +1,29 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
//go:linkname _Cgo_CString runtime.cgo_CString
|
const C.bar = C.foo
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
const C.foo = 3
|
||||||
|
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
type C.int16_t = int16
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
type C.int8_t = int8
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
type C.uint64_t = uint64
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
type C.uint8_t = uint8
|
||||||
}
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
type C.int int32
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
type C.schar int8
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
type C.short int16
|
||||||
}
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
|
type C.ulong uint32
|
||||||
func _Cgo___CBytes([]byte) unsafe.Pointer
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
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 (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
|
|
||||||
const _Cgo_foo = 3
|
|
||||||
const _Cgo_bar = _Cgo_foo
|
|
||||||
const _Cgo_unreferenced = 4
|
|
||||||
const _Cgo_referenced = _Cgo_unreferenced
|
|
||||||
const _Cgo_fnlike_val = 5
|
|
||||||
const _Cgo_square_val = (20 * 20)
|
|
||||||
const _Cgo_add_val = (3 + 5)
|
|
||||||
|
|||||||
Vendored
+1
-35
@@ -10,39 +10,18 @@ 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
|
|
||||||
// #cgo CFLAGS: -DSOME_PARAM_CONST_invalid=3/+3
|
|
||||||
// #cgo CFLAGS: -DSOME_PARAM_CONST_valid=3+4
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// #warning another warning
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// #define add(a, b) (a+b)
|
|
||||||
// #define add_toomuch add(1, 2, 3)
|
|
||||||
// #define add_toolittle add(1)
|
|
||||||
import "C"
|
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
|
||||||
_ C.char = 2 << 10
|
_ C.uint8_t = 2 << 10
|
||||||
|
|
||||||
// z member does not exist
|
// z member does not exist
|
||||||
_ C.point_t = C.point_t{z: 3}
|
_ C.point_t = C.point_t{z: 3}
|
||||||
@@ -51,17 +30,4 @@ var (
|
|||||||
_ = C.SOME_CONST_1
|
_ = C.SOME_CONST_1
|
||||||
|
|
||||||
_ byte = C.SOME_CONST_3
|
_ byte = C.SOME_CONST_3
|
||||||
|
|
||||||
_ = C.SOME_CONST_4
|
|
||||||
|
|
||||||
_ = C.SOME_CONST_b
|
|
||||||
|
|
||||||
_ = C.SOME_CONST_startspace
|
|
||||||
|
|
||||||
// constants passed by a command line parameter
|
|
||||||
_ = C.SOME_PARAM_CONST_invalid
|
|
||||||
_ = C.SOME_PARAM_CONST_valid
|
|
||||||
|
|
||||||
_ = C.add_toomuch
|
|
||||||
_ = C.add_toolittle
|
|
||||||
)
|
)
|
||||||
|
|||||||
Vendored
+28
-74
@@ -1,89 +1,43 @@
|
|||||||
// 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:13:23: unexpected token )
|
||||||
// testdata/errors.go:18:23: unexpected token ), expected end of expression
|
|
||||||
// testdata/errors.go:26:26: unexpected token ), expected end of expression
|
|
||||||
// testdata/errors.go:21:33: unexpected token ), expected end of expression
|
|
||||||
// testdata/errors.go:22:34: unexpected token ), expected end of expression
|
|
||||||
// -: unexpected token INT, expected end of expression
|
|
||||||
// testdata/errors.go:35:35: unexpected number of parameters: expected 2, got 3
|
|
||||||
// testdata/errors.go:36:31: unexpected number of parameters: expected 2, got 1
|
|
||||||
// testdata/errors.go:3:1: function "unusedFunction" in #cgo noescape line is not used
|
|
||||||
|
|
||||||
// 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 uint8 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: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
|
const C.SOME_CONST_3 = 1234
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
|
||||||
|
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
type C.int16_t = int16
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
type C.int8_t = int8
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
type C.uint64_t = uint64
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
|
type C.point_t = struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
|
||||||
|
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
|
|
||||||
func _Cgo___CBytes([]byte) unsafe.Pointer
|
|
||||||
|
|
||||||
func _Cgo_CBytes(b []byte) unsafe.Pointer {
|
|
||||||
return _Cgo___CBytes(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
|
|
||||||
func _Cgo___get_errno_num() uintptr
|
|
||||||
|
|
||||||
func _Cgo___get_errno() error {
|
|
||||||
return syscall.Errno(_Cgo___get_errno_num())
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
type _Cgo_struct_point_t struct {
|
|
||||||
x _Cgo_int
|
|
||||||
y _Cgo_int
|
|
||||||
}
|
|
||||||
type _Cgo_point_t = _Cgo_struct_point_t
|
|
||||||
|
|
||||||
const _Cgo_SOME_CONST_3 = 1234
|
|
||||||
const _Cgo_SOME_PARAM_CONST_valid = 3 + 4
|
|
||||||
|
|||||||
Vendored
+22
-50
@@ -5,58 +5,30 @@
|
|||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
//go:linkname _Cgo_CString runtime.cgo_CString
|
const C.BAR = 3
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
const C.FOO_H = 1
|
||||||
|
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
type C.int16_t = int16
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
type C.int8_t = int8
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
type C.uint64_t = uint64
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
type C.uint8_t = uint8
|
||||||
}
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
type C.int int32
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
type C.schar int8
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
type C.short int16
|
||||||
}
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
|
type C.ulong uint32
|
||||||
func _Cgo___CBytes([]byte) unsafe.Pointer
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
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 (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
|
|
||||||
const _Cgo_BAR = 3
|
|
||||||
const _Cgo_FOO_H = 1
|
|
||||||
|
|||||||
Vendored
-30
@@ -1,30 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Function signatures.
|
|
||||||
int foo(int a, int b);
|
|
||||||
void variadic0();
|
|
||||||
void variadic2(int x, int y, ...);
|
|
||||||
static void staticfunc(int x);
|
|
||||||
|
|
||||||
// Global variable signatures.
|
|
||||||
extern int someValue;
|
|
||||||
|
|
||||||
void notEscapingFunction(int *a);
|
|
||||||
|
|
||||||
#cgo noescape notEscapingFunction
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// Test function signatures.
|
|
||||||
func accessFunctions() {
|
|
||||||
C.foo(3, 4)
|
|
||||||
C.variadic0()
|
|
||||||
C.variadic2(3, 5)
|
|
||||||
C.staticfunc(3)
|
|
||||||
C.notEscapingFunction(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func accessGlobals() {
|
|
||||||
_ = C.someValue
|
|
||||||
}
|
|
||||||
Vendored
-84
@@ -1,84 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
|
||||||
|
|
||||||
//go:linkname _Cgo_CString runtime.cgo_CString
|
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
|
||||||
|
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
|
||||||
|
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
|
||||||
|
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
|
||||||
|
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname _Cgo___CBytes runtime.cgo_CBytes
|
|
||||||
func _Cgo___CBytes([]byte) unsafe.Pointer
|
|
||||||
|
|
||||||
func _Cgo_CBytes(b []byte) unsafe.Pointer {
|
|
||||||
return _Cgo___CBytes(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:linkname _Cgo___get_errno_num runtime.cgo_errno
|
|
||||||
func _Cgo___get_errno_num() uintptr
|
|
||||||
|
|
||||||
func _Cgo___get_errno() error {
|
|
||||||
return syscall.Errno(_Cgo___get_errno_num())
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
|
|
||||||
//export foo
|
|
||||||
func _Cgo_foo(a _Cgo_int, b _Cgo_int) _Cgo_int
|
|
||||||
|
|
||||||
var _Cgo_foo$funcaddr unsafe.Pointer
|
|
||||||
|
|
||||||
//export variadic0
|
|
||||||
//go:variadic
|
|
||||||
func _Cgo_variadic0()
|
|
||||||
|
|
||||||
var _Cgo_variadic0$funcaddr unsafe.Pointer
|
|
||||||
|
|
||||||
//export variadic2
|
|
||||||
//go:variadic
|
|
||||||
func _Cgo_variadic2(x _Cgo_int, y _Cgo_int)
|
|
||||||
|
|
||||||
var _Cgo_variadic2$funcaddr unsafe.Pointer
|
|
||||||
|
|
||||||
//export _Cgo_static_173c95a79b6df1980521_staticfunc
|
|
||||||
func _Cgo_staticfunc!symbols.go(x _Cgo_int)
|
|
||||||
|
|
||||||
var _Cgo_staticfunc!symbols.go$funcaddr unsafe.Pointer
|
|
||||||
|
|
||||||
//export notEscapingFunction
|
|
||||||
//go:noescape
|
|
||||||
func _Cgo_notEscapingFunction(a *_Cgo_int)
|
|
||||||
|
|
||||||
var _Cgo_notEscapingFunction$funcaddr unsafe.Pointer
|
|
||||||
//go:extern someValue
|
|
||||||
var _Cgo_someValue _Cgo_int
|
|
||||||
Vendored
+10
-8
@@ -104,18 +104,14 @@ typedef struct {
|
|||||||
unsigned char e : 3;
|
unsigned char e : 3;
|
||||||
// Note that C++ allows bitfields bigger than the underlying type.
|
// Note that C++ allows bitfields bigger than the underlying type.
|
||||||
} bitfield_t;
|
} bitfield_t;
|
||||||
|
|
||||||
|
// Function signatures.
|
||||||
|
void variadic0();
|
||||||
|
void variadic2(int x, int y, ...);
|
||||||
*/
|
*/
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
// // Test that we can refer from this CGo fragment to the fragment above.
|
|
||||||
// typedef myint myint2;
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// aliases
|
|
||||||
_ C.float
|
|
||||||
_ C.double
|
|
||||||
|
|
||||||
// Simple typedefs.
|
// Simple typedefs.
|
||||||
_ C.myint
|
_ C.myint
|
||||||
|
|
||||||
@@ -171,3 +167,9 @@ func accessUnion() {
|
|||||||
var _ *C.int = union2d.unionfield_i()
|
var _ *C.int = union2d.unionfield_i()
|
||||||
var _ *[2]float64 = union2d.unionfield_d()
|
var _ *[2]float64 = union2d.unionfield_d()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test function signatures.
|
||||||
|
func accessFunctions() {
|
||||||
|
C.variadic0()
|
||||||
|
C.variadic2(3, 5)
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+123
-139
@@ -1,166 +1,150 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "syscall"
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
var _ unsafe.Pointer
|
var _ unsafe.Pointer
|
||||||
|
|
||||||
//go:linkname _Cgo_CString runtime.cgo_CString
|
func C.variadic0() //go:variadic
|
||||||
func _Cgo_CString(string) *_Cgo_char
|
func C.variadic2(x C.int, y C.int) //go:variadic
|
||||||
|
var C.variadic0$funcaddr unsafe.Pointer
|
||||||
|
var C.variadic2$funcaddr unsafe.Pointer
|
||||||
|
|
||||||
//go:linkname _Cgo_GoString runtime.cgo_GoString
|
const C.option2A = 20
|
||||||
func _Cgo_GoString(*_Cgo_char) string
|
const C.optionA = 0
|
||||||
|
const C.optionB = 1
|
||||||
|
const C.optionC = -5
|
||||||
|
const C.optionD = -4
|
||||||
|
const C.optionE = 10
|
||||||
|
const C.optionF = 11
|
||||||
|
const C.optionG = 12
|
||||||
|
const C.unused1 = 5
|
||||||
|
|
||||||
//go:linkname _Cgo___GoStringN runtime.cgo_GoStringN
|
type C.int16_t = int16
|
||||||
func _Cgo___GoStringN(*_Cgo_char, uintptr) string
|
type C.int32_t = int32
|
||||||
|
type C.int64_t = int64
|
||||||
func _Cgo_GoStringN(cstr *_Cgo_char, length _Cgo_int) string {
|
type C.int8_t = int8
|
||||||
return _Cgo___GoStringN(cstr, uintptr(length))
|
type C.uint16_t = uint16
|
||||||
|
type C.uint32_t = uint32
|
||||||
|
type C.uint64_t = uint64
|
||||||
|
type C.uint8_t = uint8
|
||||||
|
type C.uintptr_t = uintptr
|
||||||
|
type C.char uint8
|
||||||
|
type C.int int32
|
||||||
|
type C.long int32
|
||||||
|
type C.longlong int64
|
||||||
|
type C.schar int8
|
||||||
|
type C.short int16
|
||||||
|
type C.uchar uint8
|
||||||
|
type C.uint uint32
|
||||||
|
type C.ulong uint32
|
||||||
|
type C.ulonglong uint64
|
||||||
|
type C.ushort uint16
|
||||||
|
type C.bitfield_t = C.struct_4
|
||||||
|
type C.myIntArray = [10]C.int
|
||||||
|
type C.myint = C.int
|
||||||
|
type C.option2_t = C.uint
|
||||||
|
type C.option_t = C.enum_option
|
||||||
|
type C.point2d_t = struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
}
|
}
|
||||||
|
type C.point3d_t = C.struct_point3d
|
||||||
|
type C.struct_nested_t = struct {
|
||||||
|
begin C.point2d_t
|
||||||
|
end C.point2d_t
|
||||||
|
tag C.int
|
||||||
|
|
||||||
//go:linkname _Cgo___GoBytes runtime.cgo_GoBytes
|
coord C.union_2
|
||||||
func _Cgo___GoBytes(unsafe.Pointer, uintptr) []byte
|
|
||||||
|
|
||||||
func _Cgo_GoBytes(ptr unsafe.Pointer, length _Cgo_int) []byte {
|
|
||||||
return _Cgo___GoBytes(ptr, uintptr(length))
|
|
||||||
}
|
}
|
||||||
|
type C.types_t = struct {
|
||||||
//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 (
|
|
||||||
_Cgo_char uint8
|
|
||||||
_Cgo_schar int8
|
|
||||||
_Cgo_uchar uint8
|
|
||||||
_Cgo_short int16
|
|
||||||
_Cgo_ushort uint16
|
|
||||||
_Cgo_int int32
|
|
||||||
_Cgo_uint uint32
|
|
||||||
_Cgo_long int32
|
|
||||||
_Cgo_ulong uint32
|
|
||||||
_Cgo_longlong int64
|
|
||||||
_Cgo_ulonglong uint64
|
|
||||||
)
|
|
||||||
type _Cgo_myint = _Cgo_int
|
|
||||||
type _Cgo_struct_point2d_t struct {
|
|
||||||
x _Cgo_int
|
|
||||||
y _Cgo_int
|
|
||||||
}
|
|
||||||
type _Cgo_point2d_t = _Cgo_struct_point2d_t
|
|
||||||
type _Cgo_struct_point3d struct {
|
|
||||||
x _Cgo_int
|
|
||||||
y _Cgo_int
|
|
||||||
z _Cgo_int
|
|
||||||
}
|
|
||||||
type _Cgo_point3d_t = _Cgo_struct_point3d
|
|
||||||
type _Cgo_struct_type1 struct {
|
|
||||||
_type _Cgo_int
|
|
||||||
__type _Cgo_int
|
|
||||||
___type _Cgo_int
|
|
||||||
}
|
|
||||||
type _Cgo_struct_type2 struct{ _type _Cgo_int }
|
|
||||||
type _Cgo_union_union1_t struct{ i _Cgo_int }
|
|
||||||
type _Cgo_union1_t = _Cgo_union_union1_t
|
|
||||||
type _Cgo_union_union3_t struct{ $union uint64 }
|
|
||||||
|
|
||||||
func (union *_Cgo_union_union3_t) unionfield_i() *_Cgo_int {
|
|
||||||
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo_union_union3_t) unionfield_d() *float64 {
|
|
||||||
return (*float64)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo_union_union3_t) unionfield_s() *_Cgo_short {
|
|
||||||
return (*_Cgo_short)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type _Cgo_union3_t = _Cgo_union_union3_t
|
|
||||||
type _Cgo_union_union2d struct{ $union [2]uint64 }
|
|
||||||
|
|
||||||
func (union *_Cgo_union_union2d) unionfield_i() *_Cgo_int {
|
|
||||||
return (*_Cgo_int)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo_union_union2d) unionfield_d() *[2]float64 {
|
|
||||||
return (*[2]float64)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type _Cgo_union2d_t = _Cgo_union_union2d
|
|
||||||
type _Cgo_union_unionarray_t struct{ arr [10]_Cgo_uchar }
|
|
||||||
type _Cgo_unionarray_t = _Cgo_union_unionarray_t
|
|
||||||
type _Cgo__Ctype_union___0 struct{ $union [3]uint32 }
|
|
||||||
|
|
||||||
func (union *_Cgo__Ctype_union___0) unionfield_area() *_Cgo_point2d_t {
|
|
||||||
return (*_Cgo_point2d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo__Ctype_union___0) unionfield_solid() *_Cgo_point3d_t {
|
|
||||||
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type _Cgo_struct_struct_nested_t struct {
|
|
||||||
begin _Cgo_point2d_t
|
|
||||||
end _Cgo_point2d_t
|
|
||||||
tag _Cgo_int
|
|
||||||
|
|
||||||
coord _Cgo__Ctype_union___0
|
|
||||||
}
|
|
||||||
type _Cgo_struct_nested_t = _Cgo_struct_struct_nested_t
|
|
||||||
type _Cgo_union_union_nested_t struct{ $union [2]uint64 }
|
|
||||||
|
|
||||||
func (union *_Cgo_union_union_nested_t) unionfield_point() *_Cgo_point3d_t {
|
|
||||||
return (*_Cgo_point3d_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo_union_union_nested_t) unionfield_array() *_Cgo_unionarray_t {
|
|
||||||
return (*_Cgo_unionarray_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
func (union *_Cgo_union_union_nested_t) unionfield_thing() *_Cgo_union3_t {
|
|
||||||
return (*_Cgo_union3_t)(unsafe.Pointer(&union.$union))
|
|
||||||
}
|
|
||||||
|
|
||||||
type _Cgo_union_nested_t = _Cgo_union_union_nested_t
|
|
||||||
type _Cgo_enum_option = _Cgo_int
|
|
||||||
type _Cgo_option_t = _Cgo_enum_option
|
|
||||||
type _Cgo_enum_option2_t = _Cgo_uint
|
|
||||||
type _Cgo_option2_t = _Cgo_enum_option2_t
|
|
||||||
type _Cgo_struct_types_t struct {
|
|
||||||
f float32
|
f float32
|
||||||
d float64
|
d float64
|
||||||
ptr *_Cgo_int
|
ptr *C.int
|
||||||
}
|
}
|
||||||
type _Cgo_types_t = _Cgo_struct_types_t
|
type C.union1_t = struct{ i C.int }
|
||||||
type _Cgo_myIntArray = [10]_Cgo_int
|
type C.union2d_t = C.union_union2d
|
||||||
type _Cgo_struct_bitfield_t struct {
|
type C.union3_t = C.union_1
|
||||||
start _Cgo_uchar
|
type C.union_nested_t = C.union_3
|
||||||
__bitfield_1 _Cgo_uchar
|
type C.unionarray_t = struct{ arr [10]C.uchar }
|
||||||
|
|
||||||
d _Cgo_uchar
|
func (s *C.struct_4) bitfield_a() C.uchar {
|
||||||
e _Cgo_uchar
|
return s.__bitfield_1 & 0x1f
|
||||||
}
|
}
|
||||||
|
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
|
||||||
func (s *_Cgo_struct_bitfield_t) bitfield_a() _Cgo_uchar { return s.__bitfield_1 & 0x1f }
|
|
||||||
func (s *_Cgo_struct_bitfield_t) set_bitfield_a(value _Cgo_uchar) {
|
|
||||||
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.struct_4) 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.struct_4) 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.struct_4) 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.struct_4) 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.struct_4 struct {
|
||||||
|
start C.uchar
|
||||||
|
__bitfield_1 C.uchar
|
||||||
|
|
||||||
|
d C.uchar
|
||||||
|
e C.uchar
|
||||||
|
}
|
||||||
|
type C.struct_point3d struct {
|
||||||
|
x C.int
|
||||||
|
y C.int
|
||||||
|
z C.int
|
||||||
|
}
|
||||||
|
type C.struct_type1 struct {
|
||||||
|
_type C.int
|
||||||
|
__type C.int
|
||||||
|
___type C.int
|
||||||
|
}
|
||||||
|
type C.struct_type2 struct{ _type C.int }
|
||||||
|
|
||||||
|
func (union *C.union_1) unionfield_i() *C.int {
|
||||||
|
return (*C.int)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_1) unionfield_d() *float64 {
|
||||||
|
return (*float64)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_1) unionfield_s() *C.short {
|
||||||
|
return (*C.short)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
|
||||||
|
type C.union_1 struct{ $union uint64 }
|
||||||
|
|
||||||
|
func (union *C.union_2) unionfield_area() *C.point2d_t {
|
||||||
|
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_2) unionfield_solid() *C.point3d_t {
|
||||||
|
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
|
||||||
|
type C.union_2 struct{ $union [3]uint32 }
|
||||||
|
|
||||||
|
func (union *C.union_3) unionfield_point() *C.point3d_t {
|
||||||
|
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_3) unionfield_array() *C.unionarray_t {
|
||||||
|
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_3) unionfield_thing() *C.union3_t {
|
||||||
|
return (*C.union3_t)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
|
||||||
|
type C.union_3 struct{ $union [2]uint64 }
|
||||||
|
|
||||||
|
func (union *C.union_union2d) unionfield_i() *C.int {
|
||||||
|
return (*C.int)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
func (union *C.union_union2d) unionfield_d() *[2]float64 {
|
||||||
|
return (*[2]float64)(unsafe.Pointer(&union.$union))
|
||||||
|
}
|
||||||
|
|
||||||
|
type C.union_union2d struct{ $union [2]uint64 }
|
||||||
|
type C.enum_option C.int
|
||||||
|
type C.enum_unused C.uint
|
||||||
|
|||||||
+84
-398
@@ -5,36 +5,23 @@ package compileopts
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
|
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||||
func (c *Config) Triple() string {
|
func (c *Config) Triple() string {
|
||||||
return c.Target.Triple
|
return c.Target.Triple
|
||||||
}
|
}
|
||||||
@@ -45,34 +32,11 @@ 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
|
||||||
// will be returned.
|
// empty list will be returned.
|
||||||
func (c *Config) Features() string {
|
func (c *Config) Features() []string {
|
||||||
if c.Target.Features == "" {
|
return c.Target.Features
|
||||||
return c.Options.LLVMFeatures
|
|
||||||
}
|
|
||||||
if c.Options.LLVMFeatures == "" {
|
|
||||||
return c.Target.Features
|
|
||||||
}
|
|
||||||
return c.Target.Features + "," + c.Options.LLVMFeatures
|
|
||||||
}
|
|
||||||
|
|
||||||
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
|
|
||||||
// string is returned if the target doesn't specify an ABI.
|
|
||||||
func (c *Config) ABI() string {
|
|
||||||
return c.Target.ABI
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
||||||
@@ -83,48 +47,32 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// GOARM will return the GOARM environment variable given to the compiler when
|
|
||||||
// building a program.
|
|
||||||
func (c *Config) GOARM() string {
|
|
||||||
return c.Options.GOARM
|
|
||||||
}
|
|
||||||
|
|
||||||
// GOMIPS will return the GOMIPS environment variable given to the compiler when
|
|
||||||
// building a program.
|
|
||||||
func (c *Config) GOMIPS() string {
|
|
||||||
return c.Options.GOMIPS
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildTags returns the complete list of build tags used during this build.
|
// 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", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
|
||||||
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))
|
||||||
}
|
}
|
||||||
tags = append(tags, c.Options.Tags...)
|
if extraTags := strings.Fields(c.Options.Tags); len(extraTags) != 0 {
|
||||||
|
tags = append(tags, extraTags...)
|
||||||
|
}
|
||||||
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", "extalloc", 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
|
||||||
@@ -132,16 +80,21 @@ func (c *Config) GC() string {
|
|||||||
if c.Target.GC != "" {
|
if c.Target.GC != "" {
|
||||||
return c.Target.GC
|
return c.Target.GC
|
||||||
}
|
}
|
||||||
return "conservative"
|
for _, tag := range c.Target.BuildTags {
|
||||||
|
if tag == "baremetal" || tag == "wasm" {
|
||||||
|
return "conservative"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "extalloc"
|
||||||
}
|
}
|
||||||
|
|
||||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||||
// 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", "extalloc":
|
||||||
for _, tag := range c.BuildTags() {
|
for _, tag := range c.BuildTags() {
|
||||||
if tag == "tinygo.wasm" {
|
if tag == "wasm" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +106,7 @@ func (c *Config) NeedsStackObjects() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scheduler returns the scheduler implementation. Valid values are "none",
|
// Scheduler returns the scheduler implementation. Valid values are "none",
|
||||||
// "asyncify" and "tasks".
|
//"coroutines" and "tasks".
|
||||||
func (c *Config) Scheduler() string {
|
func (c *Config) Scheduler() string {
|
||||||
if c.Options.Scheduler != "" {
|
if c.Options.Scheduler != "" {
|
||||||
return c.Options.Scheduler
|
return c.Options.Scheduler
|
||||||
@@ -161,36 +114,24 @@ func (c *Config) Scheduler() string {
|
|||||||
if c.Target.Scheduler != "" {
|
if c.Target.Scheduler != "" {
|
||||||
return c.Target.Scheduler
|
return c.Target.Scheduler
|
||||||
}
|
}
|
||||||
// Fall back to none.
|
// Fall back to coroutines, which are supported everywhere.
|
||||||
return "none"
|
return "coroutines"
|
||||||
}
|
|
||||||
|
|
||||||
// Serial returns the serial implementation for this build configuration: uart,
|
|
||||||
// usb (meaning USB-CDC), or none.
|
|
||||||
func (c *Config) Serial() string {
|
|
||||||
if c.Options.Serial != "" {
|
|
||||||
return c.Options.Serial
|
|
||||||
}
|
|
||||||
if c.Target.Serial != "" {
|
|
||||||
return c.Target.Serial
|
|
||||||
}
|
|
||||||
return "none"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
// 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.
|
||||||
@@ -198,6 +139,31 @@ func (c *Config) OptLevel() (level string, speedLevel, sizeLevel int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FuncImplementation picks an appropriate func value implementation for the
|
||||||
|
// target.
|
||||||
|
func (c *Config) FuncImplementation() string {
|
||||||
|
switch c.Scheduler() {
|
||||||
|
case "tasks":
|
||||||
|
// A func value is implemented as a pair of pointers:
|
||||||
|
// {context, function pointer}
|
||||||
|
// where the context may be a pointer to a heap-allocated struct
|
||||||
|
// containing the free variables, or it may be undef if the function
|
||||||
|
// being pointed to doesn't need a context. The function pointer is a
|
||||||
|
// regular function pointer.
|
||||||
|
return "doubleword"
|
||||||
|
case "none", "coroutines":
|
||||||
|
// As "doubleword", but with the function pointer replaced by a unique
|
||||||
|
// ID per function signature. Function values are called by using a
|
||||||
|
// switch statement and choosing which function to call.
|
||||||
|
// Pick the switch implementation with the coroutines scheduler, as it
|
||||||
|
// allows the use of blocking inside a function that is used as a func
|
||||||
|
// value.
|
||||||
|
return "switch"
|
||||||
|
default:
|
||||||
|
panic("unknown scheduler type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||||
// instruction).
|
// instruction).
|
||||||
@@ -215,238 +181,24 @@ func (c *Config) AutomaticStackSize() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// StackSize returns the default stack size to be used for goroutines, if the
|
|
||||||
// stack size could not be determined automatically at compile time.
|
|
||||||
func (c *Config) StackSize() uint64 {
|
|
||||||
if c.Options.StackSize != 0 {
|
|
||||||
return c.Options.StackSize
|
|
||||||
}
|
|
||||||
return c.Target.DefaultStackSize
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
|
|
||||||
func (c *Config) MaxStackAlloc() uint64 {
|
|
||||||
if c.StackSize() >= 16*1024 {
|
|
||||||
return 1024
|
|
||||||
}
|
|
||||||
|
|
||||||
return 256
|
|
||||||
}
|
|
||||||
|
|
||||||
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
|
||||||
// calculates and patches in the checksum for the 2nd stage bootloader.
|
|
||||||
func (c *Config) RP2040BootPatch() bool {
|
|
||||||
if c.Target.RP2040BootPatch != nil {
|
|
||||||
return *c.Target.RP2040BootPatch
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a canonicalized architecture name, so we don't have to deal with arm*
|
|
||||||
// vs thumb* vs arm64.
|
|
||||||
func CanonicalArchName(triple string) string {
|
|
||||||
arch := strings.Split(triple, "-")[0]
|
|
||||||
if arch == "arm64" {
|
|
||||||
return "aarch64"
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
|
|
||||||
return "arm"
|
|
||||||
}
|
|
||||||
if arch == "mipsel" {
|
|
||||||
return "mips"
|
|
||||||
}
|
|
||||||
return arch
|
|
||||||
}
|
|
||||||
|
|
||||||
// MuslArchitecture returns the architecture name as used in musl libc. It is
|
|
||||||
// usually the same as the first part of the LLVM triple, but not always.
|
|
||||||
func MuslArchitecture(triple string) string {
|
|
||||||
return CanonicalArchName(triple)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the libc needs to include malloc, for the libcs where this
|
|
||||||
// matters.
|
|
||||||
func (c *Config) LibcNeedsMalloc() bool {
|
|
||||||
if c.GC() == "boehm" && c.Target.Libc == "wasi-libc" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// LibraryPath returns the path to the library build directory. The path will be
|
|
||||||
// a library path in the cache directory (which might not yet be built).
|
|
||||||
func (c *Config) LibraryPath(name string) string {
|
|
||||||
archname := c.Triple()
|
|
||||||
if 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.
|
|
||||||
if v, ok := libVersions[name]; ok {
|
|
||||||
archname += "-v" + strconv.Itoa(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
options := ""
|
|
||||||
if c.LibcNeedsMalloc() {
|
|
||||||
options += "+malloc"
|
|
||||||
}
|
|
||||||
|
|
||||||
// No precompiled library found. Determine the path name that will be used
|
|
||||||
// in the build cache.
|
|
||||||
return filepath.Join(goenv.Get("GOCACHE"), name+options+"-"+archname)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultBinaryExtension returns the default extension for binaries, such as
|
|
||||||
// .exe, .wasm, or no extension (depending on the target).
|
|
||||||
func (c *Config) DefaultBinaryExtension() string {
|
|
||||||
parts := strings.Split(c.Triple(), "-")
|
|
||||||
if parts[0] == "wasm32" {
|
|
||||||
// WebAssembly files always have the .wasm file extension.
|
|
||||||
return ".wasm"
|
|
||||||
}
|
|
||||||
if len(parts) >= 3 && parts[2] == "windows" {
|
|
||||||
// Windows uses .exe.
|
|
||||||
return ".exe"
|
|
||||||
}
|
|
||||||
if len(parts) >= 3 && parts[2] == "unknown" {
|
|
||||||
// There appears to be a convention to use the .elf file extension for
|
|
||||||
// ELF files intended for microcontrollers. I'm not aware of the origin
|
|
||||||
// of this, it's just something that is used by many projects.
|
|
||||||
// I think it's a good tradition, so let's keep it.
|
|
||||||
return ".elf"
|
|
||||||
}
|
|
||||||
// Linux, MacOS, etc, don't use a file extension. Use it as a fallback.
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||||
// preprocessing.
|
// preprocessing.
|
||||||
func (c *Config) CFlags(libclang bool) []string {
|
func (c *Config) CFlags() []string {
|
||||||
var cflags []string
|
var cflags []string
|
||||||
for _, flag := range c.Target.CFlags {
|
for _, flag := range c.Target.CFlags {
|
||||||
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
||||||
}
|
}
|
||||||
resourceDir := goenv.ClangResourceDir(libclang)
|
if c.Target.Libc == "picolibc" {
|
||||||
if resourceDir != "" {
|
root := goenv.Get("TINYGOROOT")
|
||||||
// The resource directory contains the built-in clang headers like
|
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
|
||||||
// stdbool.h, stdint.h, float.h, etc.
|
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
|
||||||
// It is left empty if we're using an external compiler (that already
|
|
||||||
// knows these headers).
|
|
||||||
cflags = append(cflags,
|
|
||||||
"-resource-dir="+resourceDir,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
cflags = append(cflags, c.LibcCFlags()...)
|
if c.Debug() {
|
||||||
// Always emit debug information. It is optionally stripped at link time.
|
cflags = append(cflags, "-g")
|
||||||
cflags = append(cflags, "-gdwarf-4")
|
|
||||||
// Use the same optimization level as TinyGo.
|
|
||||||
cflags = append(cflags, "-O"+c.Options.Opt)
|
|
||||||
// Set the LLVM target triple.
|
|
||||||
cflags = append(cflags, "--target="+c.Triple())
|
|
||||||
// Set the -mcpu (or similar) flag.
|
|
||||||
if c.Target.CPU != "" {
|
|
||||||
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
|
||||||
// x86 prefers the -march flag (-mcpu is deprecated there).
|
|
||||||
cflags = append(cflags, "-march="+c.Target.CPU)
|
|
||||||
} else if strings.HasPrefix(c.Triple(), "avr") {
|
|
||||||
// AVR MCUs use -mmcu instead of -mcpu.
|
|
||||||
cflags = append(cflags, "-mmcu="+c.Target.CPU)
|
|
||||||
} else {
|
|
||||||
// The rest just uses -mcpu.
|
|
||||||
cflags = append(cflags, "-mcpu="+c.Target.CPU)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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.
|
||||||
@@ -461,8 +213,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,9 +234,8 @@ func (c *Config) VerifyIR() bool {
|
|||||||
return c.Options.VerifyIR
|
return c.Options.VerifyIR
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug returns whether debug (DWARF) information should be retained by the
|
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
|
||||||
// linker. By default, debug information is retained, but it can be removed
|
// and similar.
|
||||||
// with the -no-debug flag.
|
|
||||||
func (c *Config) Debug() bool {
|
func (c *Config) Debug() bool {
|
||||||
return c.Options.Debug
|
return c.Options.Debug
|
||||||
}
|
}
|
||||||
@@ -501,13 +250,6 @@ func (c *Config) BinaryFormat(ext string) string {
|
|||||||
return c.Target.BinaryFormat
|
return c.Target.BinaryFormat
|
||||||
}
|
}
|
||||||
return "bin"
|
return "bin"
|
||||||
case ".img":
|
|
||||||
// Image file. Only defined for the ESP32 at the moment, where it is a
|
|
||||||
// full (runnable) image that can be used in the Espressif QEMU fork.
|
|
||||||
if c.Target.BinaryFormat != "" {
|
|
||||||
return c.Target.BinaryFormat + "-img"
|
|
||||||
}
|
|
||||||
return "bin"
|
|
||||||
case ".hex":
|
case ".hex":
|
||||||
// Similar to bin, but includes the start address and is thus usually a
|
// Similar to bin, but includes the start address and is thus usually a
|
||||||
// better format.
|
// better format.
|
||||||
@@ -517,11 +259,6 @@ func (c *Config) BinaryFormat(ext string) string {
|
|||||||
// More information:
|
// More information:
|
||||||
// https://github.com/Microsoft/uf2
|
// https://github.com/Microsoft/uf2
|
||||||
return "uf2"
|
return "uf2"
|
||||||
case ".zip":
|
|
||||||
if c.Target.BinaryFormat != "" {
|
|
||||||
return c.Target.BinaryFormat
|
|
||||||
}
|
|
||||||
return "zip"
|
|
||||||
default:
|
default:
|
||||||
// Use the ELF format for unrecognized file formats.
|
// Use the ELF format for unrecognized file formats.
|
||||||
return "elf"
|
return "elf"
|
||||||
@@ -530,18 +267,15 @@ 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":
|
|
||||||
// The -programmer flag only specifies the flash method.
|
|
||||||
return c.Options.Programmer, ""
|
|
||||||
default:
|
default:
|
||||||
// The -programmer flag specifies something else, assume it specifies
|
// The -programmer flag specifies something else, assume it specifies
|
||||||
// the OpenOCD interface name.
|
// the OpenOCD interface name.
|
||||||
@@ -557,33 +291,26 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
|||||||
if openocdInterface == "" {
|
if openocdInterface == "" {
|
||||||
return nil, errors.New("OpenOCD programmer not set")
|
return nil, errors.New("OpenOCD programmer not set")
|
||||||
}
|
}
|
||||||
if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(openocdInterface) {
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(openocdInterface) {
|
||||||
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
return nil, fmt.Errorf("OpenOCD programmer has an invalid name: %#v", openocdInterface)
|
||||||
}
|
}
|
||||||
if c.Target.OpenOCDTarget == "" {
|
if c.Target.OpenOCDTarget == "" {
|
||||||
return nil, errors.New("OpenOCD chip not set")
|
return nil, errors.New("OpenOCD chip not set")
|
||||||
}
|
}
|
||||||
if !regexp.MustCompile(`^[\p{L}0-9_-]+$`).MatchString(c.Target.OpenOCDTarget) {
|
if !regexp.MustCompile("^[\\p{L}0-9_-]+$").MatchString(c.Target.OpenOCDTarget) {
|
||||||
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
return nil, fmt.Errorf("OpenOCD target has an invalid name: %#v", c.Target.OpenOCDTarget)
|
||||||
}
|
}
|
||||||
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
if c.Target.OpenOCDTransport != "" && c.Target.OpenOCDTransport != "swd" {
|
||||||
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"}
|
||||||
if c.Target.OpenOCDTransport != "" {
|
|
||||||
transport := c.Target.OpenOCDTransport
|
|
||||||
if transport == "swd" {
|
|
||||||
switch openocdInterface {
|
|
||||||
case "stlink-dap":
|
|
||||||
transport = "dapdirect_swd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
args = append(args, "-c", "transport select "+transport)
|
|
||||||
}
|
|
||||||
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
|
||||||
for _, cmd := range c.Target.OpenOCDCommands {
|
for _, cmd := range c.Target.OpenOCDCommands {
|
||||||
args = append(args, "-c", cmd)
|
args = append(args, "-c", cmd)
|
||||||
}
|
}
|
||||||
|
if c.Target.OpenOCDTransport != "" {
|
||||||
|
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||||
|
}
|
||||||
|
args = append(args, "-f", "target/"+c.Target.OpenOCDTarget+".cfg")
|
||||||
return args, nil
|
return args, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -606,57 +333,16 @@ func (c *Config) RelocationModel() string {
|
|||||||
return "static"
|
return "static"
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmulatorName is a shorthand to get the command for this emulator, something
|
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
|
||||||
// like qemu-system-arm or simavr.
|
// the value is overridden by `-wasm-abi` flag if it is provided
|
||||||
func (c *Config) EmulatorName() string {
|
func (c *Config) WasmAbi() string {
|
||||||
parts := strings.SplitN(c.Target.Emulator, " ", 2)
|
if c.Options.WasmAbi != "" {
|
||||||
if len(parts) > 1 {
|
return c.Options.WasmAbi
|
||||||
return parts[0]
|
|
||||||
}
|
}
|
||||||
return ""
|
return c.Target.WasmAbi
|
||||||
}
|
|
||||||
|
|
||||||
// EmulatorFormat returns the binary format for the emulator and the associated
|
|
||||||
// file extension. An empty string means to pass directly whatever the linker
|
|
||||||
// produces directly without conversion (usually ELF format).
|
|
||||||
func (c *Config) EmulatorFormat() (format, fileExt string) {
|
|
||||||
switch {
|
|
||||||
case strings.Contains(c.Target.Emulator, "{img}"):
|
|
||||||
return "img", ".img"
|
|
||||||
default:
|
|
||||||
return "", ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emulator returns a ready-to-run command to run the given binary in an
|
|
||||||
// emulator. Give it the format (returned by EmulatorFormat()) and the path to
|
|
||||||
// the compiled binary.
|
|
||||||
func (c *Config) Emulator(format, binary string) ([]string, error) {
|
|
||||||
parts, err := shlex.Split(c.Target.Emulator)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse emulator command: %w", err)
|
|
||||||
}
|
|
||||||
var emulator []string
|
|
||||||
for _, s := range parts {
|
|
||||||
s = strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT"))
|
|
||||||
// Allow replacement of what's usually /tmp except notably Windows.
|
|
||||||
s = strings.ReplaceAll(s, "{tmpDir}", os.TempDir())
|
|
||||||
s = strings.ReplaceAll(s, "{"+format+"}", binary)
|
|
||||||
emulator = append(emulator, s)
|
|
||||||
}
|
|
||||||
return emulator, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-65
@@ -4,75 +4,41 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
validBuildModeOptions = []string{"default", "c-shared", "wasi-legacy"}
|
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
||||||
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise", "boehm"}
|
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
|
||||||
validSchedulerOptions = []string{"none", "tasks", "asyncify", "threads", "cores"}
|
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||||
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
|
|
||||||
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"}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options contains extra options to give to the compiler. These options are
|
// Options contains extra options to give to the compiler. These options are
|
||||||
// usually passed from the command line, but can also be passed in environment
|
// usually passed from the command line.
|
||||||
// variables for example.
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
GOOS string // environment variable
|
Target string
|
||||||
GOARCH string // environment variable
|
Opt string
|
||||||
GOARM string // environment variable (only used with GOARCH=arm)
|
GC string
|
||||||
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
|
PanicStrategy string
|
||||||
Directory string // working dir, leave it unset to use the current working dir
|
Scheduler string
|
||||||
Target string
|
PrintIR bool
|
||||||
BuildMode string // -buildmode flag
|
DumpSSA bool
|
||||||
Opt string
|
VerifyIR bool
|
||||||
GC string
|
PrintCommands bool
|
||||||
PanicStrategy string
|
Debug bool
|
||||||
Scheduler string
|
PrintSizes string
|
||||||
StackSize uint64 // goroutine stack size (if none could be automatically determined)
|
PrintAllocs *regexp.Regexp // regexp string
|
||||||
Serial string
|
PrintStacks bool
|
||||||
Work bool // -work flag to print temporary build directory
|
Tags string
|
||||||
InterpTimeout time.Duration
|
WasmAbi string
|
||||||
InterpMaxLoopIterations int
|
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||||
PrintIR bool
|
TestConfig TestConfig
|
||||||
DumpSSA bool
|
Programmer string
|
||||||
VerifyIR bool
|
|
||||||
SkipDWARF bool
|
|
||||||
PrintCommands func(cmd string, args ...string) `json:"-"`
|
|
||||||
Semaphore chan struct{} `json:"-"` // -p flag controls cap
|
|
||||||
Debug bool
|
|
||||||
Nobounds bool
|
|
||||||
PrintSizes string
|
|
||||||
PrintAllocs *regexp.Regexp // regexp string
|
|
||||||
PrintStacks bool
|
|
||||||
Tags []string
|
|
||||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
|
||||||
TestConfig TestConfig
|
|
||||||
Programmer string
|
|
||||||
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 := isInArray(validBuildModeOptions, o.BuildMode)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
|
|
||||||
o.BuildMode,
|
|
||||||
strings.Join(validBuildModeOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if o.GC != "" {
|
if o.GC != "" {
|
||||||
valid := isInArray(validGCOptions, o.GC)
|
valid := isInArray(validGCOptions, o.GC)
|
||||||
if !valid {
|
if !valid {
|
||||||
@@ -91,15 +57,6 @@ func (o *Options) Verify() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if o.Serial != "" {
|
|
||||||
valid := isInArray(validSerialOptions, o.Serial)
|
|
||||||
if !valid {
|
|
||||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
|
||||||
o.Serial,
|
|
||||||
strings.Join(validSerialOptions, ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.PrintSizes != "" {
|
if o.PrintSizes != "" {
|
||||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||||
if !valid {
|
if !valid {
|
||||||
|
|||||||
@@ -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, extalloc, 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, coroutines`)
|
||||||
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 {
|
||||||
@@ -43,15 +43,15 @@ func TestVerifyOptions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GCOptionConservative",
|
name: "GCOptionExtalloc",
|
||||||
opts: compileopts.Options{
|
opts: compileopts.Options{
|
||||||
GC: "conservative",
|
GC: "extalloc",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "GCOptionCustom",
|
name: "GCOptionConservative",
|
||||||
opts: compileopts.Options{
|
opts: compileopts.Options{
|
||||||
GC: "custom",
|
GC: "conservative",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -73,6 +73,12 @@ func TestVerifyOptions(t *testing.T) {
|
|||||||
Scheduler: "tasks",
|
Scheduler: "tasks",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "SchedulerOptionCoroutines",
|
||||||
|
opts: compileopts.Options{
|
||||||
|
Scheduler: "coroutines",
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "InvalidPrintSizeOption",
|
name: "InvalidPrintSizeOption",
|
||||||
opts: compileopts.Options{
|
opts: compileopts.Options{
|
||||||
|
|||||||
+151
-436
@@ -5,7 +5,6 @@ package compileopts
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -23,58 +22,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"`
|
Linker string `json:"linker"`
|
||||||
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified)
|
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||||
GC string `json:"gc,omitempty"`
|
Libc string `json:"libc"`
|
||||||
Scheduler string `json:"scheduler,omitempty"`
|
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
|
||||||
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
|
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
|
||||||
Linker string `json:"linker,omitempty"`
|
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.
|
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
||||||
CFlags []string `json:"cflags,omitempty"`
|
FlashCommand string `json:"flash-command"`
|
||||||
LDFlags []string `json:"ldflags,omitempty"`
|
GDB []string `json:"gdb"`
|
||||||
LinkerScript string `json:"linkerscript,omitempty"`
|
PortReset string `json:"flash-1200-bps-reset"`
|
||||||
ExtraFiles []string `json:"extra-files,omitempty"`
|
FlashMethod string `json:"flash-method"`
|
||||||
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum
|
FlashVolume string `json:"msd-volume-name"`
|
||||||
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear.
|
FlashFilename string `json:"msd-firmware-name"`
|
||||||
Emulator string `json:"emulator,omitempty"`
|
UF2FamilyID string `json:"uf2-family-id"`
|
||||||
FlashCommand string `json:"flash-command,omitempty"`
|
BinaryFormat string `json:"binary-format"`
|
||||||
GDB []string `json:"gdb,omitempty"`
|
OpenOCDInterface string `json:"openocd-interface"`
|
||||||
PortReset string `json:"flash-1200-bps-reset,omitempty"`
|
OpenOCDTarget string `json:"openocd-target"`
|
||||||
SerialPort []string `json:"serial-port,omitempty"` // serial port IDs in the form "vid:pid"
|
OpenOCDTransport string `json:"openocd-transport"`
|
||||||
FlashMethod string `json:"flash-method,omitempty"`
|
OpenOCDCommands []string `json:"openocd-commands"`
|
||||||
FlashVolume []string `json:"msd-volume-name,omitempty"`
|
JLinkDevice string `json:"jlink-device"`
|
||||||
FlashFilename string `json:"msd-firmware-name,omitempty"`
|
CodeModel string `json:"code-model"`
|
||||||
UF2FamilyID string `json:"uf2-family-id,omitempty"`
|
RelocationModel string `json:"relocation-model"`
|
||||||
BinaryFormat string `json:"binary-format,omitempty"`
|
WasmAbi string `json:"wasm-abi"`
|
||||||
OpenOCDInterface string `json:"openocd-interface,omitempty"`
|
|
||||||
OpenOCDTarget string `json:"openocd-target,omitempty"`
|
|
||||||
OpenOCDTransport string `json:"openocd-transport,omitempty"`
|
|
||||||
OpenOCDCommands []string `json:"openocd-commands,omitempty"`
|
|
||||||
OpenOCDVerify *bool `json:"openocd-verify,omitempty"` // enable verify when flashing with openocd
|
|
||||||
JLinkDevice string `json:"jlink-device,omitempty"`
|
|
||||||
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
|
|
||||||
ADBPushRemote string `json:"adb-push-remote,omitempty"`
|
|
||||||
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
|
|
||||||
CodeModel string `json:"code-model,omitempty"`
|
|
||||||
RelocationModel string `json:"relocation-model,omitempty"`
|
|
||||||
WITPackage string `json:"wit-package,omitempty"`
|
|
||||||
WITWorld string `json:"wit-world,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
func (spec *TargetSpec) overrideProperties(child *TargetSpec) error {
|
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
||||||
specType := reflect.TypeOf(spec).Elem()
|
specType := reflect.TypeOf(spec).Elem()
|
||||||
specValue := reflect.ValueOf(spec).Elem()
|
specValue := reflect.ValueOf(spec).Elem()
|
||||||
childValue := reflect.ValueOf(child).Elem()
|
childValue := reflect.ValueOf(child).Elem()
|
||||||
@@ -93,30 +79,27 @@ 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)
|
||||||
}
|
}
|
||||||
case reflect.Slice: // for slices, append the field and check for duplicates
|
case reflect.Slice: // for slices...
|
||||||
dst.Set(reflect.AppendSlice(dst, src))
|
if src.Len() > 0 { // ... if not empty ...
|
||||||
for i := 0; i < dst.Len(); i++ {
|
switch tag := field.Tag.Get("override"); tag {
|
||||||
v := dst.Index(i).String()
|
case "copy":
|
||||||
for j := i + 1; j < dst.Len(); j++ {
|
// copy the field of child to spec
|
||||||
w := dst.Index(j).String()
|
dst.Set(src)
|
||||||
if v == w {
|
case "append", "":
|
||||||
return fmt.Errorf("duplicate value '%s' in field %s", v, field.Name)
|
// or append the field of child to spec
|
||||||
}
|
dst.Set(reflect.AppendSlice(src, dst))
|
||||||
|
default:
|
||||||
|
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown field type: %s", kind)
|
panic("unknown field type : " + kind.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// load reads a target specification from the JSON in the given io.Reader. It
|
// load reads a target specification from the JSON in the given io.Reader. It
|
||||||
@@ -131,10 +114,10 @@ func (spec *TargetSpec) load(r io.Reader) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// loadFromGivenStr loads the TargetSpec from the given string that could be:
|
// loadFromGivenStr loads the TargetSpec from the given string that could be:
|
||||||
// - targets/ directory inside the compiler sources
|
// - targets/ directory inside the compiler sources
|
||||||
// - a relative or absolute path to custom (project specific) target specification .json file;
|
// - a relative or absolute path to custom (project specific) target specification .json file;
|
||||||
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
|
// the Inherits[] could contain the files from target folder (ex. stm32f4disco)
|
||||||
// as well as path to custom files (ex. myAwesomeProject.json)
|
// as well as path to custom files (ex. myAwesomeProject.json)
|
||||||
func (spec *TargetSpec) loadFromGivenStr(str string) error {
|
func (spec *TargetSpec) loadFromGivenStr(str string) error {
|
||||||
path := ""
|
path := ""
|
||||||
if strings.HasSuffix(str, ".json") {
|
if strings.HasSuffix(str, ".json") {
|
||||||
@@ -152,11 +135,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 {
|
||||||
@@ -169,396 +147,133 @@ func (spec *TargetSpec) resolveInherits() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = newSpec.overrideProperties(subtarget)
|
newSpec.overrideProperties(subtarget)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When all properties are loaded, make sure they are properly inherited.
|
// When all properties are loaded, make sure they are properly inherited.
|
||||||
err := newSpec.overrideProperties(spec)
|
newSpec.overrideProperties(spec)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*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(target string) (*TargetSpec, error) {
|
||||||
if options.Target == "" && options.GOARCH == "wasm" {
|
if target == "" {
|
||||||
// Set a specific target if we're building from a known GOOS/GOARCH
|
// Configure based on GOOS/GOARCH environment variables (falling back to
|
||||||
// combination that is defined in a target JSON file.
|
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
|
||||||
switch options.GOOS {
|
goos := goenv.Get("GOOS")
|
||||||
case "js":
|
goarch := goenv.Get("GOARCH")
|
||||||
options.Target = "wasm"
|
llvmos := goos
|
||||||
case "wasip1":
|
llvmarch := map[string]string{
|
||||||
options.Target = "wasip1"
|
"386": "i386",
|
||||||
case "wasip2":
|
"amd64": "x86_64",
|
||||||
options.Target = "wasip2"
|
"arm64": "aarch64",
|
||||||
default:
|
}[goarch]
|
||||||
return nil, errors.New("GOARCH=wasm but GOOS is not set correctly. Please set GOOS to js, wasip1, or wasip2.")
|
if llvmarch == "" {
|
||||||
|
llvmarch = goarch
|
||||||
}
|
}
|
||||||
}
|
target = llvmarch + "--" + llvmos
|
||||||
|
if goarch == "arm" {
|
||||||
if options.Target == "" {
|
target += "-gnueabihf"
|
||||||
return defaultTarget(options)
|
}
|
||||||
|
return defaultTarget(goos, 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.
|
||||||
// Arduino).
|
// Arduino).
|
||||||
spec := &TargetSpec{}
|
spec := &TargetSpec{}
|
||||||
err := spec.loadFromGivenStr(options.Target)
|
err := spec.loadFromGivenStr(target)
|
||||||
if err != nil {
|
if err == nil {
|
||||||
|
// Successfully loaded this target from a built-in .json file. Make sure
|
||||||
|
// it includes all parents as specified in the "inherits" key.
|
||||||
|
err = spec.resolveInherits()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return spec, nil
|
||||||
|
} else if !os.IsNotExist(err) {
|
||||||
|
// Expected a 'file not found' error, got something else. Report it as
|
||||||
|
// an error.
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
// Load target from given triple, ignore GOOS/GOARCH environment
|
||||||
|
// variables.
|
||||||
|
tripleSplit := strings.Split(target, "-")
|
||||||
|
if len(tripleSplit) < 3 {
|
||||||
|
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
|
||||||
|
}
|
||||||
|
if tripleSplit[0] == "arm" {
|
||||||
|
// LLVM and Clang have a different idea of what "arm" means, so
|
||||||
|
// upgrade to a slightly more modern ARM. In fact, when you pass
|
||||||
|
// --target=arm--linux-gnueabihf to Clang, it will convert that
|
||||||
|
// internally to armv7-unknown-linux-gnueabihf. Changing the
|
||||||
|
// architecture to armv7 will keep things consistent.
|
||||||
|
tripleSplit[0] = "armv7"
|
||||||
|
}
|
||||||
|
goos := tripleSplit[2]
|
||||||
|
if strings.HasPrefix(goos, "darwin") {
|
||||||
|
goos = "darwin"
|
||||||
|
}
|
||||||
|
goarch := map[string]string{ // map from LLVM arch to Go arch
|
||||||
|
"i386": "386",
|
||||||
|
"i686": "386",
|
||||||
|
"x86_64": "amd64",
|
||||||
|
"aarch64": "arm64",
|
||||||
|
"armv7": "arm",
|
||||||
|
}[tripleSplit[0]]
|
||||||
|
if goarch == "" {
|
||||||
|
goarch = tripleSplit[0]
|
||||||
|
}
|
||||||
|
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
|
||||||
}
|
}
|
||||||
// Successfully loaded this target from a built-in .json file. Make sure
|
|
||||||
// it includes all parents as specified in the "inherits" key.
|
|
||||||
err = spec.resolveInherits()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s : %w", options.Target, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if spec.Scheduler == "asyncify" {
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S")
|
|
||||||
}
|
|
||||||
|
|
||||||
return spec, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTargetSpecs retrieves target specifications from the TINYGOROOT targets
|
// WindowsBuildNotSupportedErr is being thrown, when goos is windows and no target has been specified.
|
||||||
// directory. Only valid target JSON files are considered, and the function
|
var WindowsBuildNotSupportedErr = errors.New("Building Windows binaries is currently not supported. Try specifying a different target")
|
||||||
// returns a map of target names to their respective TargetSpec.
|
|
||||||
func GetTargetSpecs() (map[string]*TargetSpec, error) {
|
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||||
dir := filepath.Join(goenv.Get("TINYGOROOT"), "targets")
|
if goos == "windows" {
|
||||||
entries, err := os.ReadDir(dir)
|
return nil, WindowsBuildNotSupportedErr
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not list targets: %w", err)
|
|
||||||
}
|
}
|
||||||
|
// No target spec available. Use the default one, useful on most systems
|
||||||
maps := map[string]*TargetSpec{}
|
// with a regular OS.
|
||||||
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,
|
||||||
Linker: "cc",
|
BuildTags: []string{goos, goarch},
|
||||||
DefaultStackSize: 1024 * 64, // 64kB
|
Linker: "cc",
|
||||||
GDB: []string{"gdb"},
|
CFlags: []string{"--target=" + triple},
|
||||||
PortReset: "false",
|
GDB: []string{"gdb"},
|
||||||
|
PortReset: "false",
|
||||||
}
|
}
|
||||||
|
if goos == "darwin" {
|
||||||
// Configure target based on GOARCH.
|
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||||
var llvmarch string
|
} else {
|
||||||
switch options.GOARCH {
|
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
||||||
case "386":
|
|
||||||
llvmarch = "i386"
|
|
||||||
spec.CPU = "pentium4"
|
|
||||||
spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
||||||
case "amd64":
|
|
||||||
llvmarch = "x86_64"
|
|
||||||
spec.CPU = "x86-64"
|
|
||||||
spec.Features = "+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
|
||||||
case "arm":
|
|
||||||
spec.CPU = "generic"
|
|
||||||
spec.CFlags = append(spec.CFlags, "-fno-unwind-tables", "-fno-asynchronous-unwind-tables")
|
|
||||||
subarch := strings.Split(options.GOARM, ",")
|
|
||||||
if len(subarch) > 2 {
|
|
||||||
return nil, fmt.Errorf("invalid GOARM=%s, must be of form <num>,[hardfloat|softfloat]", options.GOARM)
|
|
||||||
}
|
|
||||||
archLevel := subarch[0]
|
|
||||||
var fpu string
|
|
||||||
if len(subarch) >= 2 {
|
|
||||||
fpu = subarch[1]
|
|
||||||
} else {
|
|
||||||
// Pick the default fpu value: softfloat for armv5 and hardfloat
|
|
||||||
// above that.
|
|
||||||
if archLevel == "5" {
|
|
||||||
fpu = "softfloat"
|
|
||||||
} else {
|
|
||||||
fpu = "hardfloat"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch fpu {
|
|
||||||
case "softfloat":
|
|
||||||
spec.CFlags = append(spec.CFlags, "-msoft-float")
|
|
||||||
spec.SoftFloat = true
|
|
||||||
case "hardfloat":
|
|
||||||
// Hardware floating point support is the default everywhere except
|
|
||||||
// on ARMv5 where it needs to be enabled explicitly.
|
|
||||||
if archLevel == "5" {
|
|
||||||
spec.CFlags = append(spec.CFlags, "-mfpu=vfpv2")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid extension GOARM=%s, must be softfloat or hardfloat", options.GOARM)
|
|
||||||
}
|
|
||||||
switch archLevel {
|
|
||||||
case "5":
|
|
||||||
llvmarch = "armv5"
|
|
||||||
if spec.SoftFloat {
|
|
||||||
spec.Features = "+armv5t,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
} else {
|
|
||||||
spec.Features = "+armv5t,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
}
|
|
||||||
case "6":
|
|
||||||
llvmarch = "armv6"
|
|
||||||
if spec.SoftFloat {
|
|
||||||
spec.Features = "+armv6,+dsp,+soft-float,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
} else {
|
|
||||||
spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
}
|
|
||||||
case "7":
|
|
||||||
llvmarch = "armv7"
|
|
||||||
if spec.SoftFloat {
|
|
||||||
spec.Features = "+armv7-a,+dsp,+soft-float,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
} else {
|
|
||||||
spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid GOARM=%s, must be of form <num>,[hardfloat|softfloat] where num is 5, 6, or 7", options.GOARM)
|
|
||||||
}
|
|
||||||
case "arm64":
|
|
||||||
spec.CPU = "generic"
|
|
||||||
llvmarch = "aarch64"
|
|
||||||
if options.GOOS == "darwin" {
|
|
||||||
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a"
|
|
||||||
// Looks like Apple prefers to call this architecture ARM64
|
|
||||||
// instead of AArch64.
|
|
||||||
llvmarch = "arm64"
|
|
||||||
} else if options.GOOS == "windows" {
|
|
||||||
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv"
|
|
||||||
} else { // linux
|
|
||||||
spec.Features = "+ete,+fp-armv8,+neon,+trbe,+v8a,-fmv,-outline-atomics"
|
|
||||||
}
|
|
||||||
case "mips", "mipsle":
|
|
||||||
spec.CPU = "mips32"
|
|
||||||
spec.CFlags = append(spec.CFlags, "-fno-pic")
|
|
||||||
if options.GOARCH == "mips" {
|
|
||||||
llvmarch = "mips" // big endian
|
|
||||||
} else {
|
|
||||||
llvmarch = "mipsel" // little endian
|
|
||||||
}
|
|
||||||
switch options.GOMIPS {
|
|
||||||
case "hardfloat":
|
|
||||||
spec.Features = "+fpxx,+mips32,+nooddspreg,-noabicalls"
|
|
||||||
case "softfloat":
|
|
||||||
spec.SoftFloat = true
|
|
||||||
spec.Features = "+mips32,+soft-float,-noabicalls"
|
|
||||||
spec.CFlags = append(spec.CFlags, "-msoft-float")
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid GOMIPS=%s: must be hardfloat or softfloat", options.GOMIPS)
|
|
||||||
}
|
|
||||||
case "wasm":
|
|
||||||
return nil, fmt.Errorf("GOARCH=wasm but GOOS is unset. Please set GOOS to js, wasip1, or wasip2.")
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown GOARCH=%s", options.GOARCH)
|
|
||||||
}
|
}
|
||||||
|
if goarch != "wasm" {
|
||||||
// Configure target based on GOOS.
|
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
|
||||||
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.Libc = "darwin-libSystem"
|
|
||||||
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer to
|
|
||||||
// iOS!
|
|
||||||
llvmos = "macosx" + platformVersion
|
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
|
||||||
"-flavor", "darwin",
|
|
||||||
"-dead_strip",
|
|
||||||
"-arch", llvmarch,
|
|
||||||
"-platform_version", "macos", platformVersion, platformVersion,
|
|
||||||
)
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
|
||||||
"src/internal/futex/futex_darwin.c",
|
|
||||||
"src/internal/task/task_threads.c",
|
|
||||||
"src/runtime/os_darwin.c",
|
|
||||||
"src/runtime/runtime_unix.c",
|
|
||||||
"src/runtime/signal.c")
|
|
||||||
case "linux":
|
|
||||||
spec.GC = "boehm"
|
|
||||||
spec.Scheduler = "threads"
|
|
||||||
spec.Linker = "ld.lld"
|
|
||||||
spec.RTLib = "compiler-rt"
|
|
||||||
spec.Libc = "musl"
|
|
||||||
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
|
|
||||||
if options.GOARCH == "arm64" {
|
|
||||||
// Disable outline atomics. For details, see:
|
|
||||||
// https://cpufun.substack.com/p/atomics-in-aarch64
|
|
||||||
// A better way would be to fully support outline atomics, which
|
|
||||||
// makes atomics slightly more efficient on systems with many cores.
|
|
||||||
// But the instructions are only supported on newer aarch64 CPUs, so
|
|
||||||
// this feature is normally put in a system library which does
|
|
||||||
// feature detection for you.
|
|
||||||
// We take the lazy way out and simply disable this feature, instead
|
|
||||||
// of enabling it in compiler-rt (which is a bit more complicated).
|
|
||||||
// We don't really need this feature anyway as we don't even support
|
|
||||||
// proper threading.
|
|
||||||
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
|
|
||||||
}
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
|
||||||
"src/internal/futex/futex_linux.c",
|
|
||||||
"src/internal/task/task_threads.c",
|
|
||||||
"src/runtime/runtime_unix.c",
|
|
||||||
"src/runtime/signal.c")
|
|
||||||
case "windows":
|
|
||||||
spec.GC = "boehm"
|
|
||||||
spec.Scheduler = "tasks"
|
|
||||||
spec.Linker = "ld.lld"
|
|
||||||
spec.Libc = "mingw-w64"
|
|
||||||
switch options.GOARCH {
|
|
||||||
case "386":
|
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
|
||||||
"-m", "i386pe",
|
|
||||||
"--major-os-version", "4",
|
|
||||||
"--major-subsystem-version", "4",
|
|
||||||
)
|
|
||||||
// __udivdi3 is not present in ucrt it seems.
|
|
||||||
spec.RTLib = "compiler-rt"
|
|
||||||
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",
|
|
||||||
)
|
|
||||||
case "arm64":
|
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
|
||||||
"-m", "arm64pe",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
spec.LDFlags = append(spec.LDFlags,
|
|
||||||
"-Bdynamic",
|
|
||||||
"--gc-sections",
|
|
||||||
"--no-insert-timestamp",
|
|
||||||
"--no-dynamicbase",
|
|
||||||
)
|
|
||||||
case "wasm", "wasip1", "wasip2":
|
|
||||||
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unknown GOOS=%s", options.GOOS)
|
|
||||||
}
|
}
|
||||||
|
if goarch != runtime.GOARCH {
|
||||||
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 := ""
|
|
||||||
if options.GOOS == "windows" && options.GOARCH == "amd64" {
|
|
||||||
// Windows uses a different calling convention on amd64 from other
|
|
||||||
// operating systems so we need separate assembly files.
|
|
||||||
suffix = "_windows"
|
|
||||||
}
|
|
||||||
asmGoarch := options.GOARCH
|
|
||||||
if options.GOARCH == "mips" || options.GOARCH == "mipsle" {
|
|
||||||
asmGoarch = "mipsx"
|
|
||||||
}
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/asm_"+asmGoarch+suffix+".S")
|
|
||||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+asmGoarch+suffix+".S")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 goarch == "arm" && goos == "linux" {
|
||||||
switch options.GOARCH {
|
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
||||||
case "386":
|
spec.Linker = "arm-linux-gnueabihf-gcc"
|
||||||
// amd64 can _usually_ run 32-bit programs, so skip the emulator in that case.
|
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
||||||
if runtime.GOARCH != "amd64" {
|
}
|
||||||
spec.Emulator = "qemu-i386 {}"
|
if goarch == "arm64" && goos == "linux" {
|
||||||
}
|
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
||||||
case "amd64":
|
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||||
spec.Emulator = "qemu-x86_64 {}"
|
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||||
case "arm":
|
}
|
||||||
spec.Emulator = "qemu-arm {}"
|
if goarch == "386" && runtime.GOARCH == "amd64" {
|
||||||
case "arm64":
|
spec.CFlags = append(spec.CFlags, "-m32")
|
||||||
spec.Emulator = "qemu-aarch64 {}"
|
spec.LDFlags = append(spec.LDFlags, "-m32")
|
||||||
case "mips":
|
|
||||||
spec.Emulator = "qemu-mips {}"
|
|
||||||
case "mipsle":
|
|
||||||
spec.Emulator = "qemu-mipsel {}"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if options.GOOS != runtime.GOOS {
|
|
||||||
if options.GOOS == "windows" {
|
|
||||||
spec.Emulator = "wine {}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &spec, nil
|
return &spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
-40
@@ -1,66 +1,34 @@
|
|||||||
package compileopts
|
package compileopts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io/fs"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadTarget(t *testing.T) {
|
func TestLoadTarget(t *testing.T) {
|
||||||
_, err := LoadTarget(&Options{Target: "arduino"})
|
_, err := LoadTarget("arduino")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("LoadTarget test failed:", err)
|
t.Error("LoadTarget test failed:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = LoadTarget(&Options{Target: "notexist"})
|
_, err = LoadTarget("notexist")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("LoadTarget should have failed with non existing target")
|
t.Error("LoadTarget should have failed with non existing target")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
|
||||||
t.Error("LoadTarget failed for wrong reason:", err)
|
t.Error("LoadTarget failed for wrong reason:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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{
|
||||||
GOOS: "baseGoos",
|
GOOS: "baseGoos",
|
||||||
CPU: "baseCpu",
|
CPU: "baseCpu",
|
||||||
CFlags: []string{"-base-foo", "-base-bar"},
|
Features: []string{"bf1", "bf2"},
|
||||||
BuildTags: []string{"bt1", "bt2"},
|
BuildTags: []string{"bt1", "bt2"},
|
||||||
|
Emulator: []string{"be1", "be2"},
|
||||||
DefaultStackSize: 42,
|
DefaultStackSize: 42,
|
||||||
AutoStackSize: &baseAutoStackSize,
|
AutoStackSize: &baseAutoStackSize,
|
||||||
}
|
}
|
||||||
@@ -68,7 +36,8 @@ func TestOverrideProperties(t *testing.T) {
|
|||||||
child := &TargetSpec{
|
child := &TargetSpec{
|
||||||
GOOS: "",
|
GOOS: "",
|
||||||
CPU: "chlidCpu",
|
CPU: "chlidCpu",
|
||||||
CFlags: []string{"-child-foo", "-child-bar"},
|
Features: []string{"cf1", "cf2"},
|
||||||
|
Emulator: []string{"ce1", "ce2"},
|
||||||
AutoStackSize: &childAutoStackSize,
|
AutoStackSize: &childAutoStackSize,
|
||||||
DefaultStackSize: 64,
|
DefaultStackSize: 64,
|
||||||
}
|
}
|
||||||
@@ -81,12 +50,15 @@ func TestOverrideProperties(t *testing.T) {
|
|||||||
if base.CPU != "chlidCpu" {
|
if base.CPU != "chlidCpu" {
|
||||||
t.Errorf("Overriding failed : got %v", base.CPU)
|
t.Errorf("Overriding failed : got %v", base.CPU)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(base.CFlags, []string{"-base-foo", "-base-bar", "-child-foo", "-child-bar"}) {
|
if !reflect.DeepEqual(base.Features, []string{"cf1", "cf2", "bf1", "bf2"}) {
|
||||||
t.Errorf("Overriding failed : got %v", base.CFlags)
|
t.Errorf("Overriding failed : got %v", base.Features)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
|
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
|
||||||
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) {
|
||||||
|
t.Errorf("Overriding failed : got %v", base.Emulator)
|
||||||
|
}
|
||||||
if *base.AutoStackSize != false {
|
if *base.AutoStackSize != false {
|
||||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
package compiler
|
|
||||||
|
|
||||||
// This file defines alias functions for functions that are normally defined in
|
|
||||||
// Go assembly.
|
|
||||||
//
|
|
||||||
// The Go toolchain defines many performance critical functions in assembly
|
|
||||||
// instead of plain Go. This is a problem for TinyGo as it currently (as of
|
|
||||||
// august 2021) is not able to compile these assembly files and even if it
|
|
||||||
// could, it would not be able to make use of them for many targets that are
|
|
||||||
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
|
|
||||||
// functions are aliased to their generic Go implementation.
|
|
||||||
// This results in slower than possible implementations, but at least they are
|
|
||||||
// usable.
|
|
||||||
|
|
||||||
import "tinygo.org/x/go-llvm"
|
|
||||||
|
|
||||||
var stdlibAliases = map[string]string{
|
|
||||||
// crypto packages
|
|
||||||
"crypto/ed25519/internal/edwards25519/field.feMul": "crypto/ed25519/internal/edwards25519/field.feMulGeneric",
|
|
||||||
"crypto/internal/edwards25519/field.feSquare": "crypto/ed25519/internal/edwards25519/field.feSquareGeneric",
|
|
||||||
"crypto/md5.block": "crypto/md5.blockGeneric",
|
|
||||||
"crypto/sha1.block": "crypto/sha1.blockGeneric",
|
|
||||||
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
|
|
||||||
"crypto/sha256.block": "crypto/sha256.blockGeneric",
|
|
||||||
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
|
|
||||||
"internal/chacha8rand.block": "internal/chacha8rand.block_generic",
|
|
||||||
|
|
||||||
// AES
|
|
||||||
"crypto/aes.decryptBlockAsm": "crypto/aes.decryptBlock",
|
|
||||||
"crypto/aes.encryptBlockAsm": "crypto/aes.encryptBlock",
|
|
||||||
|
|
||||||
// math package
|
|
||||||
"math.archHypot": "math.hypot",
|
|
||||||
"math.archMax": "math.max",
|
|
||||||
"math.archMin": "math.min",
|
|
||||||
"math.archModf": "math.modf",
|
|
||||||
}
|
|
||||||
|
|
||||||
// createAlias implements the function (in the builder) as a call to the alias
|
|
||||||
// function.
|
|
||||||
func (b *builder) createAlias(alias llvm.Value) {
|
|
||||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
|
||||||
b.llvmFn.SetUnnamedAddr(true)
|
|
||||||
|
|
||||||
if b.Debug {
|
|
||||||
if b.fn.Syntax() != nil {
|
|
||||||
// Create debug info file if present.
|
|
||||||
b.difunc = b.attachDebugInfo(b.fn)
|
|
||||||
}
|
|
||||||
pos := b.program.Fset.Position(b.fn.Pos())
|
|
||||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
|
||||||
}
|
|
||||||
entryBlock := b.ctx.AddBasicBlock(b.llvmFn, "entry")
|
|
||||||
b.SetInsertPointAtEnd(entryBlock)
|
|
||||||
if b.llvmFn.Type() != alias.Type() {
|
|
||||||
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
|
|
||||||
b.CreateUnreachable()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
result := b.CreateCall(alias.GlobalValueType(), alias, b.llvmFn.Params(), "")
|
|
||||||
if result.Type().TypeKind() == llvm.VoidTypeKind {
|
|
||||||
b.CreateRetVoid()
|
|
||||||
} else {
|
|
||||||
b.CreateRet(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+49
-93
@@ -15,16 +15,22 @@ import (
|
|||||||
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
|
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||||
// slice. This is required by the Go language spec: an index out of bounds must
|
// slice. This is required by the Go language spec: an index out of bounds must
|
||||||
// cause a panic.
|
// cause a panic.
|
||||||
// The caller should make sure that index is at least as big as arrayLen.
|
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
||||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
|
|
||||||
if b.info.nobounds {
|
if b.info.nobounds {
|
||||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
// checking.
|
// checking.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extend arrayLen if it's too small.
|
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||||
if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||||
|
// correctly extend that type.
|
||||||
|
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||||
|
index = b.CreateZExt(index, arrayLen.Type(), "")
|
||||||
|
} else {
|
||||||
|
index = b.CreateSExt(index, arrayLen.Type(), "")
|
||||||
|
}
|
||||||
|
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||||
// The index is bigger than the array length type, so extend it.
|
// The index is bigger than the array length type, so extend it.
|
||||||
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
|
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
|
||||||
}
|
}
|
||||||
@@ -64,9 +70,27 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extend low and high to be the same size as capacity.
|
// Extend low and high to be the same size as capacity.
|
||||||
low = b.extendInteger(low, lowType, capacityType)
|
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||||
high = b.extendInteger(high, highType, capacityType)
|
if lowType.Info()&types.IsUnsigned != 0 {
|
||||||
max = b.extendInteger(max, maxType, capacityType)
|
low = b.CreateZExt(low, capacityType, "")
|
||||||
|
} else {
|
||||||
|
low = b.CreateSExt(low, capacityType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||||
|
if highType.Info()&types.IsUnsigned != 0 {
|
||||||
|
high = b.CreateZExt(high, capacityType, "")
|
||||||
|
} else {
|
||||||
|
high = b.CreateSExt(high, capacityType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||||
|
if maxType.Info()&types.IsUnsigned != 0 {
|
||||||
|
max = b.CreateZExt(max, capacityType, "")
|
||||||
|
} else {
|
||||||
|
max = b.CreateSExt(max, capacityType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Now do the bounds check: low > high || high > capacity
|
// Now do the bounds check: low > high || high > capacity
|
||||||
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||||
@@ -77,50 +101,6 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
|||||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
|
||||||
// conversions. This conversion was added in Go 1.17. For details, see:
|
|
||||||
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
|
|
||||||
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
|
|
||||||
// From the spec:
|
|
||||||
// > If the length of the slice is less than the length of the array, a
|
|
||||||
// > run-time panic occurs.
|
|
||||||
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
|
||||||
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
|
||||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
|
|
||||||
// and unsafe.String. This function must panic if the ptr/len parameters are
|
|
||||||
// invalid.
|
|
||||||
func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) {
|
|
||||||
// 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
|
|
||||||
// > zero, a run-time panic occurs.
|
|
||||||
// However, in practice, it is also necessary to check that the length is
|
|
||||||
// not too big that a GEP wouldn't be possible without wrapping the pointer.
|
|
||||||
// These two checks (non-negative and not too big) can be merged into one
|
|
||||||
// using an unsigned greater than.
|
|
||||||
|
|
||||||
// Make sure the len value is at least as big as a uintptr.
|
|
||||||
len = b.extendInteger(len, lenType, b.uintptrType)
|
|
||||||
|
|
||||||
// Determine the maximum slice size, and therefore the maximum value of the
|
|
||||||
// len parameter.
|
|
||||||
maxSize := b.maxSliceSize(elementType)
|
|
||||||
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
|
|
||||||
|
|
||||||
// Do the check. By using unsigned greater than for the length check, signed
|
|
||||||
// negative values are also checked (which are very large numbers when
|
|
||||||
// interpreted as signed values).
|
|
||||||
zero := llvm.ConstInt(len.Type(), 0, false)
|
|
||||||
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
|
|
||||||
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
|
|
||||||
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
|
||||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
|
||||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
|
||||||
b.createRuntimeAssert(assert, name, "unsafeSlicePanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||||
// check that the value is not too big for runtime.chanMake.
|
// check that the value is not too big for runtime.chanMake.
|
||||||
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
||||||
@@ -130,12 +110,23 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure bufSize is at least as big as maxBufSize (an uintptr).
|
// Check whether the bufSize parameter must be cast to a wider integer for
|
||||||
bufSize = b.extendInteger(bufSize, bufSizeType, b.uintptrType)
|
// comparison.
|
||||||
|
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
||||||
|
if bufSizeType.Info()&types.IsUnsigned != 0 {
|
||||||
|
// Unsigned, so zero-extend to uint type.
|
||||||
|
bufSizeType = types.Typ[types.Uint]
|
||||||
|
bufSize = b.CreateZExt(bufSize, b.intType, "")
|
||||||
|
} else {
|
||||||
|
// Signed, so sign-extend to int type.
|
||||||
|
bufSizeType = types.Typ[types.Int]
|
||||||
|
bufSize = b.CreateSExt(bufSize, b.intType, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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 +137,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.
|
||||||
@@ -171,10 +162,6 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
|
|||||||
case *ssa.Alloc:
|
case *ssa.Alloc:
|
||||||
// An alloc is never nil.
|
// An alloc is never nil.
|
||||||
return
|
return
|
||||||
case *ssa.FreeVar:
|
|
||||||
// A free variable is allocated in a parent function and is thus never
|
|
||||||
// nil.
|
|
||||||
return
|
|
||||||
case *ssa.IndexAddr:
|
case *ssa.IndexAddr:
|
||||||
// This pointer is the result of an index operation into a slice or
|
// This pointer is the result of an index operation into a slice or
|
||||||
// array. Such slices/arrays are already bounds checked so the pointer
|
// array. Such slices/arrays are already bounds checked so the pointer
|
||||||
@@ -215,19 +202,6 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
|||||||
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
|
|
||||||
// will be emitted. This follows the Go specification which says that a divide
|
|
||||||
// by zero must cause a run time panic.
|
|
||||||
func (b *builder) createDivideByZeroCheck(y llvm.Value) {
|
|
||||||
if b.info.nobounds {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// isZero = y == 0
|
|
||||||
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
|
|
||||||
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRuntimeAssert is a common function to create a new branch on an assert
|
// createRuntimeAssert is a common function to create a new branch on an assert
|
||||||
// bool, calling an assert func if the assert value is true (1).
|
// bool, calling an assert func if the assert value is true (1).
|
||||||
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
|
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
|
||||||
@@ -241,11 +215,9 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, 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")
|
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||||
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
|
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, 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)
|
||||||
@@ -258,19 +230,3 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
|||||||
// Ok: assert didn't trigger so continue normally.
|
// Ok: assert didn't trigger so continue normally.
|
||||||
b.SetInsertPointAtEnd(nextBlock)
|
b.SetInsertPointAtEnd(nextBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extendInteger extends the value to at least targetType using a zero or sign
|
|
||||||
// extend. The resulting value is not truncated: it may still be bigger than
|
|
||||||
// targetType.
|
|
||||||
func (b *builder) extendInteger(value llvm.Value, valueType types.Type, targetType llvm.Type) llvm.Value {
|
|
||||||
if value.Type().IntTypeWidth() < targetType.IntTypeWidth() {
|
|
||||||
if valueType.Underlying().(*types.Basic).Info()&types.IsUnsigned != 0 {
|
|
||||||
// Unsigned, so zero-extend to the target type.
|
|
||||||
value = b.CreateZExt(value, targetType, "")
|
|
||||||
} else {
|
|
||||||
// Signed, so sign-extend to the target type.
|
|
||||||
value = b.CreateSExt(value, targetType, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|||||||
+59
-32
@@ -1,57 +1,84 @@
|
|||||||
package compiler
|
package compiler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// createAtomicOp lowers a sync/atomic function by lowering it as an LLVM atomic
|
// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
|
||||||
// operation. It returns the result of the operation, or a zero llvm.Value if
|
// operation. It returns the result of the operation and true if the call could
|
||||||
// the result is void.
|
// be lowered inline, and false otherwise.
|
||||||
func (b *builder) createAtomicOp(name string) llvm.Value {
|
func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||||
|
name := call.Value.(*ssa.Function).Name()
|
||||||
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(call.Args[0])
|
||||||
val := b.getValue(b.fn.Params[1], getPos(b.fn))
|
val := b.getValue(call.Args[1])
|
||||||
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, ""), true
|
||||||
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(call.Args[0])
|
||||||
val := b.getValue(b.fn.Params[1], getPos(b.fn))
|
val := b.getValue(call.Args[1])
|
||||||
|
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
|
||||||
|
if isPointer {
|
||||||
|
// atomicrmw only supports integers, so cast to an integer.
|
||||||
|
val = b.CreatePtrToInt(val, b.uintptrType, "")
|
||||||
|
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
|
||||||
|
}
|
||||||
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
||||||
return oldVal
|
if isPointer {
|
||||||
|
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
|
||||||
|
}
|
||||||
|
return oldVal, true
|
||||||
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(call.Args[0])
|
||||||
old := b.getValue(b.fn.Params[1], getPos(b.fn))
|
old := b.getValue(call.Args[1])
|
||||||
newVal := b.getValue(b.fn.Params[2], getPos(b.fn))
|
newVal := b.getValue(call.Args[2])
|
||||||
|
if strings.HasSuffix(name, "64") {
|
||||||
|
arch := strings.Split(b.Triple, "-")[0]
|
||||||
|
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
|
||||||
|
// Work around a bug in LLVM, at least LLVM 11:
|
||||||
|
// https://reviews.llvm.org/D95891
|
||||||
|
// Check for armv6m, armv7, armv7em, and perhaps others.
|
||||||
|
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||||
|
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
|
||||||
|
if compareAndSwap.IsNil() {
|
||||||
|
// Declare the function if it isn't already declared.
|
||||||
|
i64Type := b.ctx.Int64Type()
|
||||||
|
fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false)
|
||||||
|
compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType)
|
||||||
|
}
|
||||||
|
actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "")
|
||||||
|
// The __sync_val_compare_and_swap_8 function returns the old
|
||||||
|
// value. However, we shouldn't return the old value, we should
|
||||||
|
// return whether the compare/exchange was successful. This is
|
||||||
|
// easily done by comparing the returned (actual) old value with
|
||||||
|
// the expected old value passed to
|
||||||
|
// __sync_val_compare_and_swap_8.
|
||||||
|
swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "")
|
||||||
|
return swapped, true
|
||||||
|
}
|
||||||
|
}
|
||||||
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
|
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, true
|
||||||
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(call.Args[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, true
|
||||||
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(call.Args[0])
|
||||||
val := b.getValue(b.fn.Params[1], getPos(b.fn))
|
val := b.getValue(call.Args[1])
|
||||||
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
|
||||||
return llvm.Value{}
|
return store, true
|
||||||
default:
|
default:
|
||||||
b.addError(b.fn.Pos(), "unknown atomic operation: "+b.fn.Name())
|
return llvm.Value{}, false
|
||||||
return llvm.Value{}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-88
@@ -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,74 +28,32 @@ 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
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
|
// createCall creates a new call to runtime.<fnName> with the given arguments.
|
||||||
// createRuntimeInvoke instead.
|
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||||
func (b *builder) createRuntimeCallCommon(fnName string, args []llvm.Value, name string, isInvoke bool) llvm.Value {
|
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
|
||||||
member := b.program.ImportedPackage("runtime").Members[fnName]
|
llvmFn := b.getFunction(fn)
|
||||||
if member == nil {
|
|
||||||
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 {
|
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
||||||
return b.createInvoke(fnType, llvmFn, args, name)
|
return b.createCall(llvmFn, args, name)
|
||||||
}
|
|
||||||
return b.createCall(fnType, llvmFn, args, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRuntimeCall creates a new call to runtime.<fnName> with the given
|
|
||||||
// arguments.
|
|
||||||
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
|
||||||
return b.createRuntimeCallCommon(fnName, args, name, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// createRuntimeInvoke creates a new call to runtime.<fnName> with the given
|
|
||||||
// arguments. If the runtime call panics, control flow is diverted to the
|
|
||||||
// landing pad block.
|
|
||||||
// Note that "invoke" here is meant in the LLVM sense (a call that can
|
|
||||||
// panic/throw), not in the Go sense (an interface method call).
|
|
||||||
func (b *builder) createRuntimeInvoke(fnName string, args []llvm.Value, name string) llvm.Value {
|
|
||||||
return b.createRuntimeCallCommon(fnName, args, name, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createCall creates a call to the given function with the arguments possibly
|
// 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
|
|
||||||
// the call resulted in a panic.
|
|
||||||
func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
|
||||||
if b.hasDeferFrame() {
|
|
||||||
b.createInvokeCheckpoint()
|
|
||||||
}
|
|
||||||
return b.createCall(fnType, 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
|
||||||
@@ -106,13 +63,19 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
|
|||||||
case llvm.StructTypeKind:
|
case llvm.StructTypeKind:
|
||||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
fieldInfos := c.flattenAggregateType(t, name, goType)
|
||||||
if len(fieldInfos) <= maxFieldsPerParam {
|
if len(fieldInfos) <= maxFieldsPerParam {
|
||||||
// managed to expand this parameter
|
|
||||||
return fieldInfos
|
return fieldInfos
|
||||||
|
} else {
|
||||||
|
// failed to lower
|
||||||
}
|
}
|
||||||
// failed to expand this parameter: too many fields
|
|
||||||
}
|
}
|
||||||
// TODO: split small arrays
|
// TODO: split small arrays
|
||||||
return []paramInfo{c.getParamInfo(t, name, goType)}
|
return []paramInfo{
|
||||||
|
{
|
||||||
|
llvmType: t,
|
||||||
|
name: name,
|
||||||
|
flags: getTypeFlags(goType),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// expandFormalParamOffsets returns a list of offsets from the start of an
|
// expandFormalParamOffsets returns a list of offsets from the start of an
|
||||||
@@ -162,6 +125,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
|
||||||
@@ -170,7 +134,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.
|
||||||
@@ -187,48 +150,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
|
||||||
@@ -251,7 +212,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.
|
||||||
|
|||||||
+46
-80
@@ -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,65 +27,46 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
|||||||
// createChanSend emits a pseudo chan send operation. It is lowered to the
|
// createChanSend emits a pseudo chan send operation. It is lowered to the
|
||||||
// actual channel send operation during goroutine lowering.
|
// actual channel send operation during goroutine lowering.
|
||||||
func (b *builder) createChanSend(instr *ssa.Send) {
|
func (b *builder) createChanSend(instr *ssa.Send) {
|
||||||
ch := b.getValue(instr.Chan, getPos(instr))
|
ch := b.getValue(instr.Chan)
|
||||||
chanValue := b.getValue(instr.X, getPos(instr))
|
chanValue := b.getValue(instr.X)
|
||||||
|
|
||||||
// store value-to-send
|
// store value-to-send
|
||||||
valueType := b.getLLVMType(instr.X.Type())
|
valueType := b.getLLVMType(instr.X.Type())
|
||||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||||
var valueAlloca, valueAllocaSize llvm.Value
|
b.CreateStore(chanValue, valueAlloca)
|
||||||
if isZeroSize {
|
|
||||||
valueAlloca = llvm.ConstNull(b.dataPtrType)
|
|
||||||
} else {
|
|
||||||
valueAlloca, 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.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, 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)
|
||||||
if !isZeroSize {
|
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
b.emitLifetimeEnd(valueAlloca, 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.
|
||||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||||
var valueAlloca, valueAllocaSize llvm.Value
|
|
||||||
if isZeroSize {
|
|
||||||
valueAlloca = llvm.ConstNull(b.dataPtrType)
|
|
||||||
} else {
|
|
||||||
valueAlloca, 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, valueAlloca, channelOpAlloca}, "")
|
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
||||||
var received llvm.Value
|
received := b.CreateLoad(valueAlloca, "chan.received")
|
||||||
if isZeroSize {
|
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||||
received = llvm.ConstNull(valueType)
|
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||||
} else {
|
|
||||||
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
|
|
||||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
|
||||||
}
|
|
||||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
|
||||||
|
|
||||||
if unop.CommaOk {
|
if unop.CommaOk {
|
||||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||||
@@ -126,20 +105,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.
|
||||||
@@ -151,10 +116,11 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
|||||||
// determine the receive buffer size and alignment.
|
// determine the receive buffer size and alignment.
|
||||||
recvbufSize := uint64(0)
|
recvbufSize := uint64(0)
|
||||||
recvbufAlign := 0
|
recvbufAlign := 0
|
||||||
|
hasReceives := false
|
||||||
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 {
|
||||||
@@ -167,13 +133,15 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
|||||||
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
||||||
recvbufAlign = align
|
recvbufAlign = align
|
||||||
}
|
}
|
||||||
|
hasReceives = true
|
||||||
case types.SendOnly:
|
case types.SendOnly:
|
||||||
// Store this value in an alloca and put a pointer to this alloca
|
// Store this value in an alloca and put a pointer to this alloca
|
||||||
// in the send state.
|
// in the send state.
|
||||||
sendValue := b.getValue(state.Send, state.Pos)
|
sendValue := b.getValue(state.Send)
|
||||||
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
||||||
b.CreateStore(sendValue, alloca)
|
b.CreateStore(sendValue, alloca)
|
||||||
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
|
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
|
||||||
|
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
|
||||||
default:
|
default:
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
@@ -181,12 +149,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 hasReceives {
|
||||||
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")
|
||||||
@@ -194,16 +162,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")
|
||||||
@@ -214,10 +182,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")
|
||||||
@@ -225,23 +193,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
|
||||||
@@ -263,7 +228,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, "")
|
||||||
@@ -271,7 +236,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
|
||||||
@@ -280,7 +245,8 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
|
|||||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
// receive can proceed at a time) so we'll get that alloca, bitcast
|
||||||
// it to the correct type, and dereference it.
|
// it to the correct type, and dereference it.
|
||||||
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||||
typ := b.getLLVMType(expr.Type())
|
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
|
||||||
return b.CreateLoad(typ, recvbuf, "")
|
ptr := b.CreateBitCast(recvbuf, typ, "")
|
||||||
|
return b.CreateLoad(ptr, "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+439
-1251
File diff suppressed because it is too large
Load Diff
+65
-160
@@ -3,13 +3,12 @@ package compiler
|
|||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"go/types"
|
"go/types"
|
||||||
"os"
|
"io/ioutil"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
"github.com/tinygo-org/tinygo/goenv"
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
@@ -17,68 +16,73 @@ import (
|
|||||||
// Pass -update to go test to update the output of the test files.
|
// Pass -update to go test to update the output of the test files.
|
||||||
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
||||||
|
|
||||||
type testCase struct {
|
|
||||||
file string
|
|
||||||
target string
|
|
||||||
scheduler string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Basic tests for the compiler. Build some Go files and compare the output with
|
// Basic tests for the compiler. Build some Go files and compare the output with
|
||||||
// the expected LLVM IR for regression testing.
|
// the expected LLVM IR for regression testing.
|
||||||
func TestCompiler(t *testing.T) {
|
func TestCompiler(t *testing.T) {
|
||||||
t.Parallel()
|
// Check LLVM version.
|
||||||
|
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
|
||||||
// Determine Go minor version (e.g. 16 in go1.16.3).
|
|
||||||
_, goMinor, err := goenv.GetGorootVersion()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("could not read Go version:", err)
|
t.Fatal("could not parse LLVM version:", llvm.Version)
|
||||||
|
}
|
||||||
|
if llvmMajor < 11 {
|
||||||
|
// It is likely this version needs to be bumped in the future.
|
||||||
|
// The goal is to at least test the LLVM version that's used by default
|
||||||
|
// in TinyGo and (if possible without too many workarounds) also some
|
||||||
|
// previous versions.
|
||||||
|
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine which tests to run, depending on the Go and LLVM versions.
|
target, err := compileopts.LoadTarget("i686--linux")
|
||||||
tests := []testCase{
|
if err != nil {
|
||||||
{"basic.go", "", ""},
|
t.Fatal("failed to load target:", err)
|
||||||
{"pointer.go", "", ""},
|
|
||||||
{"slice.go", "", ""},
|
|
||||||
{"string.go", "", ""},
|
|
||||||
{"float.go", "", ""},
|
|
||||||
{"interface.go", "", ""},
|
|
||||||
{"func.go", "", ""},
|
|
||||||
{"defer.go", "cortex-m-qemu", ""},
|
|
||||||
{"pragma.go", "", ""},
|
|
||||||
{"goroutine.go", "wasm", "asyncify"},
|
|
||||||
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
|
||||||
{"channel.go", "", ""},
|
|
||||||
{"gc.go", "", ""},
|
|
||||||
{"zeromap.go", "", ""},
|
|
||||||
{"generics.go", "", ""},
|
|
||||||
}
|
}
|
||||||
if goMinor >= 20 {
|
config := &compileopts.Config{
|
||||||
tests = append(tests, testCase{"go1.20.go", "", ""})
|
Options: &compileopts.Options{},
|
||||||
|
Target: target,
|
||||||
}
|
}
|
||||||
if goMinor >= 21 {
|
compilerConfig := &Config{
|
||||||
tests = append(tests, testCase{"go1.21.go", "", ""})
|
Triple: config.Triple(),
|
||||||
|
GOOS: config.GOOS(),
|
||||||
|
GOARCH: config.GOARCH(),
|
||||||
|
CodeModel: config.CodeModel(),
|
||||||
|
RelocationModel: config.RelocationModel(),
|
||||||
|
Scheduler: config.Scheduler(),
|
||||||
|
FuncImplementation: config.FuncImplementation(),
|
||||||
|
AutomaticStackSize: config.AutomaticStackSize(),
|
||||||
|
}
|
||||||
|
machine, err := NewTargetMachine(compilerConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to create target machine:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
tests := []string{
|
||||||
name := tc.file
|
"basic.go",
|
||||||
targetString := "wasm"
|
"pointer.go",
|
||||||
if tc.target != "" {
|
"slice.go",
|
||||||
targetString = tc.target
|
"string.go",
|
||||||
name += "-" + tc.target
|
"float.go",
|
||||||
}
|
"interface.go",
|
||||||
if tc.scheduler != "" {
|
"func.go",
|
||||||
name += "-" + tc.scheduler
|
}
|
||||||
}
|
|
||||||
|
|
||||||
t.Run(name, func(t *testing.T) {
|
for _, testCase := range tests {
|
||||||
options := &compileopts.Options{
|
t.Run(testCase, func(t *testing.T) {
|
||||||
Target: targetString,
|
// Load entire program AST into memory.
|
||||||
|
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
|
||||||
|
Sizes: Sizes(machine),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to create target machine:", err)
|
||||||
}
|
}
|
||||||
if tc.scheduler != "" {
|
err = lprogram.Parse()
|
||||||
options.Scheduler = tc.scheduler
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse test case %s: %s", testCase, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mod, errs := testCompilePackage(t, options, tc.file)
|
// Compile AST to IR.
|
||||||
|
program := lprogram.LoadSSA()
|
||||||
|
pkg := lprogram.MainPkg()
|
||||||
|
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||||
if errs != nil {
|
if errs != nil {
|
||||||
for _, err := range errs {
|
for _, err := range errs {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
@@ -86,38 +90,33 @@ func TestCompiler(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimize IR a little.
|
// Optimize IR a little.
|
||||||
passOptions := llvm.NewPassBuilderOptions()
|
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
||||||
defer passOptions.Dispose()
|
defer funcPasses.Dispose()
|
||||||
err = mod.RunPasses("instcombine", llvm.TargetMachine{}, passOptions)
|
funcPasses.AddInstructionCombiningPass()
|
||||||
if err != nil {
|
funcPasses.InitializeFunc()
|
||||||
t.Error(err)
|
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||||
|
funcPasses.RunFunc(fn)
|
||||||
}
|
}
|
||||||
|
funcPasses.FinalizeFunc()
|
||||||
|
|
||||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
|
||||||
if tc.target != "" {
|
|
||||||
outFilePrefix += "-" + tc.target
|
|
||||||
}
|
|
||||||
if tc.scheduler != "" {
|
|
||||||
outFilePrefix += "-" + tc.scheduler
|
|
||||||
}
|
|
||||||
outPath := "./testdata/" + outFilePrefix + ".ll"
|
|
||||||
|
|
||||||
// Update test if needed. Do not check the result.
|
// Update test if needed. Do not check the result.
|
||||||
if *flagUpdate {
|
if *flagUpdate {
|
||||||
err := os.WriteFile(outPath, []byte(mod.String()), 0666)
|
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error("failed to write updated output file:", err)
|
t.Error("failed to write updated output file:", err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
expected, err := os.ReadFile(outPath)
|
expected, err := ioutil.ReadFile(outfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to read golden file:", err)
|
t.Fatal("failed to read golden file:", err)
|
||||||
}
|
}
|
||||||
@@ -153,12 +152,6 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
|||||||
// stripped out.
|
// stripped out.
|
||||||
func filterIrrelevantIRLines(lines []string) []string {
|
func filterIrrelevantIRLines(lines []string) []string {
|
||||||
var out []string
|
var out []string
|
||||||
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
|
||||||
if err != nil {
|
|
||||||
// Note: this should never happen and if it does, it will always happen
|
|
||||||
// for a particular build because llvm.Version is a constant.
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
line = strings.Split(line, ";")[0] // strip out comments/info
|
line = strings.Split(line, ";")[0] // strip out comments/info
|
||||||
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
|
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
|
||||||
@@ -168,95 +161,7 @@ func filterIrrelevantIRLines(lines []string) []string {
|
|||||||
if strings.HasPrefix(line, "source_filename = ") {
|
if strings.HasPrefix(line, "source_filename = ") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if llvmVersion < 15 && strings.HasPrefix(line, "target datalayout = ") {
|
|
||||||
// The datalayout string may vary betewen LLVM versions.
|
|
||||||
// Right now test outputs are for LLVM 15 and higher.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = append(out, line)
|
out = append(out, line)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCompilerErrors(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
// Read expected errors from the test file.
|
|
||||||
var expectedErrors []string
|
|
||||||
errorsFile, err := os.ReadFile("testdata/errors.go")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
|
|
||||||
for _, line := range strings.Split(errorsFileString, "\n") {
|
|
||||||
if strings.HasPrefix(line, "// ERROR: ") {
|
|
||||||
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile the Go file with errors.
|
|
||||||
options := &compileopts.Options{
|
|
||||||
Target: "wasm",
|
|
||||||
}
|
|
||||||
_, errs := testCompilePackage(t, options, "errors.go")
|
|
||||||
|
|
||||||
// Check whether the actual errors match the expected errors.
|
|
||||||
expectedErrorsIdx := 0
|
|
||||||
for _, err := range errs {
|
|
||||||
err := err.(types.Error)
|
|
||||||
position := err.Fset.Position(err.Pos)
|
|
||||||
position.Filename = "errors.go" // don't use a full path
|
|
||||||
if expectedErrorsIdx >= len(expectedErrors) || expectedErrors[expectedErrorsIdx] != err.Msg {
|
|
||||||
t.Errorf("unexpected compiler error: %s: %s", position.String(), err.Msg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
expectedErrorsIdx++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build a package given a number of compiler options and a file.
|
|
||||||
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
|
|
||||||
target, err := compileopts.LoadTarget(options)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to load target:", err)
|
|
||||||
}
|
|
||||||
config := &compileopts.Config{
|
|
||||||
Options: options,
|
|
||||||
Target: target,
|
|
||||||
}
|
|
||||||
compilerConfig := &Config{
|
|
||||||
Triple: config.Triple(),
|
|
||||||
Features: config.Features(),
|
|
||||||
ABI: config.ABI(),
|
|
||||||
GOOS: config.GOOS(),
|
|
||||||
GOARCH: config.GOARCH(),
|
|
||||||
CodeModel: config.CodeModel(),
|
|
||||||
RelocationModel: config.RelocationModel(),
|
|
||||||
Scheduler: config.Scheduler(),
|
|
||||||
AutomaticStackSize: config.AutomaticStackSize(),
|
|
||||||
DefaultStackSize: config.StackSize(),
|
|
||||||
NeedsStackObjects: config.NeedsStackObjects(),
|
|
||||||
}
|
|
||||||
machine, err := NewTargetMachine(compilerConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
defer machine.Dispose()
|
|
||||||
|
|
||||||
// Load entire program AST into memory.
|
|
||||||
lprogram, err := loader.Load(config, "./testdata/"+file, types.Config{
|
|
||||||
Sizes: Sizes(machine),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
err = lprogram.Parse()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("could not parse test case %s: %s", file, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile AST to IR.
|
|
||||||
program := lprogram.LoadSSA()
|
|
||||||
pkg := lprogram.MainPkg()
|
|
||||||
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
|
||||||
}
|
|
||||||
|
|||||||
+107
-369
@@ -15,40 +15,12 @@ package compiler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/types"
|
"go/types"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||||
"golang.org/x/tools/go/ssa"
|
"golang.org/x/tools/go/ssa"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// supportsRecover returns whether the compiler supports the recover() builtin
|
|
||||||
// for the current architecture.
|
|
||||||
func (b *builder) supportsRecover() bool {
|
|
||||||
switch b.archFamily() {
|
|
||||||
case "wasm32":
|
|
||||||
// Probably needs to be implemented using the exception handling
|
|
||||||
// proposal of WebAssembly:
|
|
||||||
// https://github.com/WebAssembly/exception-handling
|
|
||||||
return false
|
|
||||||
case "riscv64", "xtensa":
|
|
||||||
// TODO: add support for these architectures
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasDeferFrame returns whether the current function needs to catch panics and
|
|
||||||
// run defers.
|
|
||||||
func (b *builder) hasDeferFrame() bool {
|
|
||||||
if b.fn.Recover == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return b.supportsRecover()
|
|
||||||
}
|
|
||||||
|
|
||||||
// deferInitFunc sets up this function for future deferred calls. It must be
|
// deferInitFunc sets up this function for future deferred calls. It must be
|
||||||
// called from within the entry block when this function contains deferred
|
// called from within the entry block when this function contains deferred
|
||||||
// calls.
|
// calls.
|
||||||
@@ -61,281 +33,43 @@ func (b *builder) deferInitFunc() {
|
|||||||
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
||||||
|
|
||||||
// Create defer list pointer.
|
// Create defer list pointer.
|
||||||
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
|
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
|
||||||
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
|
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
|
||||||
|
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
|
||||||
if b.hasDeferFrame() {
|
|
||||||
// Set up the defer frame with the current stack pointer.
|
|
||||||
// This assumes that the stack pointer doesn't move outside of the
|
|
||||||
// function prologue/epilogue (an invariant maintained by TinyGo but
|
|
||||||
// possibly broken by the C alloca function).
|
|
||||||
// The frame pointer is _not_ saved, because it is marked as clobbered
|
|
||||||
// in the setjmp-like inline assembly.
|
|
||||||
deferFrameType := b.getLLVMRuntimeType("deferFrame")
|
|
||||||
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
|
|
||||||
stackPointer := b.readStackPointer()
|
|
||||||
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
|
|
||||||
|
|
||||||
// Create the landing pad block, which is where control transfers after
|
|
||||||
// a panic.
|
|
||||||
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createLandingPad fills in the landing pad block. This block runs the deferred
|
// isInLoop checks if there is a path from a basic block to itself.
|
||||||
// functions and returns (by jumping to the recover block). If the function is
|
func isInLoop(start *ssa.BasicBlock) bool {
|
||||||
// still panicking after the defers are run, the panic will be re-raised in
|
// Use a breadth-first search to scan backwards through the block graph.
|
||||||
// destroyDeferFrame.
|
queue := []*ssa.BasicBlock{start}
|
||||||
func (b *builder) createLandingPad() {
|
checked := map[*ssa.BasicBlock]struct{}{}
|
||||||
b.SetInsertPointAtEnd(b.landingpad)
|
|
||||||
|
|
||||||
// Add debug info, if needed.
|
for len(queue) > 0 {
|
||||||
// The location used is the closing bracket of the function.
|
// pop a block off of the queue
|
||||||
if b.Debug {
|
block := queue[len(queue)-1]
|
||||||
pos := b.program.Fset.Position(b.fn.Syntax().End())
|
queue = queue[:len(queue)-1]
|
||||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
|
||||||
}
|
|
||||||
|
|
||||||
b.createRunDefers()
|
// Search through predecessors.
|
||||||
|
// Searching backwards means that this is pretty fast when the block is close to the start of the function.
|
||||||
// Continue at the 'recover' block, which returns to the parent in an
|
// Defers are often placed near the start of the function.
|
||||||
// appropriate way.
|
for _, pred := range block.Preds {
|
||||||
b.CreateBr(b.blockInfo[b.fn.Recover.Index].entry)
|
if pred == start {
|
||||||
}
|
// cycle found
|
||||||
|
return true
|
||||||
// Create a checkpoint (similar to setjmp). This emits inline assembly that
|
|
||||||
// stores the current program counter inside the ptr address (actually
|
|
||||||
// ptr+sizeof(ptr)) and then returns a boolean indicating whether this is the
|
|
||||||
// normal flow (false) or we jumped here from somewhere else (true).
|
|
||||||
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
|
||||||
// Construct inline assembly equivalents of setjmp.
|
|
||||||
// The assembly works as follows:
|
|
||||||
// * All registers (both callee-saved and caller saved) are clobbered
|
|
||||||
// after the inline assembly returns.
|
|
||||||
// * The assembly stores the address just past the end of the assembly
|
|
||||||
// into the jump buffer.
|
|
||||||
// * The return value (eax, rax, r0, etc) is set to zero in the inline
|
|
||||||
// assembly but set to an unspecified non-zero value when jumping using
|
|
||||||
// a longjmp.
|
|
||||||
var asmString, constraints string
|
|
||||||
resultType := b.uintptrType
|
|
||||||
switch b.archFamily() {
|
|
||||||
case "i386":
|
|
||||||
asmString = `
|
|
||||||
xorl %eax, %eax
|
|
||||||
movl $$1f, 4(%ebx)
|
|
||||||
1:`
|
|
||||||
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
|
||||||
// This doesn't include the floating point stack because TinyGo uses
|
|
||||||
// newer floating point instructions.
|
|
||||||
case "x86_64":
|
|
||||||
asmString = `
|
|
||||||
leaq 1f(%rip), %rax
|
|
||||||
movq %rax, 8(%rbx)
|
|
||||||
xorq %rax, %rax
|
|
||||||
1:`
|
|
||||||
constraints = "={rax},{rbx},~{rbx},~{rcx},~{rdx},~{rsi},~{rdi},~{rbp},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{xmm8},~{xmm9},~{xmm10},~{xmm11},~{xmm12},~{xmm13},~{xmm14},~{xmm15},~{xmm16},~{xmm17},~{xmm18},~{xmm19},~{xmm20},~{xmm21},~{xmm22},~{xmm23},~{xmm24},~{xmm25},~{xmm26},~{xmm27},~{xmm28},~{xmm29},~{xmm30},~{xmm31},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
|
||||||
// This list doesn't include AVX/AVX512 registers because TinyGo
|
|
||||||
// doesn't currently enable support for AVX instructions.
|
|
||||||
case "arm":
|
|
||||||
// Note: the following assembly takes into account that the PC is
|
|
||||||
// always 4 bytes ahead on ARM. The PC that is stored always points
|
|
||||||
// to the instruction just after the assembly fragment so that
|
|
||||||
// tinygo_longjmp lands at the correct instruction.
|
|
||||||
if b.isThumb() {
|
|
||||||
// Instructions are 2 bytes in size.
|
|
||||||
asmString = `
|
|
||||||
movs r0, #0
|
|
||||||
mov r2, pc
|
|
||||||
str r2, [r1, #4]`
|
|
||||||
} else {
|
|
||||||
// Instructions are 4 bytes in size.
|
|
||||||
asmString = `
|
|
||||||
str pc, [r1, #4]
|
|
||||||
movs r0, #0`
|
|
||||||
}
|
|
||||||
constraints = "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"
|
|
||||||
case "aarch64":
|
|
||||||
asmString = `
|
|
||||||
adr x2, 1f
|
|
||||||
str x2, [x1, #8]
|
|
||||||
mov x0, #0
|
|
||||||
1:
|
|
||||||
`
|
|
||||||
constraints = "={x0},{x1},~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7},~{x8},~{x9},~{x10},~{x11},~{x12},~{x13},~{x14},~{x15},~{x16},~{x17},~{x19},~{x20},~{x21},~{x22},~{x23},~{x24},~{x25},~{x26},~{x27},~{x28},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{q16},~{q17},~{q18},~{q19},~{q20},~{q21},~{q22},~{q23},~{q24},~{q25},~{q26},~{q27},~{q28},~{q29},~{q30},~{nzcv},~{ffr},~{memory}"
|
|
||||||
if b.GOOS != "darwin" && b.GOOS != "windows" {
|
|
||||||
// These registers cause the following warning when compiling for
|
|
||||||
// MacOS and Windows:
|
|
||||||
// warning: inline asm clobber list contains reserved registers:
|
|
||||||
// X18, FP
|
|
||||||
// Reserved registers on the clobber list may not be preserved
|
|
||||||
// across the asm statement, and clobbering them may lead to
|
|
||||||
// undefined behaviour.
|
|
||||||
constraints += ",~{x18},~{fp}"
|
|
||||||
}
|
|
||||||
// TODO: SVE registers, which we don't use in TinyGo at the moment.
|
|
||||||
case "avr":
|
|
||||||
// Note: the Y register (R28:R29) is a fixed register and therefore
|
|
||||||
// needs to be saved manually. TODO: do this only once per function with
|
|
||||||
// a defer frame, not for every call.
|
|
||||||
resultType = b.ctx.Int8Type()
|
|
||||||
asmString = `
|
|
||||||
ldi r24, pm_lo8(1f)
|
|
||||||
ldi r25, pm_hi8(1f)
|
|
||||||
std z+2, r24
|
|
||||||
std z+3, r25
|
|
||||||
std z+4, r28
|
|
||||||
std z+5, r29
|
|
||||||
ldi r24, 0
|
|
||||||
1:`
|
|
||||||
constraints = "={r24},z,~{r0},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{r13},~{r14},~{r15},~{r16},~{r17},~{r18},~{r19},~{r20},~{r21},~{r22},~{r23},~{r25},~{r26},~{r27}"
|
|
||||||
case "mips":
|
|
||||||
// $4 flag (zero or non-zero)
|
|
||||||
// $5 defer frame
|
|
||||||
asmString = `
|
|
||||||
.set noat
|
|
||||||
move $$4, $$zero
|
|
||||||
jal 1f
|
|
||||||
1:
|
|
||||||
addiu $$ra, 8
|
|
||||||
sw $$ra, 4($$5)
|
|
||||||
.set at`
|
|
||||||
constraints = "={$4},{$5},~{$1},~{$2},~{$3},~{$5},~{$6},~{$7},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$16},~{$17},~{$18},~{$19},~{$20},~{$21},~{$22},~{$23},~{$24},~{$25},~{$26},~{$27},~{$28},~{$29},~{$30},~{$31},~{memory}"
|
|
||||||
if !strings.Contains(b.Features, "+soft-float") {
|
|
||||||
// Using floating point registers together with GOMIPS=softfloat
|
|
||||||
// results in a crash: "This value type is not natively supported!"
|
|
||||||
// So only add them when using hardfloat.
|
|
||||||
constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}"
|
|
||||||
}
|
|
||||||
case "riscv32":
|
|
||||||
asmString = `
|
|
||||||
la a2, 1f
|
|
||||||
sw a2, 4(a1)
|
|
||||||
li a0, 0
|
|
||||||
1:`
|
|
||||||
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
|
|
||||||
default:
|
|
||||||
// This case should have been handled by b.supportsRecover().
|
|
||||||
b.addError(b.fn.Pos(), "unknown architecture for defer: "+b.archFamily())
|
|
||||||
}
|
|
||||||
asmType := llvm.FunctionType(resultType, []llvm.Type{b.dataPtrType}, false)
|
|
||||||
asm := llvm.InlineAsm(asmType, asmString, constraints, false, false, 0, false)
|
|
||||||
result := b.CreateCall(asmType, asm, []llvm.Value{ptr}, "setjmp")
|
|
||||||
result.AddCallSiteAttribute(-1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("returns_twice"), 0))
|
|
||||||
isZero := b.CreateICmp(llvm.IntEQ, result, llvm.ConstInt(resultType, 0, false), "setjmp.result")
|
|
||||||
return isZero
|
|
||||||
}
|
|
||||||
|
|
||||||
// createInvokeCheckpoint saves the function state at the given point, to
|
|
||||||
// continue at the landing pad if a panic happened. This is implemented using a
|
|
||||||
// setjmp-like construct.
|
|
||||||
func (b *builder) createInvokeCheckpoint() {
|
|
||||||
isZero := b.createCheckpoint(b.deferFrame)
|
|
||||||
continueBB := b.insertBasicBlock("")
|
|
||||||
b.CreateCondBr(isZero, continueBB, b.landingpad)
|
|
||||||
b.SetInsertPointAtEnd(continueBB)
|
|
||||||
b.currentBlockInfo.exit = continueBB
|
|
||||||
}
|
|
||||||
|
|
||||||
// isInLoop checks if there is a path from the current block to itself.
|
|
||||||
// Use Tarjan's strongly connected components algorithm to search for cycles.
|
|
||||||
// A one-node SCC is a cycle iff there is an edge from the node to itself.
|
|
||||||
// A multi-node SCC is always a cycle.
|
|
||||||
// https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
|
|
||||||
func (b *builder) isInLoop() bool {
|
|
||||||
if b.currentBlockInfo.tarjan.lowLink == 0 {
|
|
||||||
b.strongConnect(b.currentBlock)
|
|
||||||
}
|
|
||||||
return b.currentBlockInfo.tarjan.cyclic
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) strongConnect(block *ssa.BasicBlock) {
|
|
||||||
// Assign a new index.
|
|
||||||
// Indices start from 1 so that 0 can be used as a sentinel.
|
|
||||||
assignedIndex := b.tarjanIndex + 1
|
|
||||||
b.tarjanIndex = assignedIndex
|
|
||||||
|
|
||||||
// Apply the new index.
|
|
||||||
blockIndex := block.Index
|
|
||||||
node := &b.blockInfo[blockIndex].tarjan
|
|
||||||
node.lowLink = assignedIndex
|
|
||||||
|
|
||||||
// Push the node onto the stack.
|
|
||||||
node.onStack = true
|
|
||||||
b.tarjanStack = append(b.tarjanStack, uint(blockIndex))
|
|
||||||
|
|
||||||
// Process the successors.
|
|
||||||
for _, successor := range block.Succs {
|
|
||||||
// Look up the successor's state.
|
|
||||||
successorIndex := successor.Index
|
|
||||||
if successorIndex == blockIndex {
|
|
||||||
// Handle a self-cycle specially.
|
|
||||||
node.cyclic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
successorNode := &b.blockInfo[successorIndex].tarjan
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case successorNode.lowLink == 0:
|
|
||||||
// This node has not yet been visisted.
|
|
||||||
b.strongConnect(successor)
|
|
||||||
|
|
||||||
case !successorNode.onStack:
|
|
||||||
// This node has been visited, but is in a different SCC.
|
|
||||||
// Ignore it, and do not update lowLink.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the lowLink index.
|
|
||||||
// This always uses the min-of-lowlink instead of using index in the on-stack case.
|
|
||||||
// This is done for two reasons:
|
|
||||||
// 1. The lowLink update can be shared between the new-node and on-stack cases.
|
|
||||||
// 2. The assigned index does not need to be saved - it is only needed for root node detection.
|
|
||||||
if successorNode.lowLink < node.lowLink {
|
|
||||||
node.lowLink = successorNode.lowLink
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if node.lowLink == assignedIndex {
|
|
||||||
// This is a root node.
|
|
||||||
// Pop the SCC off the stack.
|
|
||||||
stack := b.tarjanStack
|
|
||||||
top := stack[len(stack)-1]
|
|
||||||
stack = stack[:len(stack)-1]
|
|
||||||
blocks := b.blockInfo
|
|
||||||
topNode := &blocks[top].tarjan
|
|
||||||
topNode.onStack = false
|
|
||||||
|
|
||||||
if top != uint(blockIndex) {
|
|
||||||
// The root node is not the only node in the SCC.
|
|
||||||
// Mark all nodes in this SCC as cyclic.
|
|
||||||
topNode.cyclic = true
|
|
||||||
for top != uint(blockIndex) {
|
|
||||||
top = stack[len(stack)-1]
|
|
||||||
stack = stack[:len(stack)-1]
|
|
||||||
topNode = &blocks[top].tarjan
|
|
||||||
topNode.onStack = false
|
|
||||||
topNode.cyclic = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, ok := checked[pred]; ok {
|
||||||
|
// block already checked
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// add to queue and checked map
|
||||||
|
queue = append(queue, pred)
|
||||||
|
checked[pred] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
b.tarjanStack = stack
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// tarjanNode holds per-block state for isInLoop and strongConnect.
|
return false
|
||||||
type tarjanNode struct {
|
|
||||||
// lowLink is the index of the first visited node that is reachable from this block.
|
|
||||||
// The lowLink indices are assigned by the SCC search, and do not correspond to b.Index.
|
|
||||||
// A lowLink of 0 is used as a sentinel to mark a node which has not yet been visited.
|
|
||||||
lowLink uint
|
|
||||||
|
|
||||||
// onStack tracks whether this node is currently on the SCC search stack.
|
|
||||||
onStack bool
|
|
||||||
|
|
||||||
// cyclic indicates whether this block is in a loop.
|
|
||||||
// If lowLink is 0, strongConnect must be called before reading this field.
|
|
||||||
cyclic bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDefer emits a single defer instruction, to be run when this function
|
// createDefer emits a single defer instruction, to be run when this function
|
||||||
@@ -343,7 +77,7 @@ type tarjanNode struct {
|
|||||||
func (b *builder) createDefer(instr *ssa.Defer) {
|
func (b *builder) createDefer(instr *ssa.Defer) {
|
||||||
// The pointer to the previous defer struct, which we will replace to
|
// The pointer to the previous defer struct, which we will replace to
|
||||||
// make a linked list.
|
// make a linked list.
|
||||||
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
|
next := b.CreateLoad(b.deferPtr, "defer.next")
|
||||||
|
|
||||||
var values []llvm.Value
|
var values []llvm.Value
|
||||||
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
||||||
@@ -360,13 +94,13 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
|
|
||||||
// Collect all values to be put in the struct (starting with
|
// Collect all values to be put in the struct (starting with
|
||||||
// runtime._defer fields, followed by the call parameters).
|
// runtime._defer fields, followed by the call parameters).
|
||||||
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
|
itf := b.getValue(instr.Call.Value) // interface
|
||||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
values = []llvm.Value{callback, next, typecode, receiverValue}
|
values = []llvm.Value{callback, next, typecode, receiverValue}
|
||||||
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
val := b.getValue(arg, getPos(instr))
|
val := b.getValue(arg)
|
||||||
values = append(values, val)
|
values = append(values, val)
|
||||||
valueTypes = append(valueTypes, val.Type())
|
valueTypes = append(valueTypes, val.Type())
|
||||||
}
|
}
|
||||||
@@ -383,7 +117,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
// runtime._defer fields).
|
// runtime._defer fields).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam := b.getValue(param, getPos(instr))
|
llvmParam := b.getValue(param)
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -395,7 +129,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
// pointer.
|
// pointer.
|
||||||
// TODO: ignore this closure entirely and put pointers to the free
|
// TODO: ignore this closure entirely and put pointers to the free
|
||||||
// variables directly in the defer struct, avoiding a memory allocation.
|
// variables directly in the defer struct, avoiding a memory allocation.
|
||||||
closure := b.getValue(instr.Call.Value, getPos(instr))
|
closure := b.getValue(instr.Call.Value)
|
||||||
context := b.CreateExtractValue(closure, 0, "")
|
context := b.CreateExtractValue(closure, 0, "")
|
||||||
|
|
||||||
// Get the callback number.
|
// Get the callback number.
|
||||||
@@ -411,7 +145,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
// context pointer).
|
// context pointer).
|
||||||
values = []llvm.Value{callback, next}
|
values = []llvm.Value{callback, next}
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam := b.getValue(param, getPos(instr))
|
llvmParam := b.getValue(param)
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
@@ -423,7 +157,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
var argValues []llvm.Value
|
var argValues []llvm.Value
|
||||||
for _, arg := range instr.Call.Args {
|
for _, arg := range instr.Call.Args {
|
||||||
argTypes = append(argTypes, arg.Type())
|
argTypes = append(argTypes, arg.Type())
|
||||||
argValues = append(argValues, b.getValue(arg, getPos(instr)))
|
argValues = append(argValues, b.getValue(arg))
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
|
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
|
||||||
@@ -446,7 +180,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
funcValue := b.getValue(instr.Call.Value, getPos(instr))
|
funcValue := b.getValue(instr.Call.Value)
|
||||||
|
|
||||||
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
|
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
|
||||||
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
|
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
|
||||||
@@ -461,48 +195,43 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
|||||||
values = []llvm.Value{callback, next, funcValue}
|
values = []llvm.Value{callback, next, funcValue}
|
||||||
valueTypes = append(valueTypes, funcValue.Type())
|
valueTypes = append(valueTypes, funcValue.Type())
|
||||||
for _, param := range instr.Call.Args {
|
for _, param := range instr.Call.Args {
|
||||||
llvmParam := b.getValue(param, getPos(instr))
|
llvmParam := b.getValue(param)
|
||||||
values = append(values, llvmParam)
|
values = append(values, llvmParam)
|
||||||
valueTypes = append(valueTypes, llvmParam.Type())
|
valueTypes = append(valueTypes, llvmParam.Type())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a struct out of the collected values to put in the deferred call
|
// Make a struct out of the collected values to put in the defer frame.
|
||||||
// struct.
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
deferFrame := llvm.ConstNull(deferFrameType)
|
||||||
deferredCall := llvm.ConstNull(deferredCallType)
|
|
||||||
for i, value := range values {
|
for i, value := range values {
|
||||||
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
|
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put this struct in an allocation.
|
// Put this struct in an allocation.
|
||||||
var alloca llvm.Value
|
var alloca llvm.Value
|
||||||
if instr.Block() != b.currentBlock {
|
if !isInLoop(instr.Block()) {
|
||||||
panic("block mismatch")
|
|
||||||
}
|
|
||||||
if !b.isInLoop() {
|
|
||||||
// This can safely use a stack allocation.
|
// This can safely use a stack allocation.
|
||||||
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferredCallType, "defer.alloca")
|
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
|
||||||
} else {
|
} else {
|
||||||
// This may be hit a variable number of times, so use a heap allocation.
|
// This may be hit a variable number of times, so use a heap allocation.
|
||||||
size := b.targetData.TypeAllocSize(deferredCallType)
|
size := b.targetData.TypeAllocSize(deferFrameType)
|
||||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||||
nilPtr := llvm.ConstNull(b.dataPtrType)
|
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||||
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||||
}
|
}
|
||||||
if b.NeedsStackObjects {
|
if b.NeedsStackObjects {
|
||||||
b.trackPointer(alloca)
|
b.trackPointer(alloca)
|
||||||
}
|
}
|
||||||
b.CreateStore(deferredCall, alloca)
|
b.CreateStore(deferFrame, alloca)
|
||||||
|
|
||||||
// Push it on top of the linked list by replacing deferPtr.
|
// Push it on top of the linked list by replacing deferPtr.
|
||||||
b.CreateStore(alloca, b.deferPtr)
|
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||||
|
b.CreateStore(allocaCast, b.deferPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRunDefers emits code to run all deferred functions.
|
// createRunDefers emits code to run all deferred functions.
|
||||||
func (b *builder) createRunDefers() {
|
func (b *builder) createRunDefers() {
|
||||||
deferType := b.getLLVMRuntimeType("_defer")
|
|
||||||
|
|
||||||
// Add a loop like the following:
|
// Add a loop like the following:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
// _stack := stack
|
// _stack := stack
|
||||||
@@ -518,17 +247,17 @@ func (b *builder) createRunDefers() {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// Create loop, in the order: loophead, loop, callback0, callback1, ..., unreachable, end.
|
// Create loop.
|
||||||
end := b.insertBasicBlock("rundefers.end")
|
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
|
||||||
unreachable := b.ctx.InsertBasicBlock(end, "rundefers.default")
|
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
|
||||||
loop := b.ctx.InsertBasicBlock(unreachable, "rundefers.loop")
|
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
|
||||||
loophead := b.ctx.InsertBasicBlock(loop, "rundefers.loophead")
|
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
|
||||||
b.CreateBr(loophead)
|
b.CreateBr(loophead)
|
||||||
|
|
||||||
// Create loop head:
|
// Create loop head:
|
||||||
// for stack != nil {
|
// for stack != nil {
|
||||||
b.SetInsertPointAtEnd(loophead)
|
b.SetInsertPointAtEnd(loophead)
|
||||||
deferData := b.CreateLoad(b.dataPtrType, b.deferPtr, "")
|
deferData := b.CreateLoad(b.deferPtr, "")
|
||||||
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
||||||
b.CreateCondBr(stackIsNil, end, loop)
|
b.CreateCondBr(stackIsNil, end, loop)
|
||||||
|
|
||||||
@@ -537,24 +266,24 @@ func (b *builder) createRunDefers() {
|
|||||||
// stack = stack.next
|
// stack = stack.next
|
||||||
// switch stack.callback {
|
// switch stack.callback {
|
||||||
b.SetInsertPointAtEnd(loop)
|
b.SetInsertPointAtEnd(loop)
|
||||||
nextStackGEP := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
|
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
|
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
|
||||||
}, "stack.next.gep")
|
}, "stack.next.gep")
|
||||||
nextStack := b.CreateLoad(b.dataPtrType, nextStackGEP, "stack.next")
|
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
|
||||||
b.CreateStore(nextStack, b.deferPtr)
|
b.CreateStore(nextStack, b.deferPtr)
|
||||||
gep := b.CreateInBoundsGEP(deferType, deferData, []llvm.Value{
|
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
|
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
|
||||||
}, "callback.gep")
|
}, "callback.gep")
|
||||||
callback := b.CreateLoad(b.uintptrType, gep, "callback")
|
callback := b.CreateLoad(gep, "callback")
|
||||||
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
|
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
|
||||||
|
|
||||||
for i, callback := range b.allDeferFuncs {
|
for i, callback := range b.allDeferFuncs {
|
||||||
// Create switch case, for example:
|
// Create switch case, for example:
|
||||||
// case 0:
|
// case 0:
|
||||||
// // run first deferred call
|
// // run first deferred call
|
||||||
block := b.insertBasicBlock("rundefers.callback" + strconv.Itoa(i))
|
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
|
||||||
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
||||||
b.SetInsertPointAtEnd(block)
|
b.SetInsertPointAtEnd(block)
|
||||||
switch callback := callback.(type) {
|
switch callback := callback.(type) {
|
||||||
@@ -562,32 +291,33 @@ func (b *builder) createRunDefers() {
|
|||||||
// Call on an value or interface value.
|
// Call on an value or interface value.
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
if !callback.IsInvoke() {
|
||||||
//Expect funcValue to be passed through the deferred call.
|
//Expect funcValue to be passed through the defer frame.
|
||||||
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
||||||
} else {
|
} else {
|
||||||
//Expect typecode
|
//Expect typecode
|
||||||
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, arg := range callback.Args {
|
for _, arg := range callback.Args {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
|
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct (including receiver).
|
// Extract the params from the struct (including receiver).
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||||
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
forwardParam := b.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
var fnPtr llvm.Value
|
var fnPtr llvm.Value
|
||||||
var fnType llvm.Type
|
|
||||||
|
|
||||||
if !callback.IsInvoke() {
|
if !callback.IsInvoke() {
|
||||||
// Isolate the func value.
|
// Isolate the func value.
|
||||||
@@ -595,86 +325,93 @@ func (b *builder) createRunDefers() {
|
|||||||
forwardParams = forwardParams[1:]
|
forwardParams = forwardParams[1:]
|
||||||
|
|
||||||
//Get function pointer and context
|
//Get function pointer and context
|
||||||
var context llvm.Value
|
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
|
||||||
fnPtr, context = b.decodeFuncValue(funcValue)
|
fnPtr = fp
|
||||||
fnType = b.getLLVMFunctionType(callback.Signature())
|
|
||||||
|
|
||||||
//Pass context
|
//Pass context
|
||||||
forwardParams = append(forwardParams, context)
|
forwardParams = append(forwardParams, context)
|
||||||
} else {
|
} else {
|
||||||
// Move typecode from the start to the end of the list of
|
// Isolate the typecode.
|
||||||
// parameters.
|
typecode := forwardParams[0]
|
||||||
forwardParams = append(forwardParams[1:], forwardParams[0])
|
forwardParams = forwardParams[1:]
|
||||||
fnPtr = b.getInvokeFunction(callback)
|
fnPtr = b.getInvokePtr(callback, typecode)
|
||||||
fnType = fnPtr.GlobalValueType()
|
|
||||||
|
|
||||||
// Add the context parameter. An interface call cannot also be a
|
// Add the context parameter. An interface call cannot also be a
|
||||||
// closure but we have to supply the parameter anyway for platforms
|
// closure but we have to supply the parameter anyway for platforms
|
||||||
// with a strict calling convention.
|
// with a strict calling convention.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
}
|
}
|
||||||
|
|
||||||
b.createCall(fnType, fnPtr, forwardParams, "")
|
// Parent coroutine handle.
|
||||||
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
|
b.createCall(fnPtr, forwardParams, "")
|
||||||
|
|
||||||
case *ssa.Function:
|
case *ssa.Function:
|
||||||
// Direct call.
|
// Direct call.
|
||||||
|
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||||
for _, param := range getParams(callback.Signature) {
|
for _, param := range getParams(callback.Signature) {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||||
}
|
}
|
||||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
|
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct.
|
// Extract the params from the struct.
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||||
for i := range getParams(callback.Signature) {
|
for i := range getParams(callback.Signature) {
|
||||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||||
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
forwardParam := b.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
|
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
|
||||||
// These parameters should not be supplied when calling into an external C/ASM function.
|
// These parameters should not be supplied when calling into an external C/ASM function.
|
||||||
if !b.getFunctionInfo(callback).exported {
|
if !b.getFunctionInfo(callback).exported {
|
||||||
// Add the context parameter. We know it is ignored by the receiving
|
// Add the context parameter. We know it is ignored by the receiving
|
||||||
// function, but we have to pass one anyway.
|
// function, but we have to pass one anyway.
|
||||||
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
|
// Parent coroutine handle.
|
||||||
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call real function.
|
// Call real function.
|
||||||
fnType, fn := b.getFunction(callback)
|
b.createCall(b.getFunction(callback), forwardParams, "")
|
||||||
b.createInvoke(fnType, fn, forwardParams, "")
|
|
||||||
|
|
||||||
case *ssa.MakeClosure:
|
case *ssa.MakeClosure:
|
||||||
// Get the real defer struct type and cast to it.
|
// Get the real defer struct type and cast to it.
|
||||||
fn := callback.Fn.(*ssa.Function)
|
fn := callback.Fn.(*ssa.Function)
|
||||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||||
params := fn.Signature.Params()
|
params := fn.Signature.Params()
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||||
}
|
}
|
||||||
valueTypes = append(valueTypes, b.dataPtrType) // closure
|
valueTypes = append(valueTypes, b.i8ptrType) // closure
|
||||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
|
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct.
|
// Extract the params from the struct.
|
||||||
forwardParams := []llvm.Value{}
|
forwardParams := []llvm.Value{}
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||||
for i := 2; i < len(valueTypes); i++ {
|
for i := 2; i < len(valueTypes); i++ {
|
||||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
||||||
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
|
forwardParam := b.CreateLoad(gep, "param")
|
||||||
forwardParams = append(forwardParams, forwardParam)
|
forwardParams = append(forwardParams, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent coroutine handle.
|
||||||
|
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||||
|
|
||||||
// Call deferred function.
|
// Call deferred function.
|
||||||
fnType, llvmFn := b.getFunction(fn)
|
b.createCall(b.getFunction(fn), forwardParams, "")
|
||||||
b.createCall(fnType, llvmFn, forwardParams, "")
|
|
||||||
case *ssa.Builtin:
|
case *ssa.Builtin:
|
||||||
db := b.deferBuiltinFuncs[callback]
|
db := b.deferBuiltinFuncs[callback]
|
||||||
|
|
||||||
//Get parameter types
|
//Get parameter types
|
||||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||||
|
|
||||||
//Get signature from call results
|
//Get signature from call results
|
||||||
params := callback.Type().Underlying().(*types.Signature).Params()
|
params := callback.Type().Underlying().(*types.Signature).Params()
|
||||||
@@ -682,14 +419,15 @@ func (b *builder) createRunDefers() {
|
|||||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||||
}
|
}
|
||||||
|
|
||||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||||
|
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||||
|
|
||||||
// Extract the params from the struct.
|
// Extract the params from the struct.
|
||||||
var argValues []llvm.Value
|
var argValues []llvm.Value
|
||||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||||
for i := 0; i < params.Len(); i++ {
|
for i := 0; i < params.Len(); i++ {
|
||||||
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||||
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
|
forwardParam := b.CreateLoad(gep, "param")
|
||||||
argValues = append(argValues, forwardParam)
|
argValues = append(argValues, forwardParam)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user