mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-28 07:38:41 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 67b45dd14d |
@@ -90,8 +90,6 @@ commands:
|
||||
name: Check Go code formatting
|
||||
command: make fmt-check lint
|
||||
- run: make gen-device -j4
|
||||
# TODO: change this to -skip='TestErrors|TestWasm' with Go 1.20
|
||||
- run: go test -tags=llvm<<parameters.llvm>> -short -run='TestBuild|TestTest|TestGetList|TestTraceback'
|
||||
- run: make smoketest XTENSA=0
|
||||
- save_cache:
|
||||
key: go-cache-v4-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
open_collective: tinygo
|
||||
@@ -16,26 +16,24 @@ jobs:
|
||||
name: build-macos
|
||||
strategy:
|
||||
matrix:
|
||||
# macos-13: amd64 (oldest supported version as of 18-10-2024)
|
||||
# macos-12: amd64 (oldest supported version as of 05-02-2024)
|
||||
# macos-14: arm64 (oldest arm64 version)
|
||||
os: [macos-13, macos-14]
|
||||
os: [macos-12, macos-14]
|
||||
include:
|
||||
- os: macos-13
|
||||
- os: macos-12
|
||||
goarch: amd64
|
||||
- os: macos-14
|
||||
goarch: arm64
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
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@v5
|
||||
with:
|
||||
@@ -74,6 +72,7 @@ jobs:
|
||||
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
|
||||
@@ -101,13 +100,15 @@ jobs:
|
||||
- name: make gen-device
|
||||
run: make -j3 gen-device
|
||||
- name: Test TinyGo
|
||||
shell: bash
|
||||
run: make test GOTESTFLAGS="-short"
|
||||
- name: Build TinyGo release tarball
|
||||
run: make release -j3
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test
|
||||
- name: Make release artifact
|
||||
run: cp -p build/release.tar.gz build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
|
||||
shell: bash
|
||||
run: cp -p build/release.tar.gz build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
|
||||
- name: Publish release artifact
|
||||
# Note: this release artifact is double-zipped, see:
|
||||
# https://github.com/actions/upload-artifact/issues/39
|
||||
@@ -117,9 +118,10 @@ jobs:
|
||||
# We're doing the former here, to keep artifact uploads fast.
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: darwin-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
|
||||
path: build/tinygo${{ steps.version.outputs.version }}.darwin-${{ matrix.goarch }}.tar.gz
|
||||
name: darwin-${{ matrix.goarch }}-double-zipped
|
||||
path: build/tinygo.darwin-${{ matrix.goarch }}.tar.gz
|
||||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo
|
||||
test-macos-homebrew:
|
||||
name: homebrew-install
|
||||
|
||||
+15
-23
@@ -19,8 +19,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: golang:1.23-alpine
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Install apk dependencies
|
||||
# tar: needed for actions/cache@v4
|
||||
@@ -34,9 +32,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
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@v4
|
||||
with:
|
||||
@@ -125,15 +120,15 @@ jobs:
|
||||
- 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
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||
cp -p build/release.deb /tmp/tinygo_amd64.deb
|
||||
- name: Publish release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-amd64-double-zipped-${{ steps.version.outputs.version }}
|
||||
name: linux-amd64-double-zipped
|
||||
path: |
|
||||
/tmp/tinygo${{ steps.version.outputs.version }}.linux-amd64.tar.gz
|
||||
/tmp/tinygo_${{ steps.version.outputs.version }}_amd64.deb
|
||||
/tmp/tinygo.linux-amd64.tar.gz
|
||||
/tmp/tinygo_amd64.deb
|
||||
test-linux-build:
|
||||
# Test the binaries built in the build-linux job by running the smoke tests.
|
||||
runs-on: ubuntu-latest
|
||||
@@ -157,11 +152,11 @@ jobs:
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
|
||||
name: linux-amd64-double-zipped
|
||||
- name: Extract release tarball
|
||||
run: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz
|
||||
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
|
||||
- run: make tinygo-test-wasip1-fast
|
||||
- run: make tinygo-test-wasip2-fast
|
||||
@@ -169,7 +164,7 @@ jobs:
|
||||
assert-test-linux:
|
||||
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
|
||||
# potential bugs.
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -302,9 +297,6 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- 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
|
||||
@@ -389,11 +381,11 @@ jobs:
|
||||
- name: Download amd64 release
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-amd64-double-zipped-${{ needs.build-linux.outputs.version }}
|
||||
name: linux-amd64-double-zipped
|
||||
- name: Extract amd64 release
|
||||
run: |
|
||||
mkdir -p build/release
|
||||
tar -xf tinygo${{ needs.build-linux.outputs.version }}.linux-amd64.tar.gz -C build/release tinygo
|
||||
tar -xf tinygo.linux-amd64.tar.gz -C build/release tinygo
|
||||
- name: Modify release
|
||||
run: |
|
||||
cp -p build/tinygo build/release/tinygo/bin
|
||||
@@ -401,12 +393,12 @@ jobs:
|
||||
- name: Create ${{ matrix.goarch }} release
|
||||
run: |
|
||||
make release deb RELEASEONLY=1 DEB_ARCH=${{ matrix.libc }}
|
||||
cp -p build/release.tar.gz /tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
|
||||
cp -p build/release.deb /tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
|
||||
cp -p build/release.deb /tmp/tinygo_${{ matrix.libc }}.deb
|
||||
- name: Publish release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-${{ matrix.goarch }}-double-zipped-${{ steps.version.outputs.version }}
|
||||
name: linux-${{ matrix.goarch }}-double-zipped
|
||||
path: |
|
||||
/tmp/tinygo${{ steps.version.outputs.version }}.linux-${{ matrix.goarch }}.tar.gz
|
||||
/tmp/tinygo_${{ steps.version.outputs.version }}_${{ matrix.libc }}.deb
|
||||
/tmp/tinygo.linux-${{ matrix.goarch }}.tar.gz
|
||||
/tmp/tinygo_${{ matrix.libc }}.deb
|
||||
|
||||
@@ -15,11 +15,6 @@ 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@v4
|
||||
- name: Pull musl
|
||||
|
||||
@@ -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'
|
||||
@@ -14,8 +14,6 @@ concurrency:
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-2022
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
@@ -34,10 +32,6 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
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@v5
|
||||
with:
|
||||
@@ -100,13 +94,6 @@ jobs:
|
||||
- name: Build wasi-libc
|
||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
||||
run: make wasi-libc
|
||||
- name: Cache Go cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
key: go-cache-windows-v1-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
C:/Users/runneradmin/AppData/Local/go-build
|
||||
C:/Users/runneradmin/go/pkg/mod
|
||||
- name: Install wasmtime
|
||||
run: |
|
||||
scoop install wasmtime@14.0.4
|
||||
@@ -121,7 +108,7 @@ jobs:
|
||||
- name: Make release artifact
|
||||
shell: bash
|
||||
working-directory: build/release
|
||||
run: 7z -tzip a tinygo${{ steps.version.outputs.version }}.windows-amd64.zip tinygo
|
||||
run: 7z -tzip a release.zip tinygo
|
||||
- name: Publish release artifact
|
||||
# Note: this release artifact is double-zipped, see:
|
||||
# https://github.com/actions/upload-artifact/issues/39
|
||||
@@ -131,8 +118,8 @@ jobs:
|
||||
# We're doing the former here, to keep artifact uploads fast.
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-amd64-double-zipped-${{ steps.version.outputs.version }}
|
||||
path: build/release/tinygo${{ steps.version.outputs.version }}.windows-amd64.zip
|
||||
name: windows-amd64-double-zipped
|
||||
path: build/release/release.zip
|
||||
|
||||
smoke-test-windows:
|
||||
runs-on: windows-2022
|
||||
@@ -161,12 +148,12 @@ jobs:
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
|
||||
name: windows-amd64-double-zipped
|
||||
path: build/
|
||||
- name: Unzip TinyGo build
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: 7z x tinygo*.windows-amd64.zip -r
|
||||
run: 7z x release.zip -r
|
||||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
||||
@@ -191,12 +178,12 @@ jobs:
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
|
||||
name: windows-amd64-double-zipped
|
||||
path: build/
|
||||
- name: Unzip TinyGo build
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: 7z x tinygo*.windows-amd64.zip -r
|
||||
run: 7z x release.zip -r
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
||||
|
||||
@@ -227,11 +214,11 @@ jobs:
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-amd64-double-zipped-${{ needs.build-windows.outputs.version }}
|
||||
name: windows-amd64-double-zipped
|
||||
path: build/
|
||||
- name: Unzip TinyGo build
|
||||
shell: bash
|
||||
working-directory: build
|
||||
run: 7z x tinygo*.windows-amd64.zip -r
|
||||
run: 7z x release.zip -r
|
||||
- name: Test stdlib packages on wasip1
|
||||
run: make tinygo-test-wasip1-fast TINYGO=$(PWD)/build/tinygo/bin/tinygo
|
||||
|
||||
@@ -37,9 +37,5 @@ test.exe
|
||||
test.gba
|
||||
test.hex
|
||||
test.nro
|
||||
test.uf2
|
||||
test.wasm
|
||||
wasm.wasm
|
||||
|
||||
*.uf2
|
||||
*.elf
|
||||
-118
@@ -1,121 +1,3 @@
|
||||
0.35.0
|
||||
---
|
||||
* **general**
|
||||
- update cmsis-svd library
|
||||
- use default UART settings in the echo example
|
||||
- `goenv`: also show git hash with custom build of TinyGo
|
||||
- `goenv`: support parsing development versions of Go
|
||||
- `main`: parse extldflags early so we can report the error message
|
||||
* **compiler**
|
||||
- `builder`: whitelist temporary directory env var for Clang invocation to fix Windows bug
|
||||
- `builder`: fix cache paths in `-size=full` output
|
||||
- `builder`: work around incorrectly escaped DWARF paths on Windows (Clang bug)
|
||||
- `builder`: fix wasi-libc path names on Windows with `-size=full`
|
||||
- `builder`: write HTML size report
|
||||
- `cgo`: support C identifiers only referred to from within macros
|
||||
- `cgo`: support function-like macros
|
||||
- `cgo`: support errno value as second return parameter
|
||||
- `cgo`: add support for `#cgo noescape` lines
|
||||
- `compiler`: fix bug in interrupt lowering
|
||||
- `compiler`: allow panic directly in `defer`
|
||||
- `compiler`: fix wasmimport -> wasmexport in error message
|
||||
- `compiler`: support `//go:noescape` pragma
|
||||
- `compiler`: report error instead of crashing when instantiating a generic function without body
|
||||
- `interp`: align created globals
|
||||
* **standard library**
|
||||
- `machine`: modify i2s interface/implementation to better match specification
|
||||
- `os`: implement `StartProcess`
|
||||
- `reflect`: add `Value.Clear`
|
||||
- `reflect`: add interface support to `NumMethods`
|
||||
- `reflect`: fix `AssignableTo` for named + non-named types
|
||||
- `reflect`: implement `CanConvert`
|
||||
- `reflect`: handle more cases in `Convert`
|
||||
- `reflect`: fix Copy of non-pointer array with size > 64bits
|
||||
- `runtime`: don't call sleepTicks with a negative duration
|
||||
- `runtime`: optimize GC scanning (findHead)
|
||||
- `runtime`: move constants into shared package
|
||||
- `runtime`: add `runtime.fcntl` function for internal/syscall/unix
|
||||
- `runtime`: heapptr only needs to be initialized once
|
||||
- `runtime`: refactor scheduler (this fixes a few bugs with `-scheduler=none`)
|
||||
- `runtime`: rewrite channel implementation to be smaller and more flexible
|
||||
- `runtime`: use `SA_RESTART` when registering a signal for os/signal
|
||||
- `runtime`: implement race-free signals using futexes
|
||||
- `runtime`: run deferred functions in `Goexit`
|
||||
- `runtime`: remove `Cond` which seems to be unused
|
||||
- `runtime`: properly handle unix read on directory
|
||||
- `runtime/trace`: stub all public methods
|
||||
- `sync`: don't use volatile in `Mutex`
|
||||
- `sync`: implement `WaitGroup` using a (pseudo)futex
|
||||
- `sync`: make `Cond` parallelism-safe
|
||||
- `syscall`: use wasi-libc tables for wasm/js target
|
||||
* **targets**
|
||||
- `mips`: fix a bug when scanning the stack
|
||||
- `nintendoswitch`: get this target to compile again
|
||||
- `rp2350`: add support for the new RP2350
|
||||
- `rp2040/rp2350` : make I2C implementation shared for rp2040/rp2350
|
||||
- `rp2040/rp2350` : make SPI implementation shared for rp2040/rp2350
|
||||
- `rp2040/rp2350` : make RNG implementation shared for rp2040/rp2350
|
||||
- `wasm`: revise and simplify wasmtime argument handling
|
||||
- `wasm`: support `//go:wasmexport` functions after a call to `time.Sleep`
|
||||
- `wasm`: correctly return from run() in wasm_exec.js
|
||||
- `wasm`: call process.exit() when go.run() returns
|
||||
- `windows`: don't return, exit via exit(0) instead to flush stdout buffer
|
||||
* **boards**
|
||||
- add support for the Tillitis TKey
|
||||
- add support for the Raspberry Pi Pico2 (based on the RP2040)
|
||||
- add support for Pimoroni Tiny2350
|
||||
|
||||
|
||||
0.34.0
|
||||
---
|
||||
* **general**
|
||||
- fix `GOOS=wasip1` for `tinygo test`
|
||||
- add `-C DIR` flag
|
||||
- add initial documentation for project governance
|
||||
- add `-ldflags='-extldflags=...'` support
|
||||
- improve usage message with `tinygo help` and when passing invalid parameters
|
||||
* **compiler**
|
||||
- `builder`: remove environment variables when invoking Clang, to avoid the environment changing the behavior
|
||||
- `builder`: check for the Go toolchain version used to compile TinyGo
|
||||
- `cgo`: add `C.CBytes` implementation
|
||||
- `compiler`: fix passing weirdly-padded structs as parameters to new goroutines
|
||||
- `compiler`: support pragmas on generic functions
|
||||
- `compiler`: do not let the slice buffer escape when casting a `[]byte` or `[]rune` to a string, to help escape analysis
|
||||
- `compiler`: conform to the latest iteration of the wasm types proposal
|
||||
- `loader`: don't panic when main package is not named 'main'
|
||||
- `loader`: make sure we always return type checker errors even without type errors
|
||||
- `transform`: optimize range over `[]byte(string)`
|
||||
* **standard library**
|
||||
- `crypto/x509`: add package stub to build crypto/x509 on macOS
|
||||
- `machine/usb/adc/midi`: fix `PitchBend`
|
||||
- `os`: add `Truncate` stub for baremetal
|
||||
- `os`: add stubs for `os.File` deadlines
|
||||
- `os`: add internal `net.newUnixFile` for the net package
|
||||
- `runtime`: stub runtime_{Before,After}Exec for linkage
|
||||
- `runtime`: randomize map accesses
|
||||
- `runtime`: support `maps.Clone`
|
||||
- `runtime`: add more fields to `MemStats`
|
||||
- `runtime`: implement newcoro, coroswitch to support package iter
|
||||
- `runtime`: disallow defer in interrupts
|
||||
- `runtime`: add support for os/signal on Linux and MacOS
|
||||
- `runtime`: add gc layout info for some basic types to help the precise GC
|
||||
- `runtime`: bump GC mark stack size to avoid excessive heap rescans
|
||||
* **targets**
|
||||
- `darwin`: use Go standard library syscall package instead of a custom one
|
||||
- `fe310`: support GPIO `PinInput`
|
||||
- `mips`: fix compiler crash with GOMIPS=softfloat and defer
|
||||
- `mips`: add big-endian (GOARCH=mips) support
|
||||
- `mips`: use MIPS32 (instead of MIPS32R2) as the instruction set for wider compatibility
|
||||
- `wasi`: add relative and absolute --dir options to wasmtime args
|
||||
- `wasip2`: add wasmtime -S args to support network interfaces
|
||||
- `wasm`: add `//go:wasmexport` support (for all WebAssembly targets)
|
||||
- `wasm`: use precise instead of conservative GC for WebAssembly (including WASI)
|
||||
- `wasm-unknown`: add bulk memory flags since basically every runtime has it now
|
||||
* **boards**
|
||||
- add RAKwireless RAK4631
|
||||
- add WaveShare ESP-C3-32S-Kit
|
||||
|
||||
|
||||
0.33.0
|
||||
---
|
||||
|
||||
|
||||
+4
-50
@@ -291,9 +291,9 @@ endif
|
||||
|
||||
tinygo: ## Build the TinyGo compiler
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" -ldflags="-X github.com/tinygo-org/tinygo/goenv.GitSha1=`git rev-parse --short HEAD`" .
|
||||
test: wasi-libc check-nodejs-version
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
|
||||
|
||||
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
|
||||
TEST_PACKAGES_SLOW = \
|
||||
@@ -303,34 +303,26 @@ TEST_PACKAGES_SLOW = \
|
||||
|
||||
# Standard library packages that pass tests quickly on darwin, linux, wasi, and windows
|
||||
TEST_PACKAGES_FAST = \
|
||||
cmp \
|
||||
compress/lzw \
|
||||
compress/zlib \
|
||||
container/heap \
|
||||
container/list \
|
||||
container/ring \
|
||||
crypto/des \
|
||||
crypto/ecdsa \
|
||||
crypto/elliptic \
|
||||
crypto/md5 \
|
||||
crypto/rc4 \
|
||||
crypto/sha1 \
|
||||
crypto/sha256 \
|
||||
crypto/sha512 \
|
||||
database/sql/driver \
|
||||
debug/macho \
|
||||
embed/internal/embedtest \
|
||||
encoding \
|
||||
encoding/ascii85 \
|
||||
encoding/asn1 \
|
||||
encoding/base32 \
|
||||
encoding/base64 \
|
||||
encoding/csv \
|
||||
encoding/hex \
|
||||
go/ast \
|
||||
go/format \
|
||||
go/scanner \
|
||||
go/version \
|
||||
hash \
|
||||
hash/adler32 \
|
||||
hash/crc64 \
|
||||
@@ -365,17 +357,11 @@ endif
|
||||
# archive/zip requires os.ReadAt, which is not yet supported on windows
|
||||
# bytes requires mmap
|
||||
# compress/flate appears to hang on wasi
|
||||
# crypto/aes fails on wasi, needs panic()/recover()
|
||||
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
|
||||
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
|
||||
# image requires recover(), which is not yet supported on wasi
|
||||
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
|
||||
# mime: fail on wasi; neds panic()/recover()
|
||||
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# mime/quotedprintable requires syscall.Faccessat
|
||||
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# regexp/syntax: fails on wasip1; needs panic()/recover()
|
||||
# strconv requires recover() which is not yet supported on wasi
|
||||
# text/tabwriter requires recover(), which is not yet supported on wasi
|
||||
# text/template/parse requires recover(), which is not yet supported on wasi
|
||||
@@ -385,20 +371,14 @@ endif
|
||||
TEST_PACKAGES_LINUX := \
|
||||
archive/zip \
|
||||
compress/flate \
|
||||
crypto/aes \
|
||||
crypto/hmac \
|
||||
debug/dwarf \
|
||||
debug/plan9obj \
|
||||
image \
|
||||
io/ioutil \
|
||||
mime \
|
||||
mime/multipart \
|
||||
mime/quotedprintable \
|
||||
net \
|
||||
net/mail \
|
||||
net/textproto \
|
||||
os/user \
|
||||
regexp/syntax \
|
||||
strconv \
|
||||
text/tabwriter \
|
||||
text/template/parse
|
||||
@@ -503,17 +483,8 @@ tinygo-baremetal:
|
||||
# regression test for #2666: e.g. encoding/hex must pass on baremetal
|
||||
$(TINYGO) test -target cortex-m-qemu encoding/hex
|
||||
|
||||
.PHONY: testchdir
|
||||
testchdir:
|
||||
# test 'build' command with{,out} -C argument
|
||||
$(TINYGO) build -C tests/testing/chdir chdir.go && rm tests/testing/chdir/chdir
|
||||
$(TINYGO) build ./tests/testing/chdir/chdir.go && rm chdir
|
||||
# test 'run' command with{,out} -C argument
|
||||
EXPECT_DIR=$(PWD)/tests/testing/chdir $(TINYGO) run -C tests/testing/chdir chdir.go
|
||||
EXPECT_DIR=$(PWD) $(TINYGO) run ./tests/testing/chdir/chdir.go
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest: testchdir
|
||||
smoketest:
|
||||
$(TINYGO) version
|
||||
$(TINYGO) targets > /dev/null
|
||||
# regression test for #2892
|
||||
@@ -741,10 +712,6 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=thumby examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
# test pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -839,26 +806,16 @@ ifneq ($(XTENSA), 0)
|
||||
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
$(TINYGO) build -size short -o test.bin -target=esp-c3-32s-kit examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=qtpy-esp32c3 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c3 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32-c3-devkit-rust-1 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32c3-12f examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=makerfabs-esp32c3spi35 examples/machinetest
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=tkey examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
ifneq ($(WASM), 0)
|
||||
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
|
||||
@@ -873,7 +830,7 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=rtt examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o test.nro -target=nintendoswitch examples/echo2
|
||||
$(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
|
||||
@@ -932,7 +889,6 @@ endif
|
||||
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/fcntl build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/legacy build/release/tinygo/lib/musl/src
|
||||
@@ -941,7 +897,6 @@ endif
|
||||
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/math build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/misc build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/multibyte build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
|
||||
@@ -949,7 +904,6 @@ endif
|
||||
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/process build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
|
||||
@@ -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
|
||||
|
||||
+26
-76
@@ -61,10 +61,6 @@ type BuildResult struct {
|
||||
// correctly printing test results: the import path isn't always the same as
|
||||
// the path listed on the command line.
|
||||
ImportPath string
|
||||
|
||||
// Map from path to package name. It is needed to attribute binary size to
|
||||
// the right Go package.
|
||||
PackagePathMap map[string]string
|
||||
}
|
||||
|
||||
// packageAction is the struct that is serialized to JSON and hashed, to work as
|
||||
@@ -201,7 +197,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
ABI: config.ABI(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
BuildMode: config.BuildMode(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
SizeLevel: sizeLevel,
|
||||
@@ -246,12 +241,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
return result, err
|
||||
}
|
||||
|
||||
// Store which filesystem paths map to which package name.
|
||||
result.PackagePathMap = make(map[string]string, len(lprogram.Packages))
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
result.PackagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
|
||||
}
|
||||
|
||||
// Create the *ssa.Program. This does not yet build the entire SSA of the
|
||||
// program so it's pretty fast and doesn't need to be parallelized.
|
||||
program := lprogram.LoadSSA()
|
||||
@@ -660,13 +649,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
result.Binary = result.Executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", result.Executable)
|
||||
|
||||
if config.Options.BuildMode == "c-shared" {
|
||||
if !strings.HasPrefix(config.Triple(), "wasm32-") {
|
||||
return result, fmt.Errorf("buildmode c-shared is only supported on wasm at the moment")
|
||||
}
|
||||
ldflags = append(ldflags, "--no-entry")
|
||||
}
|
||||
|
||||
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
||||
// a cache.
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
@@ -700,7 +682,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg
|
||||
for _, filename := range pkg.CFiles {
|
||||
abspath := filepath.Join(pkg.OriginalDir(), filename)
|
||||
abspath := filepath.Join(pkg.Dir, filename)
|
||||
job := &compileJob{
|
||||
description: "compile CGo file " + abspath,
|
||||
run: func(job *compileJob) error {
|
||||
@@ -822,12 +804,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
return fmt.Errorf("could not modify stack sizes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Apply patches of bootloader in the order they appear.
|
||||
if len(config.Target.BootPatches) > 0 {
|
||||
err = applyPatches(result.Executable, config.Target.BootPatches)
|
||||
}
|
||||
|
||||
if config.RP2040BootPatch() {
|
||||
// Patch the second stage bootloader CRC into the .boot2 section
|
||||
err = patchRP2040BootCRC(result.Executable)
|
||||
@@ -847,13 +823,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
args = append(args, "--asyncify")
|
||||
}
|
||||
|
||||
inputFile := result.Binary
|
||||
result.Binary = result.Executable + ".wasmopt"
|
||||
exeunopt := result.Executable
|
||||
|
||||
if config.Options.Work {
|
||||
// Keep the work direction around => don't overwrite the .wasm binary with the optimized version
|
||||
exeunopt += ".pre-wasm-opt"
|
||||
os.Rename(result.Executable, exeunopt)
|
||||
}
|
||||
|
||||
args = append(args,
|
||||
opt,
|
||||
"-g",
|
||||
inputFile,
|
||||
"--output", result.Binary,
|
||||
exeunopt,
|
||||
"--output", result.Executable,
|
||||
)
|
||||
|
||||
wasmopt := goenv.Get("WASMOPT")
|
||||
@@ -883,15 +865,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
// wasm-tools component embed -w wasi:cli/command
|
||||
// $$(tinygo env TINYGOROOT)/lib/wasi-cli/wit/ main.wasm -o embedded.wasm
|
||||
componentEmbedInputFile := result.Binary
|
||||
result.Binary = result.Executable + ".wasm-component-embed"
|
||||
args := []string{
|
||||
"component",
|
||||
"embed",
|
||||
"-w", witWorld,
|
||||
witPackage,
|
||||
componentEmbedInputFile,
|
||||
"-o", result.Binary,
|
||||
result.Executable,
|
||||
"-o", result.Executable,
|
||||
}
|
||||
|
||||
wasmtools := goenv.Get("WASMTOOLS")
|
||||
@@ -904,17 +884,15 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("`wasm-tools component embed` failed: %w", err)
|
||||
return fmt.Errorf("wasm-tools failed: %w", err)
|
||||
}
|
||||
|
||||
// wasm-tools component new embedded.wasm -o component.wasm
|
||||
componentNewInputFile := result.Binary
|
||||
result.Binary = result.Executable + ".wasm-component-new"
|
||||
args = []string{
|
||||
"component",
|
||||
"new",
|
||||
componentNewInputFile,
|
||||
"-o", result.Binary,
|
||||
result.Executable,
|
||||
"-o", result.Executable,
|
||||
}
|
||||
|
||||
if config.Options.PrintCommands != nil {
|
||||
@@ -926,21 +904,24 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("`wasm-tools component new` failed: %w", err)
|
||||
return fmt.Errorf("wasm-tools failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Print code size if requested.
|
||||
if config.Options.PrintSizes != "" {
|
||||
sizes, err := loadProgramSize(result.Executable, result.PackagePathMap)
|
||||
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||
packagePathMap := make(map[string]string, len(lprogram.Packages))
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path()
|
||||
}
|
||||
sizes, err := loadProgramSize(result.Executable, packagePathMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch config.Options.PrintSizes {
|
||||
case "short":
|
||||
if config.Options.PrintSizes == "short" {
|
||||
fmt.Printf(" code data bss | flash ram\n")
|
||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code+sizes.ROData, sizes.Data, sizes.BSS, sizes.Flash(), sizes.RAM())
|
||||
case "full":
|
||||
} else {
|
||||
if !config.Debug() {
|
||||
fmt.Println("warning: data incomplete, remove the -no-debug flag for more detail")
|
||||
}
|
||||
@@ -952,13 +933,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
fmt.Printf("------------------------------- | --------------- | -------\n")
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | total\n", sizes.Code, sizes.ROData, sizes.Data, sizes.BSS, sizes.Code+sizes.ROData+sizes.Data, sizes.Data+sizes.BSS)
|
||||
case "html":
|
||||
const filename = "size-report.html"
|
||||
err := writeSizeReport(sizes, filename, pkgName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Wrote size report to", filename)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1448,23 +1422,6 @@ func printStacks(calculatedStacks []string, stackSizes map[string]functionStackS
|
||||
}
|
||||
}
|
||||
|
||||
func applyPatches(executable string, bootPatches []string) (err error) {
|
||||
for _, patch := range bootPatches {
|
||||
switch patch {
|
||||
case "rp2040":
|
||||
err = patchRP2040BootCRC(executable)
|
||||
// case "rp2350":
|
||||
// err = patchRP2350BootIMAGE_DEF(executable)
|
||||
default:
|
||||
err = errors.New("undefined boot patch name")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("apply boot patch %q: %w", patch, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RP2040 second stage bootloader CRC32 calculation
|
||||
//
|
||||
// Spec: https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf
|
||||
@@ -1476,7 +1433,7 @@ func patchRP2040BootCRC(executable string) error {
|
||||
}
|
||||
|
||||
if len(bytes) != 256 {
|
||||
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes, got %d", len(bytes))
|
||||
return fmt.Errorf("rp2040 .boot2 section must be exactly 256 bytes")
|
||||
}
|
||||
|
||||
// From the 'official' RP2040 checksum script:
|
||||
@@ -1515,10 +1472,3 @@ func lock(path string) func() {
|
||||
|
||||
return func() { flock.Close() }
|
||||
}
|
||||
|
||||
func b2u8(b bool) uint8 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ func TestClangAttributes(t *testing.T) {
|
||||
"k210",
|
||||
"nintendoswitch",
|
||||
"riscv-qemu",
|
||||
"tkey",
|
||||
"wasip1",
|
||||
"wasip2",
|
||||
"wasm",
|
||||
|
||||
@@ -3,6 +3,7 @@ package builder
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -75,3 +76,14 @@ func LookupCommand(name string) (string, error) {
|
||||
}
|
||||
return "", errors.New("none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
|
||||
}
|
||||
|
||||
func execCommand(name string, args ...string) error {
|
||||
name, err := LookupCommand(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
+4
-22
@@ -2,7 +2,6 @@ package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
@@ -24,37 +23,20 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
||||
}
|
||||
|
||||
// Version range supported by TinyGo.
|
||||
const minorMin = 19
|
||||
const minorMax = 23
|
||||
|
||||
// Check that we support this Go toolchain version.
|
||||
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
|
||||
major, minor, err := goenv.GetGorootVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if gorootMajor != 1 || gorootMinor < minorMin || gorootMinor > minorMax {
|
||||
if major != 1 || minor < 19 || minor > 23 {
|
||||
// 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.19 through 1.23, got go%d.%d", gorootMajor, gorootMinor)
|
||||
}
|
||||
|
||||
// Check that the Go toolchain version isn't too new, if we haven't been
|
||||
// compiled with the latest Go version.
|
||||
// This may be a bit too aggressive: if the newer version doesn't change the
|
||||
// Go language we will most likely be able to compile it.
|
||||
buildMajor, buildMinor, _, err := goenv.Parse(runtime.Version())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buildMajor != 1 || buildMinor < gorootMinor {
|
||||
return nil, fmt.Errorf("cannot compile with Go toolchain version go%d.%d (TinyGo was built using toolchain version %s)", gorootMajor, gorootMinor, runtime.Version())
|
||||
return nil, fmt.Errorf("requires go version 1.19 through 1.23, got go%d.%d", major, minor)
|
||||
}
|
||||
|
||||
return &compileopts.Config{
|
||||
Options: options,
|
||||
Target: spec,
|
||||
GoMinorVersion: gorootMinor,
|
||||
GoMinorVersion: minor,
|
||||
TestConfig: options.TestConfig,
|
||||
}, nil
|
||||
}
|
||||
|
||||
+16
-6
@@ -17,6 +17,14 @@ import (
|
||||
// concurrency or performance issues.
|
||||
const jobRunnerDebug = false
|
||||
|
||||
type jobState uint8
|
||||
|
||||
const (
|
||||
jobStateQueued jobState = iota // not yet running
|
||||
jobStateRunning // running
|
||||
jobStateFinished // finished running
|
||||
)
|
||||
|
||||
// compileJob is a single compiler job, comparable to a single Makefile target.
|
||||
// It is used to orchestrate various compiler tasks that can be run in parallel
|
||||
// but that have dependencies and thus have limitations in how they can be run.
|
||||
@@ -47,11 +55,12 @@ func dummyCompileJob(result string) *compileJob {
|
||||
// ordered as such in the job dependencies.
|
||||
func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
if sema == nil {
|
||||
// Have a default, if the semaphore isn't set. This is useful for tests.
|
||||
// Have a default, if the semaphore isn't set. This is useful for
|
||||
// tests.
|
||||
sema = make(chan struct{}, runtime.NumCPU())
|
||||
}
|
||||
if cap(sema) == 0 {
|
||||
return errors.New("cannot run 0 jobs at a time")
|
||||
return errors.New("cannot 0 jobs at a time")
|
||||
}
|
||||
|
||||
// Create a slice of jobs to run, where all dependencies are run in order.
|
||||
@@ -72,10 +81,10 @@ func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
|
||||
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
|
||||
dependents := make(map[*compileJob][]*compileJob, len(jobs))
|
||||
compileJobs := make(map[*compileJob]int)
|
||||
jidx := make(map[*compileJob]int)
|
||||
var ready intHeap
|
||||
for i, job := range jobs {
|
||||
compileJobs[job] = i
|
||||
jidx[job] = i
|
||||
if len(job.dependencies) == 0 {
|
||||
// This job is ready to run.
|
||||
ready.Push(i)
|
||||
@@ -96,7 +105,8 @@ func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
// Create a channel to accept notifications of completion.
|
||||
doneChan := make(chan *compileJob)
|
||||
|
||||
// Send each job in the jobs slice to a worker, taking care of job dependencies.
|
||||
// Send each job in the jobs slice to a worker, taking care of job
|
||||
// dependencies.
|
||||
numRunningJobs := 0
|
||||
var totalTime time.Duration
|
||||
start := time.Now()
|
||||
@@ -146,7 +156,7 @@ func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
delete(wait, completed)
|
||||
if len(wait) == 0 {
|
||||
// This job is now ready to run.
|
||||
ready.Push(compileJobs[j])
|
||||
ready.Push(jidx[j])
|
||||
delete(waiting, j)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,6 @@ var libMusl = Library{
|
||||
"env/*.c",
|
||||
"errno/*.c",
|
||||
"exit/*.c",
|
||||
"fcntl/*.c",
|
||||
"internal/defsysinfo.c",
|
||||
"internal/libc.c",
|
||||
"internal/syscall_ret.c",
|
||||
@@ -128,9 +127,7 @@ var libMusl = Library{
|
||||
"malloc/mallocng/*.c",
|
||||
"mman/*.c",
|
||||
"math/*.c",
|
||||
"misc/*.c",
|
||||
"multibyte/*.c",
|
||||
"signal/" + arch + "/*.s",
|
||||
"signal/*.c",
|
||||
"stdio/*.c",
|
||||
"string/*.c",
|
||||
@@ -138,20 +135,12 @@ var libMusl = Library{
|
||||
"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/"
|
||||
|
||||
@@ -34,7 +34,6 @@ var libPicolibc = Library{
|
||||
"-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",
|
||||
|
||||
@@ -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>
|
||||
+47
-102
@@ -12,7 +12,6 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
@@ -25,7 +24,7 @@ const sizesDebug = false
|
||||
|
||||
// programSize contains size statistics per package of a compiled program.
|
||||
type programSize struct {
|
||||
Packages map[string]*packageSize
|
||||
Packages map[string]packageSize
|
||||
Code uint64
|
||||
ROData uint64
|
||||
Data uint64
|
||||
@@ -53,29 +52,13 @@ func (ps *programSize) RAM() uint64 {
|
||||
return ps.Data + ps.BSS
|
||||
}
|
||||
|
||||
// Return the package size information for a given package path, creating it if
|
||||
// it doesn't exist yet.
|
||||
func (ps *programSize) getPackage(path string) *packageSize {
|
||||
if field, ok := ps.Packages[path]; ok {
|
||||
return field
|
||||
}
|
||||
field := &packageSize{
|
||||
Program: ps,
|
||||
Sub: map[string]*packageSize{},
|
||||
}
|
||||
ps.Packages[path] = field
|
||||
return field
|
||||
}
|
||||
|
||||
// packageSize contains the size of a package, calculated from the linked object
|
||||
// file.
|
||||
type packageSize struct {
|
||||
Program *programSize
|
||||
Code uint64
|
||||
ROData uint64
|
||||
Data uint64
|
||||
BSS uint64
|
||||
Sub map[string]*packageSize
|
||||
Code uint64
|
||||
ROData uint64
|
||||
Data uint64
|
||||
BSS uint64
|
||||
}
|
||||
|
||||
// Flash usage in regular microcontrollers.
|
||||
@@ -88,31 +71,6 @@ func (ps *packageSize) RAM() uint64 {
|
||||
return ps.Data + ps.BSS
|
||||
}
|
||||
|
||||
// Flash usage in regular microcontrollers, as a percentage of the total flash
|
||||
// usage of the program.
|
||||
func (ps *packageSize) FlashPercent() float64 {
|
||||
return float64(ps.Flash()) / float64(ps.Program.Flash()) * 100
|
||||
}
|
||||
|
||||
// Add a single size data point to this package.
|
||||
// This must only be called while calculating package size, not afterwards.
|
||||
func (ps *packageSize) addSize(getField func(*packageSize, bool) *uint64, filename string, size uint64, isVariable bool) {
|
||||
if size == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Add size for the package.
|
||||
*getField(ps, isVariable) += size
|
||||
|
||||
// Add size for file inside package.
|
||||
sub, ok := ps.Sub[filename]
|
||||
if !ok {
|
||||
sub = &packageSize{Program: ps.Program}
|
||||
ps.Sub[filename] = sub
|
||||
}
|
||||
*getField(sub, isVariable) += size
|
||||
}
|
||||
|
||||
// A mapping of a single chunk of code or data to a file path.
|
||||
type addressLine struct {
|
||||
Address uint64
|
||||
@@ -236,22 +194,11 @@ func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset, codeAlignment uint64
|
||||
if !prevLineEntry.EndSequence {
|
||||
// The chunk describes the code from prevLineEntry to
|
||||
// lineEntry.
|
||||
path := prevLineEntry.File.Name
|
||||
if runtime.GOOS == "windows" {
|
||||
// Work around a Clang bug on Windows:
|
||||
// https://github.com/llvm/llvm-project/issues/117317
|
||||
path = strings.ReplaceAll(path, "\\\\", "\\")
|
||||
|
||||
// wasi-libc likes to use forward slashes, but we
|
||||
// canonicalize everything to use backwards slashes as
|
||||
// is common on Windows.
|
||||
path = strings.ReplaceAll(path, "/", "\\")
|
||||
}
|
||||
line := addressLine{
|
||||
Address: prevLineEntry.Address + codeOffset,
|
||||
Length: lineEntry.Address - prevLineEntry.Address,
|
||||
Align: codeAlignment,
|
||||
File: path,
|
||||
File: prevLineEntry.File.Name,
|
||||
}
|
||||
if line.Length != 0 {
|
||||
addresses = append(addresses, line)
|
||||
@@ -826,40 +773,49 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
|
||||
// Now finally determine the binary/RAM size usage per package by going
|
||||
// through each allocated section.
|
||||
sizes := make(map[string]*packageSize)
|
||||
program := &programSize{
|
||||
Packages: sizes,
|
||||
}
|
||||
sizes := make(map[string]packageSize)
|
||||
for _, section := range sections {
|
||||
switch section.Type {
|
||||
case memoryCode:
|
||||
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
if isVariable {
|
||||
return &ps.ROData
|
||||
field.ROData += size
|
||||
} else {
|
||||
field.Code += size
|
||||
}
|
||||
return &ps.Code
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryROData:
|
||||
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
|
||||
return &ps.ROData
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.ROData += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryData:
|
||||
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
|
||||
return &ps.Data
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.Data += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryBSS:
|
||||
readSection(section, addresses, program, func(ps *packageSize, isVariable bool) *uint64 {
|
||||
return &ps.BSS
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.BSS += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryStack:
|
||||
// We store the C stack as a pseudo-package.
|
||||
program.getPackage("C stack").addSize(func(ps *packageSize, isVariable bool) *uint64 {
|
||||
return &ps.BSS
|
||||
}, "", section.Size, false)
|
||||
sizes["C stack"] = packageSize{
|
||||
BSS: section.Size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...and summarize the results.
|
||||
program := &programSize{
|
||||
Packages: sizes,
|
||||
}
|
||||
for _, pkg := range sizes {
|
||||
program.Code += pkg.Code
|
||||
program.ROData += pkg.ROData
|
||||
@@ -870,8 +826,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
}
|
||||
|
||||
// readSection determines for each byte in this section to which package it
|
||||
// belongs.
|
||||
func readSection(section memorySection, addresses []addressLine, program *programSize, getField func(*packageSize, bool) *uint64, packagePathMap map[string]string) {
|
||||
// belongs. It reports this usage through the addSize callback.
|
||||
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
|
||||
// The addr variable tracks at which address we are while going through this
|
||||
// section. We start at the beginning.
|
||||
addr := section.Address
|
||||
@@ -893,9 +849,9 @@ func readSection(section memorySection, addresses []addressLine, program *progra
|
||||
addrAligned := (addr + line.Align - 1) &^ (line.Align - 1)
|
||||
if line.Align > 1 && addrAligned >= line.Address {
|
||||
// It is, assume that's what causes the gap.
|
||||
program.getPackage("(padding)").addSize(getField, "", line.Address-addr, true)
|
||||
addSize("(padding)", line.Address-addr, true)
|
||||
} else {
|
||||
program.getPackage("(unknown)").addSize(getField, "", line.Address-addr, false)
|
||||
addSize("(unknown)", line.Address-addr, false)
|
||||
if sizesDebug {
|
||||
fmt.Printf("%08x..%08x %5d: unknown (gap), alignment=%d\n", addr, line.Address, line.Address-addr, line.Align)
|
||||
}
|
||||
@@ -917,8 +873,7 @@ func readSection(section memorySection, addresses []addressLine, program *progra
|
||||
length = line.Length - (addr - line.Address)
|
||||
}
|
||||
// Finally, mark this chunk of memory as used by the given package.
|
||||
packagePath, filename := findPackagePath(line.File, packagePathMap)
|
||||
program.getPackage(packagePath).addSize(getField, filename, length, line.IsVariable)
|
||||
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
|
||||
addr = line.Address + line.Length
|
||||
}
|
||||
if addr < sectionEnd {
|
||||
@@ -927,9 +882,9 @@ func readSection(section memorySection, addresses []addressLine, program *progra
|
||||
if section.Align > 1 && addrAligned >= sectionEnd {
|
||||
// The gap is caused by the section alignment.
|
||||
// For example, if a .rodata section ends with a non-aligned string.
|
||||
program.getPackage("(padding)").addSize(getField, "", sectionEnd-addr, true)
|
||||
addSize("(padding)", sectionEnd-addr, true)
|
||||
} else {
|
||||
program.getPackage("(unknown)").addSize(getField, "", sectionEnd-addr, false)
|
||||
addSize("(unknown)", sectionEnd-addr, false)
|
||||
if sizesDebug {
|
||||
fmt.Printf("%08x..%08x %5d: unknown (end), alignment=%d\n", addr, sectionEnd, sectionEnd-addr, section.Align)
|
||||
}
|
||||
@@ -939,25 +894,17 @@ func readSection(section memorySection, addresses []addressLine, program *progra
|
||||
|
||||
// findPackagePath returns the Go package (or a pseudo package) for the given
|
||||
// path. It uses some heuristics, for example for some C libraries.
|
||||
func findPackagePath(path string, packagePathMap map[string]string) (packagePath, filename string) {
|
||||
func findPackagePath(path string, packagePathMap map[string]string) string {
|
||||
// Check whether this path is part of one of the compiled packages.
|
||||
packagePath, ok := packagePathMap[filepath.Dir(path)]
|
||||
if ok {
|
||||
// Directory is known as a Go package.
|
||||
// Add the file itself as well.
|
||||
filename = filepath.Base(path)
|
||||
} else {
|
||||
if !ok {
|
||||
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
|
||||
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
|
||||
// package, with a "C" prefix. For example: "C picolibc" for the
|
||||
// baremetal libc.
|
||||
libPath := strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")+string(os.PathSeparator))
|
||||
parts := strings.SplitN(libPath, string(os.PathSeparator), 2)
|
||||
packagePath = "C " + parts[0]
|
||||
filename = parts[1]
|
||||
} else if prefix := filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project", "compiler-rt"); strings.HasPrefix(path, prefix) {
|
||||
// package, with a "C" prefix. For example: "C compiler-rt" for the
|
||||
// compiler runtime library from LLVM.
|
||||
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
|
||||
} else if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "llvm-project")) {
|
||||
packagePath = "C compiler-rt"
|
||||
filename = strings.TrimPrefix(path, prefix+string(os.PathSeparator))
|
||||
} else if packageSymbolRegexp.MatchString(path) {
|
||||
// Parse symbol names like main$alloc or runtime$string.
|
||||
packagePath = path[:strings.LastIndex(path, "$")]
|
||||
@@ -980,11 +927,9 @@ func findPackagePath(path string, packagePathMap map[string]string) (packagePath
|
||||
// fixed in the compiler.
|
||||
packagePath = "-"
|
||||
} else {
|
||||
// This is some other path. Not sure what it is, so just emit its
|
||||
// directory as a fallback.
|
||||
packagePath = filepath.Dir(path)
|
||||
filename = filepath.Base(path)
|
||||
// This is some other path. Not sure what it is, so just emit its directory.
|
||||
packagePath = filepath.Dir(path) // fallback
|
||||
}
|
||||
}
|
||||
return
|
||||
return packagePath
|
||||
}
|
||||
|
||||
+23
-71
@@ -1,7 +1,6 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -42,9 +41,9 @@ func TestBinarySize(t *testing.T) {
|
||||
// This is a small number of very diverse targets that we want to test.
|
||||
tests := []sizeTest{
|
||||
// microcontrollers
|
||||
{"hifive1b", "examples/echo", 4600, 280, 0, 2268},
|
||||
{"microbit", "examples/serial", 2908, 388, 8, 2272},
|
||||
{"wioterminal", "examples/pininterrupt", 6140, 1484, 116, 6824},
|
||||
{"hifive1b", "examples/echo", 4484, 280, 0, 2252},
|
||||
{"microbit", "examples/serial", 2732, 388, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 6016, 1484, 116, 6816},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
@@ -56,7 +55,26 @@ func TestBinarySize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Build the binary.
|
||||
result := buildBinary(t, tc.target, tc.path)
|
||||
options := compileopts.Options{
|
||||
Target: tc.target,
|
||||
Opt: "z",
|
||||
Semaphore: sema,
|
||||
InterpTimeout: 60 * time.Second,
|
||||
Debug: true,
|
||||
VerifyIR: true,
|
||||
}
|
||||
target, err := compileopts.LoadTarget(&options)
|
||||
if err != nil {
|
||||
t.Fatal("could not load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &options,
|
||||
Target: target,
|
||||
}
|
||||
result, err := Build(tc.path, "", t.TempDir(), config)
|
||||
if err != nil {
|
||||
t.Fatal("could not build:", err)
|
||||
}
|
||||
|
||||
// Check whether the size of the binary matches the expected size.
|
||||
sizes, err := loadProgramSize(result.Executable, nil)
|
||||
@@ -72,69 +90,3 @@ func TestBinarySize(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
+6
-35
@@ -14,45 +14,16 @@ import (
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
func runCCompiler(flags ...string) error {
|
||||
// Find the right command to run Clang.
|
||||
var cmd *exec.Cmd
|
||||
if hasBuiltinTools {
|
||||
// Compile this with the internal Clang compiler.
|
||||
cmd = exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
|
||||
} else {
|
||||
// Compile this with an external invocation of the Clang compiler.
|
||||
name, err := LookupCommand("clang")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd = exec.Command(name, flags...)
|
||||
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// Make sure the command doesn't use any environmental variables.
|
||||
// Most importantly, it should not use C_INCLUDE_PATH and the like.
|
||||
cmd.Env = []string{}
|
||||
|
||||
// Let some environment variables through. One important one is the
|
||||
// temporary directory, especially on Windows it looks like Clang breaks if
|
||||
// the temporary directory has not been set.
|
||||
// See: https://github.com/tinygo-org/tinygo/issues/4557
|
||||
// Also see: https://github.com/llvm/llvm-project/blob/release/18.x/llvm/lib/Support/Unix/Path.inc#L1435
|
||||
for _, env := range os.Environ() {
|
||||
// We could parse the key and look it up in a map, but since there are
|
||||
// only a few keys iterating through them is easier and maybe even
|
||||
// faster.
|
||||
for _, prefix := range []string{"TMPDIR=", "TMP=", "TEMP=", "TEMPDIR="} {
|
||||
if strings.HasPrefix(env, prefix) {
|
||||
cmd.Env = append(cmd.Env, env)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.Run()
|
||||
// Compile this with an external invocation of the Clang compiler.
|
||||
return execCommand("clang", flags...)
|
||||
}
|
||||
|
||||
// link invokes a linker with the given name and flags.
|
||||
|
||||
+6
-179
@@ -18,7 +18,6 @@ import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -43,7 +42,6 @@ type cgoPackage struct {
|
||||
fset *token.FileSet
|
||||
tokenFiles map[string]*token.File
|
||||
definedGlobally map[string]ast.Node
|
||||
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
|
||||
anonDecls map[interface{}]string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
@@ -82,13 +80,6 @@ type bitfieldInfo struct {
|
||||
endBit int64 // may be 0 meaning "until the end of the field"
|
||||
}
|
||||
|
||||
// Information about a #cgo noescape line in the source code.
|
||||
type noescapingFunc struct {
|
||||
name string
|
||||
pos token.Pos
|
||||
used bool // true if used somewhere in the source (for proper error reporting)
|
||||
}
|
||||
|
||||
// cgoAliases list type aliases between Go and C, for types that are equivalent
|
||||
// in both languages. See addTypeAliases.
|
||||
var cgoAliases = map[string]string{
|
||||
@@ -147,8 +138,7 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
// first.
|
||||
// These functions will be modified to get a "C." prefix, so the source below
|
||||
// doesn't reflect the final AST.
|
||||
const generatedGoFilePrefixBase = `
|
||||
import "syscall"
|
||||
const generatedGoFilePrefix = `
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -172,81 +162,6 @@ func __GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func __CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func __get_errno_num() uintptr
|
||||
`
|
||||
|
||||
const generatedGoFilePrefixOther = generatedGoFilePrefixBase + `
|
||||
func __get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
`
|
||||
|
||||
// Windows uses fake errno values in the syscall package.
|
||||
// See for example: https://github.com/golang/go/issues/23468
|
||||
// TinyGo uses mingw-w64 though, which does have defined errno values. Since the
|
||||
// syscall package is the standard library one we can't change it, but we can
|
||||
// map the errno values to match the values in the syscall package.
|
||||
// Source of the errno values: lib/mingw-w64/mingw-w64-headers/crt/errno.h
|
||||
const generatedGoFilePrefixWindows = generatedGoFilePrefixBase + `
|
||||
var __errno_mapping = [...]syscall.Errno{
|
||||
1: syscall.EPERM,
|
||||
2: syscall.ENOENT,
|
||||
3: syscall.ESRCH,
|
||||
4: syscall.EINTR,
|
||||
5: syscall.EIO,
|
||||
6: syscall.ENXIO,
|
||||
7: syscall.E2BIG,
|
||||
8: syscall.ENOEXEC,
|
||||
9: syscall.EBADF,
|
||||
10: syscall.ECHILD,
|
||||
11: syscall.EAGAIN,
|
||||
12: syscall.ENOMEM,
|
||||
13: syscall.EACCES,
|
||||
14: syscall.EFAULT,
|
||||
16: syscall.EBUSY,
|
||||
17: syscall.EEXIST,
|
||||
18: syscall.EXDEV,
|
||||
19: syscall.ENODEV,
|
||||
20: syscall.ENOTDIR,
|
||||
21: syscall.EISDIR,
|
||||
22: syscall.EINVAL,
|
||||
23: syscall.ENFILE,
|
||||
24: syscall.EMFILE,
|
||||
25: syscall.ENOTTY,
|
||||
27: syscall.EFBIG,
|
||||
28: syscall.ENOSPC,
|
||||
29: syscall.ESPIPE,
|
||||
30: syscall.EROFS,
|
||||
31: syscall.EMLINK,
|
||||
32: syscall.EPIPE,
|
||||
33: syscall.EDOM,
|
||||
34: syscall.ERANGE,
|
||||
36: syscall.EDEADLK,
|
||||
38: syscall.ENAMETOOLONG,
|
||||
39: syscall.ENOLCK,
|
||||
40: syscall.ENOSYS,
|
||||
41: syscall.ENOTEMPTY,
|
||||
42: syscall.EILSEQ,
|
||||
}
|
||||
|
||||
func __get_errno() error {
|
||||
num := C.__get_errno_num()
|
||||
if num < uintptr(len(__errno_mapping)) {
|
||||
if mapped := __errno_mapping[num]; mapped != 0 {
|
||||
return mapped
|
||||
}
|
||||
}
|
||||
return syscall.Errno(num)
|
||||
}
|
||||
`
|
||||
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
@@ -256,7 +171,7 @@ func __get_errno() error {
|
||||
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
|
||||
// hashes of the accessed C header files. If there is one or more error, it
|
||||
// returns these in the []error slice but still modifies the AST.
|
||||
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string, goos string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
|
||||
func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cflags []string) ([]*ast.File, []string, []string, []string, map[string][]byte, []error) {
|
||||
p := &cgoPackage{
|
||||
packageName: files[0].Name.Name,
|
||||
currentDir: dir,
|
||||
@@ -264,7 +179,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
fset: fset,
|
||||
tokenFiles: map[string]*token.File{},
|
||||
definedGlobally: map[string]ast.Node{},
|
||||
noescapingFuncs: map[string]*noescapingFunc{},
|
||||
anonDecls: map[interface{}]string{},
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
@@ -289,12 +203,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
// Construct a new in-memory AST for CGo declarations of this package.
|
||||
// The first part is written as Go code that is then parsed, but more code
|
||||
// is added later to the AST to declare functions, globals, etc.
|
||||
goCode := "package " + files[0].Name.Name + "\n\n"
|
||||
if goos == "windows" {
|
||||
goCode += generatedGoFilePrefixWindows
|
||||
} else {
|
||||
goCode += generatedGoFilePrefixOther
|
||||
}
|
||||
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
|
||||
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
|
||||
if err != nil {
|
||||
// This is always a bug in the cgo package.
|
||||
@@ -309,7 +218,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
switch decl.Name.Name {
|
||||
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes", "CBytes", "__CBytes", "__get_errno_num", "__get_errno", "__errno_mapping":
|
||||
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
|
||||
// Adjust the name to have a "C." prefix so it is correctly
|
||||
// resolved.
|
||||
decl.Name.Name = "C." + decl.Name.Name
|
||||
@@ -428,22 +337,6 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
})
|
||||
}
|
||||
|
||||
// Show an error when a #cgo noescape line isn't used in practice.
|
||||
// This matches upstream Go. I think the goal is to avoid issues with
|
||||
// misspelled function names, which seems very useful.
|
||||
var unusedNoescapeLines []*noescapingFunc
|
||||
for _, value := range p.noescapingFuncs {
|
||||
if !value.used {
|
||||
unusedNoescapeLines = append(unusedNoescapeLines, value)
|
||||
}
|
||||
}
|
||||
sort.SliceStable(unusedNoescapeLines, func(i, j int) bool {
|
||||
return unusedNoescapeLines[i].pos < unusedNoescapeLines[j].pos
|
||||
})
|
||||
for _, value := range unusedNoescapeLines {
|
||||
p.addError(value.pos, fmt.Sprintf("function %#v in #cgo noescape line is not used", value.name))
|
||||
}
|
||||
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
@@ -509,33 +402,6 @@ func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) strin
|
||||
}
|
||||
text = text[:lineStart] + string(spaces) + text[lineEnd:]
|
||||
|
||||
allFields := strings.Fields(line[4:])
|
||||
switch allFields[0] {
|
||||
case "noescape":
|
||||
// The code indicates that pointer parameters will not be captured
|
||||
// by the called C function.
|
||||
if len(allFields) < 2 {
|
||||
p.addErrorAfter(pos, text[:lineStart], "missing function name in #cgo noescape line")
|
||||
continue
|
||||
}
|
||||
if len(allFields) > 2 {
|
||||
p.addErrorAfter(pos, text[:lineStart], "multiple function names in #cgo noescape line")
|
||||
continue
|
||||
}
|
||||
name := allFields[1]
|
||||
p.noescapingFuncs[name] = &noescapingFunc{
|
||||
name: name,
|
||||
pos: pos,
|
||||
used: false,
|
||||
}
|
||||
continue
|
||||
case "nocallback":
|
||||
// We don't do anything special when calling a C function, so there
|
||||
// appears to be no optimization that we can do here.
|
||||
// Accept, but ignore the parameter for compatibility.
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the text before the colon in the #cgo directive.
|
||||
colon := strings.IndexByte(line, ':')
|
||||
if colon < 0 {
|
||||
@@ -1275,7 +1141,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
|
||||
if alias := cgoAliases["C."+name]; alias != "" {
|
||||
return alias
|
||||
}
|
||||
node := f.getASTDeclNode(name, found)
|
||||
node := f.getASTDeclNode(name, found, iscall)
|
||||
if node, ok := node.(*ast.FuncDecl); ok {
|
||||
if !iscall {
|
||||
return node.Name.Name + "$funcaddr"
|
||||
@@ -1287,7 +1153,7 @@ func (f *cgoFile) getASTDeclName(name string, found clangCursor, iscall bool) st
|
||||
|
||||
// getASTDeclNode will declare the given C AST node (if not already defined) and
|
||||
// returns it.
|
||||
func (f *cgoFile) getASTDeclNode(name string, found clangCursor) ast.Node {
|
||||
func (f *cgoFile) getASTDeclNode(name string, found clangCursor, iscall bool) ast.Node {
|
||||
if node, ok := f.defined[name]; ok {
|
||||
// Declaration was found in the current file, so return it immediately.
|
||||
return node
|
||||
@@ -1406,45 +1272,6 @@ extern __typeof(%s) %s __attribute__((alias(%#v)));
|
||||
// separate namespace (no _Cgo_ hacks like in gc).
|
||||
func (f *cgoFile) walker(cursor *astutil.Cursor, names map[string]clangCursor) bool {
|
||||
switch node := cursor.Node().(type) {
|
||||
case *ast.AssignStmt:
|
||||
// An assign statement could be something like this:
|
||||
//
|
||||
// val, errno := C.some_func()
|
||||
//
|
||||
// Check whether it looks like that, and if so, read the errno value and
|
||||
// return it as the second return value. The call will be transformed
|
||||
// into something like this:
|
||||
//
|
||||
// val, errno := C.some_func(), C.__get_errno()
|
||||
if len(node.Lhs) != 2 || len(node.Rhs) != 1 {
|
||||
return true
|
||||
}
|
||||
rhs, ok := node.Rhs[0].(*ast.CallExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
fun, ok := rhs.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
x, ok := fun.X.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if found, ok := names[fun.Sel.Name]; ok && x.Name == "C" {
|
||||
// Replace "C"."some_func" into "C.somefunc".
|
||||
rhs.Fun = &ast.Ident{
|
||||
NamePos: x.NamePos,
|
||||
Name: f.getASTDeclName(fun.Sel.Name, found, true),
|
||||
}
|
||||
// Add the errno value as the second value in the statement.
|
||||
node.Rhs = append(node.Rhs, &ast.CallExpr{
|
||||
Fun: &ast.Ident{
|
||||
NamePos: node.Lhs[1].End(),
|
||||
Name: "C.__get_errno",
|
||||
},
|
||||
})
|
||||
}
|
||||
case *ast.CallExpr:
|
||||
fun, ok := node.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
|
||||
+4
-23
@@ -56,7 +56,7 @@ func TestCGo(t *testing.T) {
|
||||
}
|
||||
|
||||
// Process the AST with CGo.
|
||||
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags, "linux")
|
||||
cgoFiles, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", "main", fset, cflags)
|
||||
|
||||
// Check the AST for type errors.
|
||||
var typecheckErrors []error
|
||||
@@ -64,7 +64,7 @@ func TestCGo(t *testing.T) {
|
||||
Error: func(err error) {
|
||||
typecheckErrors = append(typecheckErrors, err)
|
||||
},
|
||||
Importer: newSimpleImporter(),
|
||||
Importer: simpleImporter{},
|
||||
Sizes: types.SizesFor("gccgo", "arm"),
|
||||
}
|
||||
_, err = config.Check("", fset, append([]*ast.File{f}, cgoFiles...), nil)
|
||||
@@ -202,33 +202,14 @@ func Test_cgoPackage_isEquivalentAST(t *testing.T) {
|
||||
}
|
||||
|
||||
// simpleImporter implements the types.Importer interface, but only allows
|
||||
// importing the syscall and unsafe packages.
|
||||
// importing the unsafe package.
|
||||
type simpleImporter struct {
|
||||
syscallPkg *types.Package
|
||||
}
|
||||
|
||||
func newSimpleImporter() *simpleImporter {
|
||||
i := &simpleImporter{}
|
||||
|
||||
// Implement a dummy syscall package with the Errno type.
|
||||
i.syscallPkg = types.NewPackage("syscall", "syscall")
|
||||
obj := types.NewTypeName(token.NoPos, i.syscallPkg, "Errno", nil)
|
||||
named := types.NewNamed(obj, nil, nil)
|
||||
i.syscallPkg.Scope().Insert(obj)
|
||||
named.SetUnderlying(types.Typ[types.Uintptr])
|
||||
sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(), types.NewTuple(types.NewParam(token.NoPos, i.syscallPkg, "", types.Typ[types.String])), false)
|
||||
named.AddMethod(types.NewFunc(token.NoPos, i.syscallPkg, "Error", sig))
|
||||
i.syscallPkg.MarkComplete()
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// Import implements the Importer interface. For testing usage only: it only
|
||||
// supports importing the unsafe package.
|
||||
func (i *simpleImporter) Import(path string) (*types.Package, error) {
|
||||
func (i simpleImporter) Import(path string) (*types.Package, error) {
|
||||
switch path {
|
||||
case "syscall":
|
||||
return i.syscallPkg, nil
|
||||
case "unsafe":
|
||||
return types.Unsafe, nil
|
||||
default:
|
||||
|
||||
+4
-136
@@ -54,72 +54,8 @@ func init() {
|
||||
}
|
||||
|
||||
// parseConst parses the given string as a C constant.
|
||||
func parseConst(pos token.Pos, fset *token.FileSet, value string, params []ast.Expr, callerPos token.Pos, f *cgoFile) (ast.Expr, *scanner.Error) {
|
||||
t := newTokenizer(pos, fset, value, f)
|
||||
|
||||
// If params is non-nil (could be a zero length slice), this const is
|
||||
// actually a function-call like expression from another macro.
|
||||
// This means we have to parse a string like "(a, b) (a+b)".
|
||||
// We do this by parsing the parameters at the start and then treating the
|
||||
// following like a normal constant expression.
|
||||
if params != nil {
|
||||
// Parse opening paren.
|
||||
if t.curToken != token.LPAREN {
|
||||
return nil, unexpectedToken(t, token.LPAREN)
|
||||
}
|
||||
t.Next()
|
||||
|
||||
// Parse parameters (identifiers) and closing paren.
|
||||
var paramIdents []string
|
||||
for i := 0; ; i++ {
|
||||
if i == 0 && t.curToken == token.RPAREN {
|
||||
// No parameters, break early.
|
||||
t.Next()
|
||||
break
|
||||
}
|
||||
|
||||
// Read the parameter name.
|
||||
if t.curToken != token.IDENT {
|
||||
return nil, unexpectedToken(t, token.IDENT)
|
||||
}
|
||||
paramIdents = append(paramIdents, t.curValue)
|
||||
t.Next()
|
||||
|
||||
// Read the next token: either a continuation (comma) or end of list
|
||||
// (rparen).
|
||||
if t.curToken == token.RPAREN {
|
||||
// End of parameter list.
|
||||
t.Next()
|
||||
break
|
||||
} else if t.curToken == token.COMMA {
|
||||
// Comma, so there will be another parameter name.
|
||||
t.Next()
|
||||
} else {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "unexpected token " + t.curToken.String() + " inside macro parameters, expected ',' or ')'",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Report an error if there is a mismatch in parameter length.
|
||||
// The error is reported at the location of the closing paren from the
|
||||
// caller location.
|
||||
if len(params) != len(paramIdents) {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(callerPos),
|
||||
Msg: fmt.Sprintf("unexpected number of parameters: expected %d, got %d", len(paramIdents), len(params)),
|
||||
}
|
||||
}
|
||||
|
||||
// Assign values to the parameters.
|
||||
// These parameter names are closer in 'scope' than other identifiers so
|
||||
// will be used first when parsing an identifier.
|
||||
for i, name := range paramIdents {
|
||||
t.params[name] = params[i]
|
||||
}
|
||||
}
|
||||
|
||||
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
|
||||
t := newTokenizer(pos, fset, value)
|
||||
expr, err := parseConstExpr(t, precedenceLowest)
|
||||
t.Next()
|
||||
if t.curToken != token.EOF {
|
||||
@@ -160,68 +96,6 @@ func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
|
||||
}
|
||||
|
||||
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
// If the identifier is one of the parameters of this function-like macro,
|
||||
// use the parameter value.
|
||||
if val, ok := t.params[t.curValue]; ok {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
if t.f != nil {
|
||||
// Check whether this identifier is actually a macro "call" with
|
||||
// parameters. In that case, we should parse the parameters and pass it
|
||||
// on to a new invocation of parseConst.
|
||||
if t.peekToken == token.LPAREN {
|
||||
if cursor, ok := t.f.names[t.curValue]; ok && t.f.isFunctionLikeMacro(cursor) {
|
||||
// We know the current and peek tokens (the peek one is the '('
|
||||
// token). So skip ahead until the current token is the first
|
||||
// unknown token.
|
||||
t.Next()
|
||||
t.Next()
|
||||
|
||||
// Parse the list of parameters until ')' (rparen) is found.
|
||||
params := []ast.Expr{}
|
||||
for i := 0; ; i++ {
|
||||
if i == 0 && t.curToken == token.RPAREN {
|
||||
break
|
||||
}
|
||||
x, err := parseConstExpr(t, precedenceLowest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = append(params, x)
|
||||
t.Next()
|
||||
if t.curToken == token.COMMA {
|
||||
t.Next()
|
||||
} else if t.curToken == token.RPAREN {
|
||||
break
|
||||
} else {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "unexpected token " + t.curToken.String() + ", ',' or ')'",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the macro value and use it as the identifier value.
|
||||
rparen := t.curPos
|
||||
pos, text := t.f.getMacro(cursor)
|
||||
return parseConst(pos, t.fset, text, params, rparen, t.f)
|
||||
}
|
||||
}
|
||||
|
||||
// Normally the name is something defined in the file (like another
|
||||
// macro) which we get the declaration from using getASTDeclName.
|
||||
// This ensures that names that are only referenced inside a macro are
|
||||
// still getting defined.
|
||||
if cursor, ok := t.f.names[t.curValue]; ok {
|
||||
return &ast.Ident{
|
||||
NamePos: t.curPos,
|
||||
Name: t.f.getASTDeclName(t.curValue, cursor, false),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// t.f is nil during testing. This is a fallback.
|
||||
return &ast.Ident{
|
||||
NamePos: t.curPos,
|
||||
Name: "C." + t.curValue,
|
||||
@@ -290,25 +164,21 @@ func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
||||
|
||||
// tokenizer reads C source code and converts it to Go tokens.
|
||||
type tokenizer struct {
|
||||
f *cgoFile
|
||||
curPos, peekPos token.Pos
|
||||
fset *token.FileSet
|
||||
curToken, peekToken token.Token
|
||||
curValue, peekValue string
|
||||
buf string
|
||||
params map[string]ast.Expr
|
||||
}
|
||||
|
||||
// newTokenizer initializes a new tokenizer, positioned at the first token in
|
||||
// the string.
|
||||
func newTokenizer(start token.Pos, fset *token.FileSet, buf string, f *cgoFile) *tokenizer {
|
||||
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
|
||||
t := &tokenizer{
|
||||
f: f,
|
||||
peekPos: start,
|
||||
fset: fset,
|
||||
buf: buf,
|
||||
peekToken: token.ILLEGAL,
|
||||
params: make(map[string]ast.Expr),
|
||||
}
|
||||
// Parse the first two tokens (cur and peek).
|
||||
t.Next()
|
||||
@@ -360,7 +230,7 @@ func (t *tokenizer) Next() {
|
||||
t.peekValue = t.buf[:2]
|
||||
t.buf = t.buf[2:]
|
||||
return
|
||||
case c == '(' || c == ')' || c == ',' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
|
||||
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^':
|
||||
// Single-character tokens.
|
||||
// TODO: ++ (increment) and -- (decrement) operators.
|
||||
switch c {
|
||||
@@ -368,8 +238,6 @@ func (t *tokenizer) Next() {
|
||||
t.peekToken = token.LPAREN
|
||||
case ')':
|
||||
t.peekToken = token.RPAREN
|
||||
case ',':
|
||||
t.peekToken = token.COMMA
|
||||
case '+':
|
||||
t.peekToken = token.ADD
|
||||
case '-':
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ func TestParseConst(t *testing.T) {
|
||||
} {
|
||||
fset := token.NewFileSet()
|
||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||
expr, err := parseConst(startPos, fset, tc.C, nil, token.NoPos, nil)
|
||||
expr, err := parseConst(startPos, fset, tc.C)
|
||||
s := "<invalid>"
|
||||
if err != nil {
|
||||
if !strings.HasPrefix(tc.Go, "error: ") {
|
||||
|
||||
+41
-82
@@ -63,24 +63,10 @@ long long tinygo_clang_getEnumConstantDeclValue(GoCXCursor c);
|
||||
CXType tinygo_clang_getEnumDeclIntegerType(GoCXCursor c);
|
||||
unsigned tinygo_clang_Cursor_isAnonymous(GoCXCursor c);
|
||||
unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
||||
unsigned tinygo_clang_Cursor_isMacroFunctionLike(GoCXCursor c);
|
||||
|
||||
// Fix some warnings on Windows ARM. Without the __declspec(dllexport), it gives warnings like this:
|
||||
// In file included from _cgo_export.c:4:
|
||||
// cgo-gcc-export-header-prolog:49:34: warning: redeclaration of 'tinygo_clang_globals_visitor' should not add 'dllexport' attribute [-Wdll-attribute-on-redeclaration]
|
||||
// libclang.go:68:5: note: previous declaration is here
|
||||
// See: https://github.com/golang/go/issues/49721
|
||||
#if defined(_WIN32)
|
||||
#define CGO_DECL // __declspec(dllexport)
|
||||
#else
|
||||
#define CGO_DECL
|
||||
#endif
|
||||
|
||||
CGO_DECL
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
CGO_DECL
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
CGO_DECL
|
||||
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
@@ -269,18 +255,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
},
|
||||
},
|
||||
}
|
||||
var doc []string
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
doc = append(doc, "//go:variadic")
|
||||
}
|
||||
if _, ok := f.noescapingFuncs[name]; ok {
|
||||
doc = append(doc, "//go:noescape")
|
||||
f.noescapingFuncs[name].used = true
|
||||
}
|
||||
if len(doc) != 0 {
|
||||
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
|
||||
Slash: pos - 1,
|
||||
Text: strings.Join(doc, "\n"),
|
||||
Text: "//go:variadic",
|
||||
})
|
||||
}
|
||||
for i := 0; i < numArgs; i++ {
|
||||
@@ -392,8 +370,45 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
case C.CXCursor_MacroDefinition:
|
||||
tokenPos, value := f.getMacro(c)
|
||||
expr, scannerError := parseConst(tokenPos, f.fset, value, nil, token.NoPos, f)
|
||||
// Extract tokens from the Clang tokenizer.
|
||||
// See: https://stackoverflow.com/a/19074846/559350
|
||||
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
||||
var rawTokens *C.CXToken
|
||||
var numTokens C.unsigned
|
||||
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
|
||||
tokens := unsafe.Slice(rawTokens, numTokens)
|
||||
// Convert this range of tokens back to source text.
|
||||
// Ugly, but it works well enough.
|
||||
sourceBuf := &bytes.Buffer{}
|
||||
var startOffset int
|
||||
for i, token := range tokens {
|
||||
spelling := getString(C.clang_getTokenSpelling(tu, token))
|
||||
location := C.clang_getTokenLocation(tu, token)
|
||||
var tokenOffset C.unsigned
|
||||
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
|
||||
if i == 0 {
|
||||
// The first token is the macro name itself.
|
||||
// Skip it (after using its location).
|
||||
startOffset = int(tokenOffset) + len(name)
|
||||
} else {
|
||||
// Later tokens are the macro contents.
|
||||
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
|
||||
// Pad the source text with whitespace (that must have been
|
||||
// present in the original source as well).
|
||||
sourceBuf.WriteByte(' ')
|
||||
}
|
||||
sourceBuf.WriteString(spelling)
|
||||
}
|
||||
}
|
||||
C.clang_disposeTokens(tu, rawTokens, numTokens)
|
||||
value := sourceBuf.String()
|
||||
// Try to convert this #define into a Go constant expression.
|
||||
tokenPos := token.NoPos
|
||||
if pos != token.NoPos {
|
||||
tokenPos = pos + token.Pos(len(name))
|
||||
}
|
||||
expr, scannerError := parseConst(tokenPos, f.fset, value)
|
||||
if scannerError != nil {
|
||||
f.errors = append(f.errors, *scannerError)
|
||||
return nil, nil
|
||||
@@ -473,62 +488,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether this is a macro that's also function-like, like this:
|
||||
//
|
||||
// #define add(a, b) (a+b)
|
||||
func (f *cgoFile) isFunctionLikeMacro(c clangCursor) bool {
|
||||
if C.tinygo_clang_getCursorKind(c) != C.CXCursor_MacroDefinition {
|
||||
return false
|
||||
}
|
||||
return C.tinygo_clang_Cursor_isMacroFunctionLike(c) != 0
|
||||
}
|
||||
|
||||
// Get the macro value: the position in the source file and the string value of
|
||||
// the macro.
|
||||
func (f *cgoFile) getMacro(c clangCursor) (pos token.Pos, value string) {
|
||||
// Extract tokens from the Clang tokenizer.
|
||||
// See: https://stackoverflow.com/a/19074846/559350
|
||||
sourceRange := C.tinygo_clang_getCursorExtent(c)
|
||||
tu := C.tinygo_clang_Cursor_getTranslationUnit(c)
|
||||
var rawTokens *C.CXToken
|
||||
var numTokens C.unsigned
|
||||
C.clang_tokenize(tu, sourceRange, &rawTokens, &numTokens)
|
||||
tokens := unsafe.Slice(rawTokens, numTokens)
|
||||
defer C.clang_disposeTokens(tu, rawTokens, numTokens)
|
||||
|
||||
// Convert this range of tokens back to source text.
|
||||
// Ugly, but it works well enough.
|
||||
sourceBuf := &bytes.Buffer{}
|
||||
var startOffset int
|
||||
for i, token := range tokens {
|
||||
spelling := getString(C.clang_getTokenSpelling(tu, token))
|
||||
location := C.clang_getTokenLocation(tu, token)
|
||||
var tokenOffset C.unsigned
|
||||
C.clang_getExpansionLocation(location, nil, nil, nil, &tokenOffset)
|
||||
if i == 0 {
|
||||
// The first token is the macro name itself.
|
||||
// Skip it (after using its location).
|
||||
startOffset = int(tokenOffset)
|
||||
} else {
|
||||
// Later tokens are the macro contents.
|
||||
for int(tokenOffset) > (startOffset + sourceBuf.Len()) {
|
||||
// Pad the source text with whitespace (that must have been
|
||||
// present in the original source as well).
|
||||
sourceBuf.WriteByte(' ')
|
||||
}
|
||||
sourceBuf.WriteString(spelling)
|
||||
}
|
||||
}
|
||||
value = sourceBuf.String()
|
||||
|
||||
// Obtain the position of this token. This is the position of the first
|
||||
// character in the 'value' string and is used to report errors at the
|
||||
// correct location in the source file.
|
||||
pos = f.getCursorPosition(c)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getString(clangString C.CXString) (s string) {
|
||||
rawString := C.clang_getCString(clangString)
|
||||
s = C.GoString(rawString)
|
||||
|
||||
@@ -84,7 +84,3 @@ unsigned tinygo_clang_Cursor_isAnonymous(CXCursor c) {
|
||||
unsigned tinygo_clang_Cursor_isBitField(CXCursor c) {
|
||||
return clang_Cursor_isBitField(c);
|
||||
}
|
||||
|
||||
unsigned tinygo_clang_Cursor_isMacroFunctionLike(CXCursor c) {
|
||||
return clang_Cursor_isMacroFunctionLike(c);
|
||||
}
|
||||
|
||||
Vendored
-15
@@ -1,6 +1,5 @@
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -25,20 +24,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
|
||||
Vendored
-16
@@ -3,26 +3,10 @@ package main
|
||||
/*
|
||||
#define foo 3
|
||||
#define bar foo
|
||||
|
||||
#define unreferenced 4
|
||||
#define referenced unreferenced
|
||||
|
||||
#define fnlike() 5
|
||||
#define fnlike_val fnlike()
|
||||
#define square(n) (n*n)
|
||||
#define square_val square(20)
|
||||
#define add(a, b) (a + b)
|
||||
#define add_val add(3, 5)
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const (
|
||||
Foo = C.foo
|
||||
Bar = C.bar
|
||||
|
||||
Baz = C.referenced
|
||||
|
||||
fnlike = C.fnlike_val
|
||||
square = C.square_val
|
||||
add = C.add_val
|
||||
)
|
||||
|
||||
Vendored
-20
@@ -1,6 +1,5 @@
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -25,20 +24,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
@@ -55,8 +40,3 @@ type (
|
||||
|
||||
const C.foo = 3
|
||||
const C.bar = C.foo
|
||||
const C.unreferenced = 4
|
||||
const C.referenced = C.unreferenced
|
||||
const C.fnlike_val = 5
|
||||
const C.square_val = (20 * 20)
|
||||
const C.add_val = (3 + 5)
|
||||
|
||||
Vendored
-13
@@ -10,11 +10,6 @@ typedef struct {
|
||||
|
||||
typedef someType noType; // undefined type
|
||||
|
||||
// Some invalid noescape lines
|
||||
#cgo noescape
|
||||
#cgo noescape foo bar
|
||||
#cgo noescape unusedFunction
|
||||
|
||||
#define SOME_CONST_1 5) // invalid const syntax
|
||||
#define SOME_CONST_2 6) // const not used (so no error)
|
||||
#define SOME_CONST_3 1234 // const too large for byte
|
||||
@@ -31,11 +26,6 @@ import "C"
|
||||
// #warning another warning
|
||||
import "C"
|
||||
|
||||
// #define add(a, b) (a+b)
|
||||
// #define add_toomuch add(1, 2, 3)
|
||||
// #define add_toolittle add(1)
|
||||
import "C"
|
||||
|
||||
// Make sure that errors for the following lines won't change with future
|
||||
// additions to the CGo preamble.
|
||||
//
|
||||
@@ -61,7 +51,4 @@ var (
|
||||
// constants passed by a command line parameter
|
||||
_ = C.SOME_PARAM_CONST_invalid
|
||||
_ = C.SOME_PARAM_CONST_valid
|
||||
|
||||
_ = C.add_toomuch
|
||||
_ = C.add_toolittle
|
||||
)
|
||||
|
||||
Vendored
+5
-27
@@ -1,17 +1,12 @@
|
||||
// CGo errors:
|
||||
// testdata/errors.go:14:1: missing function name in #cgo noescape line
|
||||
// testdata/errors.go:15:1: multiple function names in #cgo noescape line
|
||||
// testdata/errors.go:4:2: warning: some warning
|
||||
// testdata/errors.go:11:9: error: unknown type name 'someType'
|
||||
// testdata/errors.go:31:5: warning: another warning
|
||||
// testdata/errors.go:18:23: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:26:26: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:21:33: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:22:34: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:26:5: warning: another warning
|
||||
// testdata/errors.go:13:23: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:21:26: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:16:33: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:17:34: unexpected token ), expected end of expression
|
||||
// -: unexpected token INT, expected end of expression
|
||||
// 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:
|
||||
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
|
||||
@@ -22,12 +17,9 @@
|
||||
// testdata/errors.go:114: undefined: C.SOME_CONST_b
|
||||
// testdata/errors.go:116: undefined: C.SOME_CONST_startspace
|
||||
// testdata/errors.go:119: undefined: C.SOME_PARAM_CONST_invalid
|
||||
// testdata/errors.go:122: undefined: C.add_toomuch
|
||||
// testdata/errors.go:123: undefined: C.add_toolittle
|
||||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -52,20 +44,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
|
||||
Vendored
-15
@@ -5,7 +5,6 @@
|
||||
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -30,20 +29,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
|
||||
Vendored
-5
@@ -9,10 +9,6 @@ static void staticfunc(int x);
|
||||
|
||||
// Global variable signatures.
|
||||
extern int someValue;
|
||||
|
||||
void notEscapingFunction(int *a);
|
||||
|
||||
#cgo noescape notEscapingFunction
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -22,7 +18,6 @@ func accessFunctions() {
|
||||
C.variadic0()
|
||||
C.variadic2(3, 5)
|
||||
C.staticfunc(3)
|
||||
C.notEscapingFunction(nil)
|
||||
}
|
||||
|
||||
func accessGlobals() {
|
||||
|
||||
Vendored
-20
@@ -1,6 +1,5 @@
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -25,20 +24,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
@@ -75,10 +60,5 @@ func C.staticfunc!symbols.go(x C.int)
|
||||
|
||||
var C.staticfunc!symbols.go$funcaddr unsafe.Pointer
|
||||
|
||||
//export notEscapingFunction
|
||||
//go:noescape
|
||||
func C.notEscapingFunction(a *C.int)
|
||||
|
||||
var C.notEscapingFunction$funcaddr unsafe.Pointer
|
||||
//go:extern someValue
|
||||
var C.someValue C.int
|
||||
|
||||
Vendored
-15
@@ -1,6 +1,5 @@
|
||||
package main
|
||||
|
||||
import "syscall"
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
@@ -25,20 +24,6 @@ func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__CBytes runtime.cgo_CBytes
|
||||
func C.__CBytes([]byte) unsafe.Pointer
|
||||
|
||||
func C.CBytes(b []byte) unsafe.Pointer {
|
||||
return C.__CBytes(b)
|
||||
}
|
||||
|
||||
//go:linkname C.__get_errno_num runtime.cgo_errno
|
||||
func C.__get_errno_num() uintptr
|
||||
|
||||
func C.__get_errno() error {
|
||||
return syscall.Errno(C.__get_errno_num())
|
||||
}
|
||||
|
||||
type (
|
||||
C.char uint8
|
||||
C.schar int8
|
||||
|
||||
@@ -33,17 +33,6 @@ func (c *Config) CPU() string {
|
||||
return c.Target.CPU
|
||||
}
|
||||
|
||||
// The current build mode (like the `-buildmode` command line flag).
|
||||
func (c *Config) BuildMode() string {
|
||||
if c.Options.BuildMode != "" {
|
||||
return c.Options.BuildMode
|
||||
}
|
||||
if c.Target.BuildMode != "" {
|
||||
return c.Target.BuildMode
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
// Features returns a list of features this CPU supports. For example, for a
|
||||
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
||||
// will be returned.
|
||||
@@ -331,7 +320,6 @@ func (c *Config) CFlags(libclang bool) []string {
|
||||
"-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")
|
||||
@@ -341,7 +329,6 @@ func (c *Config) CFlags(libclang bool) []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":
|
||||
@@ -408,8 +395,6 @@ func (c *Config) LDFlags() []string {
|
||||
if c.Target.LinkerScript != "" {
|
||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||
}
|
||||
ldflags = append(ldflags, c.Options.ExtLDFlags...)
|
||||
|
||||
return ldflags
|
||||
}
|
||||
|
||||
|
||||
+1
-12
@@ -8,11 +8,10 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
validBuildModeOptions = []string{"default", "c-shared"}
|
||||
validGCOptions = []string{"none", "leaking", "conservative", "custom", "precise"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
|
||||
validSerialOptions = []string{"none", "uart", "usb", "rtt"}
|
||||
validPrintSizeOptions = []string{"none", "short", "full", "html"}
|
||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||
validPanicStrategyOptions = []string{"print", "trap"}
|
||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
||||
)
|
||||
@@ -27,7 +26,6 @@ type Options struct {
|
||||
GOMIPS string // environment variable (only used with GOARCH=mips and GOARCH=mipsle)
|
||||
Directory string // working dir, leave it unset to use the current working dir
|
||||
Target string
|
||||
BuildMode string // -buildmode flag
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
@@ -58,19 +56,10 @@ type Options struct {
|
||||
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
|
||||
}
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
func (o *Options) Verify() error {
|
||||
if o.BuildMode != "" {
|
||||
valid := isInArray(validBuildModeOptions, o.BuildMode)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
|
||||
o.BuildMode,
|
||||
strings.Join(validBuildModeOptions, ", "))
|
||||
}
|
||||
}
|
||||
if o.GC != "" {
|
||||
valid := isInArray(validGCOptions, o.GC)
|
||||
if !valid {
|
||||
|
||||
@@ -11,7 +11,7 @@ func TestVerifyOptions(t *testing.T) {
|
||||
|
||||
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative, custom, precise`)
|
||||
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
|
||||
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full, 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`)
|
||||
|
||||
testCases := []struct {
|
||||
|
||||
@@ -32,7 +32,6 @@ type TargetSpec struct {
|
||||
GOARCH string `json:"goarch,omitempty"`
|
||||
SoftFloat bool // used for non-baremetal systems (GOMIPS=softfloat etc)
|
||||
BuildTags []string `json:"build-tags,omitempty"`
|
||||
BuildMode string `json:"buildmode,omitempty"` // default build mode (if nothing specified)
|
||||
GC string `json:"gc,omitempty"`
|
||||
Scheduler string `json:"scheduler,omitempty"`
|
||||
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
|
||||
@@ -46,7 +45,6 @@ type TargetSpec struct {
|
||||
LinkerScript string `json:"linkerscript,omitempty"`
|
||||
ExtraFiles []string `json:"extra-files,omitempty"`
|
||||
RP2040BootPatch *bool `json:"rp2040-boot-patch,omitempty"` // Patch RP2040 2nd stage bootloader checksum
|
||||
BootPatches []string `json:"boot-patches,omitempty"` // Bootloader patches to be applied in the order they appear.
|
||||
Emulator string `json:"emulator,omitempty"`
|
||||
FlashCommand string `json:"flash-command,omitempty"`
|
||||
GDB []string `json:"gdb,omitempty"`
|
||||
@@ -391,10 +389,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
"-platform_version", "macos", platformVersion, platformVersion,
|
||||
)
|
||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||
"src/internal/futex/futex_darwin.c",
|
||||
"src/runtime/os_darwin.c",
|
||||
"src/runtime/runtime_unix.c",
|
||||
"src/runtime/signal.c")
|
||||
"src/runtime/runtime_unix.c")
|
||||
case "linux":
|
||||
spec.Linker = "ld.lld"
|
||||
spec.RTLib = "compiler-rt"
|
||||
@@ -415,9 +411,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
spec.CFlags = append(spec.CFlags, "-mno-outline-atomics")
|
||||
}
|
||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||
"src/internal/futex/futex_linux.c",
|
||||
"src/runtime/runtime_unix.c",
|
||||
"src/runtime/signal.c")
|
||||
"src/runtime/runtime_unix.c")
|
||||
case "windows":
|
||||
spec.Linker = "ld.lld"
|
||||
spec.Libc = "mingw-w64"
|
||||
@@ -455,7 +449,7 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
"--stack-first",
|
||||
"--no-demangle",
|
||||
)
|
||||
spec.Emulator = "wasmtime run --dir={tmpDir}::/tmp {}"
|
||||
spec.Emulator = "wasmtime --dir={tmpDir}::/tmp {}"
|
||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||
"src/runtime/asm_tinygowasm.S",
|
||||
"src/internal/task/task_asyncify_wasm.S",
|
||||
|
||||
+5
-7
@@ -19,9 +19,8 @@ const maxFieldsPerParam = 3
|
||||
// useful while declaring or defining a function.
|
||||
type paramInfo struct {
|
||||
llvmType llvm.Type
|
||||
name string // name, possibly with suffixes for e.g. struct fields
|
||||
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
|
||||
flags paramFlags // extra flags for this parameter
|
||||
name string // name, possibly with suffixes for e.g. struct fields
|
||||
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
|
||||
}
|
||||
|
||||
// paramFlags identifies parameter attributes for flags. Most importantly, it
|
||||
@@ -29,9 +28,9 @@ type paramInfo struct {
|
||||
type paramFlags uint8
|
||||
|
||||
const (
|
||||
// Whether this is a full or partial Go parameter (int, slice, etc).
|
||||
// The extra context parameter is not a Go parameter.
|
||||
paramIsGoParam = 1 << iota
|
||||
// Parameter may have the deferenceable_or_null attribute. This attribute
|
||||
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
|
||||
paramIsDeferenceableOrNull = 1 << iota
|
||||
)
|
||||
|
||||
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
|
||||
@@ -196,7 +195,6 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty
|
||||
info := paramInfo{
|
||||
llvmType: t,
|
||||
name: name,
|
||||
flags: paramIsGoParam,
|
||||
}
|
||||
if goType != nil {
|
||||
switch underlying := goType.Underlying().(type) {
|
||||
|
||||
+17
-36
@@ -4,9 +4,7 @@ package compiler
|
||||
// or pseudo-operations that are lowered during goroutine lowering.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"math"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -43,17 +41,17 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
}
|
||||
|
||||
// Allocate buffer for the channel operation.
|
||||
channelOp := b.getLLVMRuntimeType("channelOp")
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
|
||||
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
||||
|
||||
// Do the send.
|
||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
|
||||
|
||||
// End the lifetime of the allocas.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
||||
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
|
||||
if !isZeroSize {
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
@@ -74,12 +72,12 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
}
|
||||
|
||||
// Allocate buffer for the channel operation.
|
||||
channelOp := b.getLLVMRuntimeType("channelOp")
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.getLLVMRuntimeType("channelBlockedList")
|
||||
channelBlockedListAlloca, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
||||
|
||||
// Do the receive.
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelBlockedListAlloca}, "")
|
||||
var received llvm.Value
|
||||
if isZeroSize {
|
||||
received = llvm.ConstNull(valueType)
|
||||
@@ -87,7 +85,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
||||
b.emitLifetimeEnd(channelBlockedListAlloca, channelBlockedListAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||
@@ -126,20 +124,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
const maxSelectStates = math.MaxUint32 >> 2
|
||||
if len(expr.States) > maxSelectStates {
|
||||
// The runtime code assumes that the number of state must fit in 30 bits
|
||||
// (so the select index can be stored in a uint32 with two bits reserved
|
||||
// for other purposes). It seems unlikely that a real program would have
|
||||
// that many states, but we check for this case anyway to be sure.
|
||||
// We use a uint32 (and not a uintptr or uint64) to avoid 64-bit atomic
|
||||
// operations which aren't available everywhere.
|
||||
b.addError(expr.Pos(), fmt.Sprintf("too many select states: got %d but the maximum supported number is %d", len(expr.States), maxSelectStates))
|
||||
|
||||
// Continue as usual (we'll generate broken code but the error will
|
||||
// prevent the compilation to complete).
|
||||
}
|
||||
|
||||
// This code create a (stack-allocated) slice containing all the select
|
||||
// cases and then calls runtime.chanSelect to perform the actual select
|
||||
// statement.
|
||||
@@ -214,10 +198,10 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
if expr.Blocking {
|
||||
// Stack-allocate operation structures.
|
||||
// If these were simply created as a slice, they would heap-allocate.
|
||||
opsAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelOp"), len(selectStates))
|
||||
opsAlloca, opsSize := b.createTemporaryAlloca(opsAllocaType, "select.block.alloca")
|
||||
opsLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
||||
opsPtr := b.CreateGEP(opsAllocaType, opsAlloca, []llvm.Value{
|
||||
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||
chBlockAlloca, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
||||
chBlockPtr := b.CreateGEP(chBlockAllocaType, chBlockAlloca, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
}, "select.block")
|
||||
@@ -225,18 +209,15 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
results = b.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
opsPtr, opsLen, opsLen, // []channelOp
|
||||
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
||||
}, "select.result")
|
||||
|
||||
// Terminate the lifetime of the operation structures.
|
||||
b.emitLifetimeEnd(opsAlloca, opsSize)
|
||||
b.emitLifetimeEnd(chBlockAlloca, chBlockSize)
|
||||
} else {
|
||||
opsPtr := llvm.ConstNull(b.dataPtrType)
|
||||
opsLen := llvm.ConstInt(b.uintptrType, 0, false)
|
||||
results = b.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
opsPtr, opsLen, opsLen, // []channelOp (nil slice)
|
||||
}, "select.result")
|
||||
}
|
||||
|
||||
|
||||
+3
-15
@@ -17,7 +17,6 @@ import (
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/src/tinygo"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"golang.org/x/tools/go/types/typeutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -45,7 +44,6 @@ type Config struct {
|
||||
ABI string
|
||||
GOOS string
|
||||
GOARCH string
|
||||
BuildMode string
|
||||
CodeModel string
|
||||
RelocationModel string
|
||||
SizeLevel int
|
||||
@@ -1386,11 +1384,6 @@ func (b *builder) createFunction() {
|
||||
b.llvmFn.SetLinkage(llvm.InternalLinkage)
|
||||
b.createFunction()
|
||||
}
|
||||
|
||||
// Create wrapper function that can be called externally.
|
||||
if b.info.wasmExport != "" {
|
||||
b.createWasmExport()
|
||||
}
|
||||
}
|
||||
|
||||
// posser is an interface that's implemented by both ssa.Value and
|
||||
@@ -1681,12 +1674,7 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
result = b.CreateSelect(cmp, result, arg, "")
|
||||
}
|
||||
return result, nil
|
||||
case "panic":
|
||||
// This is rare, but happens in "defer panic()".
|
||||
b.createRuntimeInvoke("_panic", argValues, "")
|
||||
return llvm.Value{}, nil
|
||||
case "print", "println":
|
||||
b.createRuntimeCall("printlock", nil, "")
|
||||
for i, value := range argValues {
|
||||
if i >= 1 && callName == "println" {
|
||||
b.createRuntimeCall("printspace", nil, "")
|
||||
@@ -1747,7 +1735,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
if callName == "println" {
|
||||
b.createRuntimeCall("printnl", nil, "")
|
||||
}
|
||||
b.createRuntimeCall("printunlock", nil, "")
|
||||
return llvm.Value{}, nil // print() or println() returns void
|
||||
case "real":
|
||||
cplx := argValues[0]
|
||||
@@ -1872,9 +1859,10 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
}
|
||||
return llvm.ConstInt(b.ctx.Int1Type(), supportsRecover, false), nil
|
||||
case name == "runtime.panicStrategy":
|
||||
// These constants are defined in src/runtime/panic.go.
|
||||
panicStrategy := map[string]uint64{
|
||||
"print": tinygo.PanicStrategyPrint,
|
||||
"trap": tinygo.PanicStrategyTrap,
|
||||
"print": 1, // panicStrategyPrint
|
||||
"trap": 2, // panicStrategyTrap
|
||||
}[b.Config.PanicStrategy]
|
||||
return llvm.ConstInt(b.ctx.Int8Type(), panicStrategy, false), nil
|
||||
case name == "runtime/interrupt.New":
|
||||
|
||||
+20
-253
@@ -7,7 +7,6 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -102,7 +101,7 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var stackSize llvm.Value
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
// The stack size is not known until after linking. Call a dummy
|
||||
// function that will be replaced with a load from a special ELF
|
||||
@@ -122,147 +121,6 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
||||
}
|
||||
|
||||
// Create an exported wrapper function for functions with the //go:wasmexport
|
||||
// pragma. This wrapper function is quite complex when the scheduler is enabled:
|
||||
// it needs to start a new goroutine each time the exported function is called.
|
||||
func (b *builder) createWasmExport() {
|
||||
pos := b.info.wasmExportPos
|
||||
if b.info.exported {
|
||||
// //export really shouldn't be used anymore when //go:wasmexport is
|
||||
// available, because //go:wasmexport is much better defined.
|
||||
b.addError(pos, "cannot use //export and //go:wasmexport at the same time")
|
||||
return
|
||||
}
|
||||
|
||||
const suffix = "#wasmexport"
|
||||
|
||||
// Declare the exported function.
|
||||
paramTypes := b.llvmFnType.ParamTypes()
|
||||
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
|
||||
b.addStandardAttributes(exportedFn)
|
||||
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
|
||||
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
|
||||
|
||||
// Create a builder for this wrapper function.
|
||||
builder := newBuilder(b.compilerContext, b.ctx.NewBuilder(), b.fn)
|
||||
defer builder.Dispose()
|
||||
|
||||
// Define this function as a separate function in DWARF
|
||||
if b.Debug {
|
||||
if b.fn.Syntax() != nil {
|
||||
// Create debug info file if needed.
|
||||
pos := b.program.Fset.Position(pos)
|
||||
builder.difunc = builder.attachDebugInfoRaw(b.fn, exportedFn, suffix, pos.Filename, pos.Line)
|
||||
}
|
||||
builder.setDebugLocation(pos)
|
||||
}
|
||||
|
||||
// Create a single basic block inside of it.
|
||||
bb := llvm.AddBasicBlock(exportedFn, "entry")
|
||||
builder.SetInsertPointAtEnd(bb)
|
||||
|
||||
// Insert an assertion to make sure this //go:wasmexport function is not
|
||||
// called at a time when it is not allowed (for example, before the runtime
|
||||
// is initialized).
|
||||
builder.createRuntimeCall("wasmExportCheckRun", nil, "")
|
||||
|
||||
if b.Scheduler == "none" {
|
||||
// When the scheduler has been disabled, this is really trivial: just
|
||||
// call the function.
|
||||
params := exportedFn.Params()
|
||||
params = append(params, llvm.ConstNull(b.dataPtrType)) // context parameter
|
||||
retval := builder.CreateCall(b.llvmFnType, b.llvmFn, params, "")
|
||||
if b.fn.Signature.Results() == nil {
|
||||
builder.CreateRetVoid()
|
||||
} else {
|
||||
builder.CreateRet(retval)
|
||||
}
|
||||
|
||||
} else {
|
||||
// The scheduler is enabled, so we need to start a new goroutine, wait
|
||||
// for it to complete, and read the result value.
|
||||
|
||||
// Build a function that looks like this:
|
||||
//
|
||||
// func foo#wasmexport(param0, param1, ..., paramN) {
|
||||
// var state *stateStruct
|
||||
//
|
||||
// // 'done' must be explicitly initialized ('state' is not zeroed)
|
||||
// state.done = false
|
||||
//
|
||||
// // store the parameters in the state object
|
||||
// state.param0 = param0
|
||||
// state.param1 = param1
|
||||
// ...
|
||||
// state.paramN = paramN
|
||||
//
|
||||
// // create a goroutine and push it to the runqueue
|
||||
// task.start(uintptr(gowrapper), &state)
|
||||
//
|
||||
// // run the scheduler
|
||||
// runtime.wasmExportRun(&state.done)
|
||||
//
|
||||
// // if there is a return value, load it and return
|
||||
// return state.result
|
||||
// }
|
||||
|
||||
hasReturn := b.fn.Signature.Results() != nil
|
||||
|
||||
// Build the state struct type.
|
||||
// It stores the function parameters, the 'done' flag, and reserves
|
||||
// space for a return value if needed.
|
||||
stateFields := exportedFnType.ParamTypes()
|
||||
numParams := len(stateFields)
|
||||
stateFields = append(stateFields, b.ctx.Int1Type()) // 'done' field
|
||||
if hasReturn {
|
||||
stateFields = append(stateFields, b.llvmFnType.ReturnType())
|
||||
}
|
||||
stateStruct := b.ctx.StructType(stateFields, false)
|
||||
|
||||
// Allocate the state struct on the stack.
|
||||
statePtr := builder.CreateAlloca(stateStruct, "status")
|
||||
|
||||
// Initialize the 'done' field.
|
||||
doneGEP := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams), false),
|
||||
}, "done.gep")
|
||||
builder.CreateStore(llvm.ConstNull(b.ctx.Int1Type()), doneGEP)
|
||||
|
||||
// Store all parameters in the state object.
|
||||
for i, param := range exportedFn.Params() {
|
||||
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
builder.CreateStore(param, gep)
|
||||
}
|
||||
|
||||
// Create a new goroutine and add it to the runqueue.
|
||||
wrapper := b.createGoroutineStartWrapper(b.llvmFnType, b.llvmFn, "", false, true, pos)
|
||||
stackSize := llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
taskStartFnType, taskStartFn := builder.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
||||
builder.createCall(taskStartFnType, taskStartFn, []llvm.Value{wrapper, statePtr, stackSize, llvm.Undef(b.dataPtrType)}, "")
|
||||
|
||||
// Run the scheduler.
|
||||
builder.createRuntimeCall("wasmExportRun", []llvm.Value{doneGEP}, "")
|
||||
|
||||
// Read the return value (if any) and return to the caller of the
|
||||
// //go:wasmexport function.
|
||||
if hasReturn {
|
||||
gep := builder.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(numParams)+1, false),
|
||||
}, "")
|
||||
retval := builder.CreateLoad(b.llvmFnType.ReturnType(), gep, "retval")
|
||||
builder.CreateRet(retval)
|
||||
} else {
|
||||
builder.CreateRetVoid()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
||||
// implementation of goroutines. For example, to call a function like this:
|
||||
//
|
||||
@@ -286,7 +144,7 @@ func (b *builder) createWasmExport() {
|
||||
// to last parameter of the function) is used for this wrapper. If hasContext is
|
||||
// false, the parameter bundle is assumed to have no context parameter and undef
|
||||
// is passed instead.
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext, isWasmExport bool, pos token.Pos) llvm.Value {
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
b := &builder{
|
||||
@@ -304,18 +162,14 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
// See whether this wrapper has already been created. If so, return it.
|
||||
name := fn.Name()
|
||||
wrapperName := name + "$gowrapper"
|
||||
if isWasmExport {
|
||||
wrapperName += "-wasmexport"
|
||||
}
|
||||
wrapper = c.mod.NamedFunction(wrapperName)
|
||||
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
||||
if !wrapper.IsNil() {
|
||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
||||
}
|
||||
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.dataPtrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapperType)
|
||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
||||
c.addStandardAttributes(wrapper)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
@@ -345,110 +199,23 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
if !isWasmExport {
|
||||
// Regular 'go' instruction.
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fnType.ParamTypes()
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
|
||||
}
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fnType.ParamTypes()
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
// Create the call.
|
||||
b.CreateCall(fnType, fn, params, "")
|
||||
|
||||
params := b.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.dataPtrType)) // add dummy context parameter
|
||||
}
|
||||
|
||||
// Create the call.
|
||||
b.CreateCall(fnType, fn, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
}
|
||||
} else {
|
||||
// Goroutine started from a //go:wasmexport pragma.
|
||||
// The function looks like this:
|
||||
//
|
||||
// func foo$gowrapper-wasmexport(state *stateStruct) {
|
||||
// // load values
|
||||
// param0 := state.params[0]
|
||||
// param1 := state.params[1]
|
||||
//
|
||||
// // call wrapped functions
|
||||
// result := foo(param0, param1, ...)
|
||||
//
|
||||
// // store result value (if there is any)
|
||||
// state.result = result
|
||||
//
|
||||
// // finish exported function
|
||||
// state.done = true
|
||||
// runtime.wasmExportExit()
|
||||
// }
|
||||
//
|
||||
// The state object here looks like:
|
||||
//
|
||||
// struct state {
|
||||
// param0
|
||||
// param1
|
||||
// param* // etc
|
||||
// done bool
|
||||
// result returnType
|
||||
// }
|
||||
|
||||
returnType := fnType.ReturnType()
|
||||
hasReturn := returnType != b.ctx.VoidType()
|
||||
statePtr := wrapper.Param(0)
|
||||
|
||||
// Create the state struct (it must match the type in createWasmExport).
|
||||
stateFields := fnType.ParamTypes()
|
||||
numParams := len(stateFields) - 1
|
||||
stateFields = stateFields[:numParams:numParams] // strip 'context' parameter
|
||||
stateFields = append(stateFields, c.ctx.Int1Type()) // 'done' bool
|
||||
if hasReturn {
|
||||
stateFields = append(stateFields, returnType)
|
||||
}
|
||||
stateStruct := b.ctx.StructType(stateFields, false)
|
||||
|
||||
// Extract parameters from the state object, and call the function
|
||||
// that's being wrapped.
|
||||
var callParams []llvm.Value
|
||||
for i := 0; i < numParams; i++ {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
param := b.CreateLoad(stateFields[i], gep, "")
|
||||
callParams = append(callParams, param)
|
||||
}
|
||||
callParams = append(callParams, llvm.ConstNull(c.dataPtrType)) // add 'context' parameter
|
||||
result := b.CreateCall(fnType, fn, callParams, "")
|
||||
|
||||
// Store the return value back into the shared state.
|
||||
// Unlike regular goroutines, these special //go:wasmexport
|
||||
// goroutines can return a value.
|
||||
if hasReturn {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams)+1, false),
|
||||
}, "result.ptr")
|
||||
b.CreateStore(result, gep)
|
||||
}
|
||||
|
||||
// Mark this function as having finished executing.
|
||||
// This is important so the runtime knows the exported function
|
||||
// didn't block.
|
||||
doneGEP := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(numParams), false),
|
||||
}, "done.gep")
|
||||
b.CreateStore(llvm.ConstInt(b.ctx.Int1Type(), 1, false), doneGEP)
|
||||
|
||||
// Call back into the runtime. This will exit the goroutine, switch
|
||||
// back to the scheduler, which will in turn return from the
|
||||
// //go:wasmexport function.
|
||||
b.createRuntimeCall("wasmExportExit", nil, "")
|
||||
if c.Scheduler == "asyncify" {
|
||||
b.CreateCall(deadlockType, deadlock, []llvm.Value{
|
||||
llvm.Undef(c.dataPtrType),
|
||||
}, "")
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -530,5 +297,5 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
|
||||
}
|
||||
|
||||
// Return a ptrtoint of the wrapper, not the function itself.
|
||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
||||
return b.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
|
||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||
// type are lowered in the interrupt lowering pass.
|
||||
// It must have an alignment of 1, otherwise LLVM thinks a ptrtoint of the
|
||||
// global has the lower bits unset.
|
||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := b.getLLVMType(globalType)
|
||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
@@ -50,7 +48,6 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetAlignment(1)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
initializer = b.CreateInsertValue(initializer, funcContext, 0, "")
|
||||
initializer = b.CreateInsertValue(initializer, funcPtr, 1, "")
|
||||
|
||||
+11
-5
@@ -6,11 +6,17 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/src/tinygo"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// constants for hashmap algorithms; must match src/runtime/hashmap.go
|
||||
const (
|
||||
hashmapAlgorithmBinary = iota
|
||||
hashmapAlgorithmString
|
||||
hashmapAlgorithmInterface
|
||||
)
|
||||
|
||||
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
|
||||
// initializing an appropriately sized object.
|
||||
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
||||
@@ -18,20 +24,20 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
||||
keyType := mapType.Key().Underlying()
|
||||
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
|
||||
var llvmKeyType llvm.Type
|
||||
var alg uint64
|
||||
var alg uint64 // must match values in src/runtime/hashmap.go
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// String keys.
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
alg = uint64(tinygo.HashmapAlgorithmString)
|
||||
alg = hashmapAlgorithmString
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// Trivially comparable keys.
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
alg = uint64(tinygo.HashmapAlgorithmBinary)
|
||||
alg = hashmapAlgorithmBinary
|
||||
} else {
|
||||
// All other keys. Implemented as map[interface{}]valueType for ease of
|
||||
// implementation.
|
||||
llvmKeyType = b.getLLVMRuntimeType("_interface")
|
||||
alg = uint64(tinygo.HashmapAlgorithmInterface)
|
||||
alg = hashmapAlgorithmInterface
|
||||
}
|
||||
keySize := b.targetData.TypeAllocSize(llvmKeyType)
|
||||
valueSize := b.targetData.TypeAllocSize(llvmValueType)
|
||||
|
||||
+110
-217
@@ -12,7 +12,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
@@ -24,18 +23,15 @@ import (
|
||||
// The linkName value contains a valid link name, even if //go:linkname is not
|
||||
// present.
|
||||
type functionInfo struct {
|
||||
wasmModule string // go:wasm-module
|
||||
wasmName string // wasm-export-name or wasm-import-name in the IR
|
||||
wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper)
|
||||
wasmExportPos token.Pos // position of //go:wasmexport comment
|
||||
linkName string // go:linkname, go:export - the IR function name
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
interrupt bool // go:interrupt
|
||||
nobounds bool // go:nobounds
|
||||
noescape bool // go:noescape
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
wasmModule string // go:wasm-module
|
||||
wasmName string // wasm-export-name or wasm-import-name in the IR
|
||||
linkName string // go:linkname, go:export - the IR function name
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
interrupt bool // go:interrupt
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
}
|
||||
|
||||
type inlineType int
|
||||
@@ -128,20 +124,11 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
|
||||
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
|
||||
for i, paramInfo := range paramInfos {
|
||||
if paramInfo.elemSize != 0 {
|
||||
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
|
||||
for i, info := range paramInfos {
|
||||
if info.elemSize != 0 {
|
||||
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize)
|
||||
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
|
||||
}
|
||||
if info.noescape && paramInfo.flags¶mIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
|
||||
// Parameters to functions with a //go:noescape parameter should get
|
||||
// the nocapture attribute. However, the context parameter should
|
||||
// not.
|
||||
// (It may be safe to add the nocapture parameter to the context
|
||||
// parameter, but I'd like to stay on the safe side here).
|
||||
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
|
||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a number of function or parameter attributes, depending on the
|
||||
@@ -183,12 +170,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.stringFromBytes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.stringFromRunes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.trackPointer":
|
||||
// This function is necessary for tracking pointers on the stack in a
|
||||
// portable way (see gc_stack_portable.go). Indicate to the optimizer
|
||||
@@ -231,15 +212,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// Build the function if needed.
|
||||
c.maybeCreateSyntheticFunction(fn, llvmFn)
|
||||
|
||||
return fnType, llvmFn
|
||||
}
|
||||
|
||||
// If this is a synthetic function (such as a generic function or a wrapper),
|
||||
// create it now.
|
||||
func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) {
|
||||
// Synthetic functions are functions that do not appear in the source code,
|
||||
// they are artificially constructed. Usually they are wrapper functions
|
||||
// that are not referenced anywhere except in a SSA call instruction so
|
||||
@@ -247,10 +219,6 @@ func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn
|
||||
// The exception is the package initializer, which does appear in the
|
||||
// *ssa.Package members and so shouldn't be created here.
|
||||
if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" {
|
||||
if len(fn.Blocks) == 0 {
|
||||
c.addError(fn.Pos(), "missing function body")
|
||||
return
|
||||
}
|
||||
irbuilder := c.ctx.NewBuilder()
|
||||
b := newBuilder(c, irbuilder, fn)
|
||||
b.createFunction()
|
||||
@@ -258,6 +226,8 @@ func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn
|
||||
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
llvmFn.SetUnnamedAddr(true)
|
||||
}
|
||||
|
||||
return fnType, llvmFn
|
||||
}
|
||||
|
||||
// getFunctionInfo returns information about a function that is not directly
|
||||
@@ -271,22 +241,8 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
// Pick the default linkName.
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
|
||||
// Check for a few runtime functions that are treated specially.
|
||||
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
|
||||
info.linkName = "_initialize"
|
||||
info.wasmName = "_initialize"
|
||||
info.exported = true
|
||||
}
|
||||
if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" {
|
||||
info.linkName = "_start"
|
||||
info.wasmName = "_start"
|
||||
info.exported = true
|
||||
}
|
||||
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
c.parsePragmas(&info, f)
|
||||
|
||||
c.functionInfos[f] = info
|
||||
return info
|
||||
}
|
||||
@@ -294,171 +250,126 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
// //export or //go:noinline.
|
||||
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
syntax := f.Syntax()
|
||||
if f.Origin() != nil {
|
||||
syntax = f.Origin().Syntax()
|
||||
}
|
||||
if syntax == nil {
|
||||
if f.Syntax() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Read all pragmas of this function.
|
||||
var pragmas []*ast.Comment
|
||||
hasWasmExport := false
|
||||
if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
for _, comment := range decl.Doc.List {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") {
|
||||
pragmas = append(pragmas, comment)
|
||||
if strings.HasPrefix(comment.Text, "//go:wasmexport ") {
|
||||
hasWasmExport = true
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse each pragma.
|
||||
for _, comment := range pragmas {
|
||||
parts := strings.Fields(comment.Text)
|
||||
switch parts[0] {
|
||||
case "//export", "//go:export":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if hasWasmExport {
|
||||
// //go:wasmexport overrides //export.
|
||||
continue
|
||||
}
|
||||
|
||||
info.linkName = parts[1]
|
||||
info.wasmName = info.linkName
|
||||
info.exported = true
|
||||
case "//go:interrupt":
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.interrupt = true
|
||||
}
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
// This is deprecated, use //go:wasmimport instead.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
info.wasmModule = parts[1]
|
||||
case "//go:wasmimport":
|
||||
// Import a WebAssembly function, for example a WASI function.
|
||||
// Original proposal: https://github.com/golang/go/issues/38248
|
||||
// Allow globally: https://github.com/golang/go/issues/59149
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
if f.Blocks != nil {
|
||||
// Defined functions cannot be exported.
|
||||
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
|
||||
continue
|
||||
}
|
||||
c.checkWasmImportExport(f, comment.Text)
|
||||
info.exported = true
|
||||
info.wasmModule = parts[1]
|
||||
info.wasmName = parts[2]
|
||||
case "//go:wasmexport":
|
||||
if f.Blocks == nil {
|
||||
c.addError(f.Pos(), "can only use //go:wasmexport on definitions")
|
||||
continue
|
||||
}
|
||||
if len(parts) != 2 {
|
||||
c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1))
|
||||
continue
|
||||
}
|
||||
name := parts[1]
|
||||
if name == "_start" || name == "_initialize" {
|
||||
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name))
|
||||
continue
|
||||
}
|
||||
if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" {
|
||||
c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode))
|
||||
continue
|
||||
}
|
||||
if c.archFamily() != "wasm32" {
|
||||
c.addError(f.Pos(), "//go:wasmexport is only supported on wasm")
|
||||
}
|
||||
c.checkWasmImportExport(f, comment.Text)
|
||||
info.wasmExport = name
|
||||
info.wasmExportPos = comment.Slash
|
||||
case "//go:inline":
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
case "//go:linkname":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
// Only enable go:linkname when the package imports "unsafe".
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
// Only enable go:section when the package imports "unsafe".
|
||||
// go:section also implies go:noinline since inlining could
|
||||
// move the code to a different section than that requested.
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.section = parts[1]
|
||||
info.linkName = parts[1]
|
||||
info.wasmName = info.linkName
|
||||
info.exported = true
|
||||
case "//go:interrupt":
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.interrupt = true
|
||||
}
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
// This is deprecated, use //go:wasmimport instead.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
info.wasmModule = parts[1]
|
||||
case "//go:wasmimport":
|
||||
// Import a WebAssembly function, for example a WASI function.
|
||||
// Original proposal: https://github.com/golang/go/issues/38248
|
||||
// Allow globally: https://github.com/golang/go/issues/59149
|
||||
if len(parts) != 3 {
|
||||
continue
|
||||
}
|
||||
c.checkWasmImport(f, comment.Text)
|
||||
info.exported = true
|
||||
info.wasmModule = parts[1]
|
||||
info.wasmName = parts[2]
|
||||
case "//go:inline":
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
}
|
||||
case "//go:nobounds":
|
||||
// Skip bounds checking in this function. Useful for some
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:noescape":
|
||||
// Don't let pointer parameters escape.
|
||||
// Following the upstream Go implementation, we only do this for
|
||||
// declarations, not definitions.
|
||||
if len(f.Blocks) == 0 {
|
||||
info.noescape = true
|
||||
}
|
||||
case "//go:variadic":
|
||||
// The //go:variadic pragma is emitted by the CGo preprocessing
|
||||
// pass for C variadic functions. This includes both explicit
|
||||
// (with ...) and implicit (no parameters in signature)
|
||||
// functions.
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// This prefix cannot naturally be created, it must have
|
||||
// been created as a result of CGo preprocessing.
|
||||
info.variadic = true
|
||||
case "//go:linkname":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
// Only enable go:linkname when the package imports "unsafe".
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
// Only enable go:section when the package imports "unsafe".
|
||||
// go:section also implies go:noinline since inlining could
|
||||
// move the code to a different section than that requested.
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.section = parts[1]
|
||||
info.inline = inlineNone
|
||||
}
|
||||
case "//go:nobounds":
|
||||
// Skip bounds checking in this function. Useful for some
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:variadic":
|
||||
// The //go:variadic pragma is emitted by the CGo preprocessing
|
||||
// pass for C variadic functions. This includes both explicit
|
||||
// (with ...) and implicit (no parameters in signature)
|
||||
// functions.
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// This prefix cannot naturally be created, it must have
|
||||
// been created as a result of CGo preprocessing.
|
||||
info.variadic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether this function can be used in //go:wasmimport or
|
||||
// //go:wasmexport. It will add an error if this is not the case.
|
||||
// Check whether this function cannot be used in //go:wasmimport. It will add an
|
||||
// error if this is the case.
|
||||
//
|
||||
// The list of allowed types is based on this proposal:
|
||||
// https://github.com/golang/go/issues/59149
|
||||
func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) {
|
||||
func (c *compilerContext) checkWasmImport(f *ssa.Function, pragma string) {
|
||||
if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" {
|
||||
// The runtime is a special case. Allow all kinds of parameters
|
||||
// (importantly, including pointers).
|
||||
return
|
||||
}
|
||||
if f.Blocks != nil {
|
||||
// Defined functions cannot be exported.
|
||||
c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
|
||||
return
|
||||
}
|
||||
if f.Signature.Results().Len() > 1 {
|
||||
c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
|
||||
} else if f.Signature.Results().Len() == 1 {
|
||||
result := f.Signature.Results().At(0)
|
||||
if !c.isValidWasmType(result.Type(), siteResult) {
|
||||
if !isValidWasmType(result.Type(), siteResult) {
|
||||
c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String()))
|
||||
}
|
||||
}
|
||||
for _, param := range f.Params {
|
||||
// Check whether the type is allowed.
|
||||
// Only a very limited number of types can be mapped to WebAssembly.
|
||||
if !c.isValidWasmType(param.Type(), siteParam) {
|
||||
if !isValidWasmType(param.Type(), siteParam) {
|
||||
c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String()))
|
||||
}
|
||||
}
|
||||
@@ -471,15 +382,13 @@ func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string)
|
||||
//
|
||||
// This previously reflected the additional restrictions documented here:
|
||||
// https://github.com/golang/go/issues/59149
|
||||
func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
|
||||
func isValidWasmType(typ types.Type, site wasmSite) bool {
|
||||
switch typ := typ.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
switch typ.Kind() {
|
||||
case types.Bool:
|
||||
return true
|
||||
case types.Int8, types.Uint8, types.Int16, types.Uint16:
|
||||
return site == siteIndirect
|
||||
case types.Int32, types.Uint32, types.Int64, types.Uint64:
|
||||
case types.Int, types.Uint, types.Int8, types.Uint8, types.Int16, types.Uint16, types.Int32, types.Uint32, types.Int64, types.Uint64:
|
||||
return true
|
||||
case types.Float32, types.Float64:
|
||||
return true
|
||||
@@ -490,35 +399,19 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
|
||||
return site == siteParam || site == siteIndirect
|
||||
}
|
||||
case *types.Array:
|
||||
return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect)
|
||||
return site == siteIndirect && isValidWasmType(typ.Elem(), siteIndirect)
|
||||
case *types.Struct:
|
||||
if site != siteIndirect {
|
||||
return false
|
||||
}
|
||||
// Structs with no fields do not need structs.HostLayout
|
||||
if typ.NumFields() == 0 {
|
||||
return true
|
||||
}
|
||||
hasHostLayout := true // default to true before detecting Go version
|
||||
// (*types.Package).GoVersion added in go1.21
|
||||
if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok {
|
||||
if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 {
|
||||
hasHostLayout = false // package structs added in go1.23
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
ftyp := typ.Field(i).Type()
|
||||
if ftyp.String() == "structs.HostLayout" {
|
||||
hasHostLayout = true
|
||||
continue
|
||||
}
|
||||
if !c.isValidWasmType(ftyp, siteIndirect) {
|
||||
if !isValidWasmType(typ.Field(i).Type(), siteIndirect) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return hasHostLayout
|
||||
return true
|
||||
case *types.Pointer:
|
||||
return c.isValidWasmType(typ.Elem(), siteIndirect)
|
||||
return isValidWasmType(typ.Elem(), siteIndirect)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Vendored
+26
-26
@@ -3,7 +3,7 @@ source_filename = "channel.go"
|
||||
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime.channelOp = type { ptr, ptr, i32, ptr }
|
||||
%runtime.channelBlockedList = type { ptr, ptr, ptr, { ptr, i32, i32 } }
|
||||
%runtime.chanSelectState = type { ptr, ptr }
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
@@ -18,15 +18,15 @@ entry:
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.chanIntSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
|
||||
store i32 3, ptr %chan.value, align 4
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
|
||||
ret void
|
||||
}
|
||||
@@ -34,48 +34,48 @@ entry:
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3
|
||||
|
||||
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
|
||||
declare void @runtime.chanSend(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
|
||||
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
|
||||
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #4
|
||||
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.blockedList, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value)
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #1
|
||||
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(32), ptr, ptr dereferenceable_or_null(24), ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%chan.op = alloca %runtime.channelOp, align 8
|
||||
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op)
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.blockedList, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %chan.blockedList)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(36) %ch1, ptr dereferenceable_or_null(36) %ch2, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.selectZeroRecv(ptr dereferenceable_or_null(32) %ch1, ptr dereferenceable_or_null(32) %ch2, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
|
||||
%select.send.value = alloca i32, align 4
|
||||
@@ -88,7 +88,7 @@ entry:
|
||||
store ptr %ch2, ptr %0, align 4
|
||||
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], ptr %select.states.alloca, i32 0, i32 1, i32 1
|
||||
store ptr null, ptr %.repack3, align 4
|
||||
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #4
|
||||
%select.result = call { i32, i1 } @runtime.tryChanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr undef) #4
|
||||
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca)
|
||||
%1 = extractvalue { i32, i1 } %select.result, 0
|
||||
%2 = icmp eq i32 %1, 0
|
||||
@@ -105,7 +105,7 @@ select.body: ; preds = %select.next
|
||||
br label %select.done
|
||||
}
|
||||
|
||||
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1
|
||||
declare { i32, i1 } @runtime.tryChanSelect(ptr, ptr, i32, i32, ptr) #1
|
||||
|
||||
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
|
||||
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
|
||||
|
||||
+1
-11
@@ -3,7 +3,7 @@ source_filename = "defer.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "thumbv7m-unknown-unknown-eabi"
|
||||
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface }
|
||||
%runtime._interface = type { ptr, ptr }
|
||||
%runtime._defer = type { i32, ptr }
|
||||
|
||||
@@ -122,18 +122,12 @@ declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #2
|
||||
; Function Attrs: nounwind
|
||||
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @runtime.printlock(ptr undef) #4
|
||||
call void @runtime.printint32(i32 3, ptr undef) #4
|
||||
call void @runtime.printunlock(ptr undef) #4
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printlock(ptr) #2
|
||||
|
||||
declare void @runtime.printint32(i32, ptr) #2
|
||||
|
||||
declare void @runtime.printunlock(ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferMultiple(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
@@ -256,18 +250,14 @@ rundefers.end7: ; preds = %rundefers.loophead1
|
||||
; Function Attrs: nounwind
|
||||
define internal void @"main.deferMultiple$1"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @runtime.printlock(ptr undef) #4
|
||||
call void @runtime.printint32(i32 3, ptr undef) #4
|
||||
call void @runtime.printunlock(ptr undef) #4
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define internal void @"main.deferMultiple$2"(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @runtime.printlock(ptr undef) #4
|
||||
call void @runtime.printint32(i32 5, ptr undef) #4
|
||||
call void @runtime.printunlock(ptr undef) #4
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
Vendored
+7
-37
@@ -1,9 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"structs"
|
||||
"unsafe"
|
||||
)
|
||||
import "unsafe"
|
||||
|
||||
//go:wasmimport modulename empty
|
||||
func empty()
|
||||
@@ -17,37 +14,31 @@ func implementation() {
|
||||
type Uint uint32
|
||||
|
||||
type S struct {
|
||||
_ structs.HostLayout
|
||||
a [4]uint32
|
||||
b uintptr
|
||||
c int
|
||||
d float32
|
||||
e float64
|
||||
}
|
||||
|
||||
//go:wasmimport modulename validparam
|
||||
func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S, j *struct{}, k *[8]uint8)
|
||||
func validparam(a int32, b uint64, c float64, d unsafe.Pointer, e Uint, f uintptr, g string, h *int32, i *S)
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [4]uint32
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type []byte
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type struct{a int}
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type chan struct{}
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type func()
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type int
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type uint
|
||||
// ERROR: //go:wasmimport modulename invalidparam: unsupported parameter type [8]int
|
||||
//
|
||||
//go:wasmimport modulename invalidparam
|
||||
func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func(), f int, g uint, h [8]int)
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{int}
|
||||
// ERROR: //go:wasmimport modulename invalidparam_no_hostlayout: unsupported parameter type *struct{string}
|
||||
//
|
||||
//go:wasmimport modulename invalidparam_no_hostlayout
|
||||
func invalidparam_no_hostlayout(a *struct{ int }, b *struct{ string })
|
||||
func invalidparam(a [4]uint32, b []byte, c struct{ a int }, d chan struct{}, e func())
|
||||
|
||||
//go:wasmimport modulename validreturn_int32
|
||||
func validreturn_int32() int32
|
||||
|
||||
//go:wasmimport modulename validreturn_int
|
||||
func validreturn_int() int
|
||||
|
||||
//go:wasmimport modulename validreturn_ptr_int32
|
||||
func validreturn_ptr_int32() *int32
|
||||
|
||||
@@ -57,12 +48,6 @@ func validreturn_ptr_string() *string
|
||||
//go:wasmimport modulename validreturn_ptr_struct
|
||||
func validreturn_ptr_struct() *S
|
||||
|
||||
//go:wasmimport modulename validreturn_ptr_struct
|
||||
func validreturn_ptr_empty_struct() *struct{}
|
||||
|
||||
//go:wasmimport modulename validreturn_ptr_array
|
||||
func validreturn_ptr_array() *[8]uint8
|
||||
|
||||
//go:wasmimport modulename validreturn_unsafe_pointer
|
||||
func validreturn_unsafe_pointer() unsafe.Pointer
|
||||
|
||||
@@ -71,26 +56,11 @@ func validreturn_unsafe_pointer() unsafe.Pointer
|
||||
//go:wasmimport modulename manyreturns
|
||||
func manyreturns() (int32, int32)
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type int
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn_int
|
||||
func invalidreturn_int() int
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn_int: unsupported result type uint
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn_int
|
||||
func invalidreturn_uint() uint
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn_func: unsupported result type func()
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn_func
|
||||
func invalidreturn_func() func()
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn_pointer_array_int: unsupported result type *[8]int
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn_pointer_array_int
|
||||
func invalidreturn_pointer_array_int() *[8]int
|
||||
|
||||
// ERROR: //go:wasmimport modulename invalidreturn_slice_byte: unsupported result type []byte
|
||||
//
|
||||
//go:wasmimport modulename invalidreturn_slice_byte
|
||||
|
||||
+2
-8
@@ -70,9 +70,7 @@ entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr undef) #9
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #9
|
||||
%2 = load i32, ptr %n, align 4
|
||||
call void @runtime.printlock(ptr undef) #9
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #9
|
||||
call void @runtime.printunlock(ptr undef) #9
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -93,12 +91,8 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printlock(ptr) #2
|
||||
|
||||
declare void @runtime.printint32(i32, ptr) #2
|
||||
|
||||
declare void @runtime.printunlock(ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
@@ -141,13 +135,13 @@ entry:
|
||||
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #9
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #2
|
||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
|
||||
|
||||
+2
-8
@@ -76,9 +76,7 @@ entry:
|
||||
store ptr %n, ptr %1, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.closureFunctionGoroutine$1$gowrapper" to i32), ptr nonnull %0, i32 65536, ptr undef) #9
|
||||
%2 = load i32, ptr %n, align 4
|
||||
call void @runtime.printlock(ptr undef) #9
|
||||
call void @runtime.printint32(i32 %2, ptr undef) #9
|
||||
call void @runtime.printunlock(ptr undef) #9
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -100,12 +98,8 @@ entry:
|
||||
unreachable
|
||||
}
|
||||
|
||||
declare void @runtime.printlock(ptr) #1
|
||||
|
||||
declare void @runtime.printint32(i32, ptr) #1
|
||||
|
||||
declare void @runtime.printunlock(ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
@@ -150,13 +144,13 @@ entry:
|
||||
declare i32 @runtime.sliceCopy(ptr nocapture writeonly, ptr nocapture readonly, i32, i32, i32, ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #2 {
|
||||
define hidden void @main.closeBuiltinGoroutine(ptr dereferenceable_or_null(32) %ch, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
call void @runtime.chanClose(ptr %ch, ptr undef) #9
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
|
||||
declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #2 {
|
||||
|
||||
Vendored
-25
@@ -48,22 +48,6 @@ func inlineFunc() {
|
||||
func noinlineFunc() {
|
||||
}
|
||||
|
||||
type Int interface {
|
||||
int8 | int16
|
||||
}
|
||||
|
||||
// Same for generic functions (but the compiler may miss the pragma due to it
|
||||
// being generic).
|
||||
//
|
||||
//go:noinline
|
||||
func noinlineGenericFunc[T Int]() {
|
||||
}
|
||||
|
||||
func useGeneric() {
|
||||
// Make sure the generic function above is instantiated.
|
||||
noinlineGenericFunc[int8]()
|
||||
}
|
||||
|
||||
// This function should have the specified section.
|
||||
//
|
||||
//go:section .special_function_section
|
||||
@@ -106,12 +90,3 @@ var undefinedGlobalNotInSection uint32
|
||||
//go:align 1024
|
||||
//go:section .global_section
|
||||
var multipleGlobalPragmas uint32
|
||||
|
||||
//go:noescape
|
||||
func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte)
|
||||
|
||||
// The //go:noescape pragma only works on declarations, not definitions.
|
||||
//
|
||||
//go:noescape
|
||||
func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
|
||||
}
|
||||
|
||||
Vendored
-21
@@ -48,19 +48,6 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.useGeneric(ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
call void @"main.noinlineGenericFunc[int8]"(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: noinline nounwind
|
||||
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #5 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: noinline nounwind
|
||||
define hidden void @main.functionInSection(ptr %context) unnamed_addr #5 section ".special_function_section" {
|
||||
entry:
|
||||
@@ -85,14 +72,6 @@ entry:
|
||||
|
||||
declare void @main.undefinedFunctionNotInSection(ptr) #1
|
||||
|
||||
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
|
||||
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
|
||||
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
|
||||
|
||||
@@ -24,8 +24,6 @@ func TestErrors(t *testing.T) {
|
||||
{name: "cgo"},
|
||||
{name: "compiler"},
|
||||
{name: "interp"},
|
||||
{name: "invalidmain"},
|
||||
{name: "invalidname"},
|
||||
{name: "linker-flashoverflow", target: "cortex-m-qemu"},
|
||||
{name: "linker-ramoverflow", target: "cortex-m-qemu"},
|
||||
{name: "linker-undefined", target: "darwin/arm64"},
|
||||
|
||||
Generated
+4
-4
@@ -20,16 +20,16 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1728500571,
|
||||
"narHash": "sha256-dOymOQ3AfNI4Z337yEwHGohrVQb4yPODCW9MDUyAc4w=",
|
||||
"lastModified": 1703068421,
|
||||
"narHash": "sha256-WSw5Faqlw75McIflnl5v7qVD/B3S2sLh+968bpOGrWA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d51c28603def282a24fa034bcb007e2bcb5b5dd0",
|
||||
"rev": "d65bceaee0fb1e64363f7871bc43dc1c6ecad99f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"ref": "nixos-24.05",
|
||||
"ref": "nixos-23.11",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
inputs = {
|
||||
# Use a recent stable release, but fix the version to make it reproducible.
|
||||
# This version should be updated from time to time.
|
||||
nixpkgs.url = "nixpkgs/nixos-24.05";
|
||||
nixpkgs.url = "nixpkgs/nixos-23.11";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
@@ -49,11 +49,11 @@
|
||||
buildInputs = [
|
||||
# These dependencies are required for building tinygo (go install).
|
||||
go
|
||||
llvmPackages_18.llvm
|
||||
llvmPackages_18.libclang
|
||||
llvmPackages_17.llvm
|
||||
llvmPackages_17.libclang
|
||||
# Additional dependencies needed at runtime, for building and/or
|
||||
# flashing.
|
||||
llvmPackages_18.lld
|
||||
llvmPackages_17.lld
|
||||
avrdude
|
||||
binaryen
|
||||
# Additional dependencies needed for on-chip debugging.
|
||||
@@ -68,7 +68,7 @@
|
||||
# Without setting these explicitly, Homebrew versions might be used
|
||||
# or the default `ar` and `nm` tools might be used (which don't
|
||||
# support wasi).
|
||||
export CLANG="clang-18 -resource-dir ${llvmPackages_18.clang.cc.lib}/lib/clang/18"
|
||||
export CLANG="clang-17 -resource-dir ${llvmPackages_17.clang.cc.lib}/lib/clang/17"
|
||||
export LLVM_AR=llvm-ar
|
||||
export LLVM_NM=llvm-nm
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
export MD5SUM=md5sum
|
||||
|
||||
# Ugly hack to make the Clang resources directory available.
|
||||
export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_18.clang.cc.lib}/lib/clang/18\" -tags=llvm18"
|
||||
export GOFLAGS="\"-ldflags=-X github.com/tinygo-org/tinygo/goenv.clangResourceDir=${llvmPackages_17.clang.cc.lib}/lib/clang/17\" -tags=llvm17"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-tty v0.0.4
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
|
||||
github.com/tetratelabs/wazero v1.6.0
|
||||
go.bug.st/serial v1.6.0
|
||||
golang.org/x/net v0.26.0
|
||||
golang.org/x/sys v0.21.0
|
||||
|
||||
@@ -12,6 +12,7 @@ github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moA
|
||||
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
|
||||
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
@@ -44,18 +45,19 @@ github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3px
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5 h1:1SoBaSPudixRecmlHXb/GxmaD3fLMtHIDN13QujwQuc=
|
||||
github.com/orisano/pixelmatch v0.0.0-20210112091706-4fa4c7ba91d5/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
|
||||
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tetratelabs/wazero v1.6.0 h1:z0H1iikCdP8t+q341xqepY4EWvHEw8Es7tlqiVzlP3g=
|
||||
github.com/tetratelabs/wazero v1.6.0/go.mod h1:0U0G41+ochRKoPKCJlh0jMg1CHkyfK8kDqiirMmKY8A=
|
||||
go.bug.st/serial v1.6.0 h1:mAbRGN4cKE2J5gMwsMHC2KQisdLRQssO9WSM+rbZJ8A=
|
||||
go.bug.st/serial v1.6.0/go.mod h1:UABfsluHAiaNI+La2iESysd9Vetq7VRdpxvjx7CmmOE=
|
||||
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -74,5 +76,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8 h1:bLsZXRUBavt++CJlMN7sppNziqu3LyamESLhFJcpqFQ=
|
||||
tinygo.org/x/go-llvm v0.0.0-20240627184919-3b50c76783a8/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
|
||||
+17
-65
@@ -4,37 +4,29 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version of TinyGo.
|
||||
// Update this value before release of new version of software.
|
||||
const version = "0.35.0"
|
||||
const version = "0.34.0-dev"
|
||||
|
||||
var (
|
||||
// This variable is set at build time using -ldflags parameters.
|
||||
// See: https://stackoverflow.com/a/11355611
|
||||
GitSha1 string
|
||||
)
|
||||
|
||||
// Return TinyGo version, either in the form 0.30.0 or as a development version
|
||||
// (like 0.30.0-dev-abcd012).
|
||||
func Version() string {
|
||||
v := version
|
||||
if strings.HasSuffix(version, "-dev") {
|
||||
if hash := readGitHash(); hash != "" {
|
||||
v += "-" + hash
|
||||
}
|
||||
if strings.HasSuffix(version, "-dev") && GitSha1 != "" {
|
||||
v += "-" + GitSha1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func readGitHash() string {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, setting := range info.Settings {
|
||||
if setting.Key == "vcs.revision" {
|
||||
return setting.Value[:8]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
func GetGorootVersion() (major, minor int, err error) {
|
||||
@@ -42,67 +34,27 @@ func GetGorootVersion() (major, minor int, err error) {
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
major, minor, _, err = Parse(s)
|
||||
return major, minor, err
|
||||
}
|
||||
|
||||
// Parse parses the Go version (like "go1.3.2") in the parameter and return the
|
||||
// major, minor, and patch version: 1, 3, and 2 in this example.
|
||||
// If there is an error, (0, 0, 0) and an error will be returned.
|
||||
func Parse(version string) (major, minor, patch int, err error) {
|
||||
if strings.HasPrefix(version, "devel ") {
|
||||
version = strings.Split(strings.TrimPrefix(version, "devel "), version)[0]
|
||||
}
|
||||
if version == "" || version[:2] != "go" {
|
||||
return 0, 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||
if s == "" || s[:2] != "go" {
|
||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||
}
|
||||
|
||||
parts := strings.Split(version[2:], ".")
|
||||
parts := strings.Split(s[2:], ".")
|
||||
if len(parts) < 2 {
|
||||
return 0, 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||
}
|
||||
|
||||
// Ignore the errors, we don't really handle errors here anyway.
|
||||
var trailing string
|
||||
n, err := fmt.Sscanf(version, "go%d.%d.%d%s", &major, &minor, &patch, &trailing)
|
||||
if n == 2 {
|
||||
n, err = fmt.Sscanf(version, "go%d.%d%s", &major, &minor, &trailing)
|
||||
}
|
||||
if n >= 2 && err == io.EOF {
|
||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||
if n == 2 && err == io.EOF {
|
||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||
}
|
||||
|
||||
return major, minor, patch, nil
|
||||
}
|
||||
|
||||
// Compare compares two Go version strings.
|
||||
// The result will be 0 if a == b, -1 if a < b, and +1 if a > b.
|
||||
// If either a or b is not a valid Go version, it is treated as "go0.0"
|
||||
// and compared lexicographically.
|
||||
// See [Parse] for more information.
|
||||
func Compare(a, b string) int {
|
||||
aMajor, aMinor, aPatch, _ := Parse(a)
|
||||
bMajor, bMinor, bPatch, _ := Parse(b)
|
||||
switch {
|
||||
case aMajor < bMajor:
|
||||
return -1
|
||||
case aMajor > bMajor:
|
||||
return +1
|
||||
case aMinor < bMinor:
|
||||
return -1
|
||||
case aMinor > bMinor:
|
||||
return +1
|
||||
case aPatch < bPatch:
|
||||
return -1
|
||||
case aPatch > bPatch:
|
||||
return +1
|
||||
default:
|
||||
return strings.Compare(a, b)
|
||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GorootVersionString returns the version string as reported by the Go
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package goenv
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
tests := []struct {
|
||||
v string
|
||||
major int
|
||||
minor int
|
||||
patch int
|
||||
wantErr bool
|
||||
}{
|
||||
{"", 0, 0, 0, true},
|
||||
{"go", 0, 0, 0, true},
|
||||
{"go1", 0, 0, 0, true},
|
||||
{"go.0", 0, 0, 0, true},
|
||||
{"go1.0", 1, 0, 0, false},
|
||||
{"go1.1", 1, 1, 0, false},
|
||||
{"go1.23", 1, 23, 0, false},
|
||||
{"go1.23.5", 1, 23, 5, false},
|
||||
{"go1.23.5-rc6", 1, 23, 5, false},
|
||||
{"go2.0", 2, 0, 0, false},
|
||||
{"go2.0.15", 2, 0, 15, false},
|
||||
{"devel go1.24-f99f5da18f Thu Nov 14 22:29:26 2024 +0000 darwin/arm64", 1, 24, 0, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.v, func(t *testing.T) {
|
||||
major, minor, patch, err := Parse(tt.v)
|
||||
if err == nil && tt.wantErr {
|
||||
t.Errorf("Parse(%q): expected err != nil", tt.v)
|
||||
}
|
||||
if err != nil && !tt.wantErr {
|
||||
t.Errorf("Parse(%q): expected err == nil", tt.v)
|
||||
}
|
||||
if major != tt.major || minor != tt.minor || patch != tt.patch {
|
||||
t.Errorf("Parse(%q): expected %d, %d, %d, nil; got %d, %d, %d, %v",
|
||||
tt.v, tt.major, tt.minor, tt.patch, major, minor, patch, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
tests := []struct {
|
||||
a string
|
||||
b string
|
||||
want int
|
||||
}{
|
||||
{"", "", 0},
|
||||
{"go0", "go0", 0},
|
||||
{"go0", "go1", -1},
|
||||
{"go1", "go0", 1},
|
||||
{"go1", "go2", -1},
|
||||
{"go2", "go1", 1},
|
||||
{"go1.1", "go1.2", -1},
|
||||
{"go1.2", "go1.1", 1},
|
||||
{"go1.1.0", "go1.2.0", -1},
|
||||
{"go1.2.0", "go1.1.0", 1},
|
||||
{"go1.2.0", "go2.3.0", -1},
|
||||
{"go1.23.2", "go1.23.10", -1},
|
||||
{"go0.1.22", "go1.23.101", -1},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.a+" "+tt.b, func(t *testing.T) {
|
||||
got := Compare(tt.a, tt.b)
|
||||
if got != tt.want {
|
||||
t.Errorf("Compare(%q, %q): expected %d; got %d",
|
||||
tt.a, tt.b, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,11 @@ module github.com/tinygo-org/tinygo/internal/tools
|
||||
|
||||
go 1.22.4
|
||||
|
||||
require github.com/bytecodealliance/wasm-tools-go v0.3.1
|
||||
require github.com/bytecodealliance/wasm-tools-go v0.2.0
|
||||
|
||||
require (
|
||||
github.com/coreos/go-semver v0.3.1 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/regclient/regclient v0.7.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.2 // indirect
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
)
|
||||
|
||||
+10
-29
@@ -1,44 +1,25 @@
|
||||
github.com/bytecodealliance/wasm-tools-go v0.3.1 h1:9Q9PjSzkbiVmkUvZ7nYCfJ02mcQDBalxycA3s8g7kR4=
|
||||
github.com/bytecodealliance/wasm-tools-go v0.3.1/go.mod h1:vNAQ8DAEp6xvvk+TUHah5DslLEa76f4H6e737OeaxuY=
|
||||
github.com/bytecodealliance/wasm-tools-go v0.2.0 h1:JdmiZew7ewHjf+ZGGRE4gZM85Ad/PGW/5I57hepEOjQ=
|
||||
github.com/bytecodealliance/wasm-tools-go v0.2.0/go.mod h1:2GnJCUlcDrslZ/L6+yYqoUnewDlBvqRS2N/0NW9ro6w=
|
||||
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
|
||||
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/olareg/olareg v0.1.0 h1:1dXBOgPrig5N7zoXyIZVQqU0QBo6sD9pbL6UYjY75CA=
|
||||
github.com/olareg/olareg v0.1.0/go.mod h1:RBuU7JW7SoIIxZKzLRhq8sVtQeAHzCAtRrXEBx2KlM4=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/regclient/regclient v0.7.1 h1:qEsJrTmZd98fZKjueAbrZCSNGU+ifnr6xjlSAs3WOPs=
|
||||
github.com/regclient/regclient v0.7.1/go.mod h1:+w/BFtJuw0h0nzIw/z2+1FuA2/dVXBzDq4rYmziJpMc=
|
||||
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.2 h1:CL8llQj3dGRLVQQzHxS+ZYRLanOuhyK1fXgLKD+qV+Y=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.2/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9 h1:P0RMy5fQm1AslQS+XCmy9UknDXctOmG/q/FZkUFnJSo=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9/go.mod h1:0kK/RUFHyh+yIKSfWxwheGndfnrvYSmYFVeKCh03ZUc=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
|
||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
|
||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
+2
-10
@@ -239,7 +239,8 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// already be emitted in initAll.
|
||||
continue
|
||||
case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" ||
|
||||
callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current" ||
|
||||
callFn.name == "os.runtime_args" || callFn.name == "syscall.runtime_envs" ||
|
||||
callFn.name == "internal/task.start" || callFn.name == "internal/task.Current" ||
|
||||
callFn.name == "time.startTimer" || callFn.name == "time.stopTimer" || callFn.name == "time.resetTimer":
|
||||
// These functions should be run at runtime. Specifically:
|
||||
// * Print and panic functions are best emitted directly without
|
||||
@@ -287,17 +288,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// Get the object layout, if it is available.
|
||||
llvmLayoutType := r.getLLVMTypeFromLayout(operands[2])
|
||||
|
||||
// Get the alignment of the memory to be allocated.
|
||||
alignment := 0 // use default alignment if unset
|
||||
alignAttr := inst.llvmInst.GetCallSiteEnumAttribute(0, llvm.AttributeKindID("align"))
|
||||
if !alignAttr.IsNil() {
|
||||
alignment = int(alignAttr.GetEnumValue())
|
||||
}
|
||||
|
||||
// Create the object.
|
||||
alloc := object{
|
||||
globalName: r.pkgName + "$alloc",
|
||||
align: alignment,
|
||||
llvmLayoutType: llvmLayoutType,
|
||||
buffer: newRawValue(uint32(size)),
|
||||
size: uint32(size),
|
||||
@@ -654,7 +647,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
globalName: r.pkgName + "$alloca",
|
||||
buffer: newRawValue(uint32(size)),
|
||||
size: uint32(size),
|
||||
align: inst.llvmInst.Alignment(),
|
||||
}
|
||||
index := len(r.objects)
|
||||
r.objects = append(r.objects, alloca)
|
||||
|
||||
+6
-9
@@ -42,7 +42,6 @@ type object struct {
|
||||
globalName string // name, if not yet created (not guaranteed to be the final name)
|
||||
buffer value // buffer with value as given by interp, nil if external
|
||||
size uint32 // must match buffer.len(), if available
|
||||
align int // alignment of the object (may be 0 if unknown)
|
||||
constant bool // true if this is a constant global
|
||||
marked uint8 // 0 means unmarked, 1 means external read, 2 means external write
|
||||
}
|
||||
@@ -594,12 +593,6 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
|
||||
// runtime.alloc.
|
||||
// First allocate a new global for this object.
|
||||
obj := mem.get(v.index())
|
||||
alignment := obj.align
|
||||
if alignment == 0 {
|
||||
// Unknown alignment, perhaps from a direct call to runtime.alloc in
|
||||
// the runtime. Use a conservative default instead.
|
||||
alignment = mem.r.maxAlign
|
||||
}
|
||||
if obj.llvmType.IsNil() && obj.llvmLayoutType.IsNil() {
|
||||
// Create an initializer without knowing the global type.
|
||||
// This is probably the result of a runtime.alloc call.
|
||||
@@ -610,7 +603,7 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
|
||||
globalType := initializer.Type()
|
||||
llvmValue = llvm.AddGlobal(mem.r.mod, globalType, obj.globalName)
|
||||
llvmValue.SetInitializer(initializer)
|
||||
llvmValue.SetAlignment(alignment)
|
||||
llvmValue.SetAlignment(mem.r.maxAlign)
|
||||
obj.llvmGlobal = llvmValue
|
||||
mem.put(v.index(), obj)
|
||||
} else {
|
||||
@@ -649,7 +642,11 @@ func (v pointerValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Val
|
||||
return llvm.Value{}, errors.New("interp: allocated value does not match allocated type")
|
||||
}
|
||||
llvmValue.SetInitializer(initializer)
|
||||
llvmValue.SetAlignment(alignment)
|
||||
if obj.llvmType.IsNil() {
|
||||
// The exact type isn't known (only the layout), so use the
|
||||
// alignment that would normally be expected from runtime.alloc.
|
||||
llvmValue.SetAlignment(mem.r.maxAlign)
|
||||
}
|
||||
}
|
||||
|
||||
// It should be included in r.globals because otherwise markExternal
|
||||
|
||||
+1
-1
Submodule lib/cmsis-svd updated: 05a9562ec5...40327a4d2d
+1
-1
Submodule lib/macos-minimal-sdk updated: 9b69407cb5...4e4113e3b1
+25
-31
@@ -218,7 +218,7 @@ func listGorootMergeLinks(goroot, tinygoroot string, overrides map[string]bool)
|
||||
// with the TinyGo version. This is the case on some targets.
|
||||
func needsSyscallPackage(buildTags []string) bool {
|
||||
for _, tag := range buildTags {
|
||||
if tag == "baremetal" || tag == "nintendoswitch" || tag == "tinygo.wasm" {
|
||||
if tag == "baremetal" || tag == "nintendoswitch" || tag == "wasip1" || tag == "wasip2" || tag == "wasm_unknown" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -229,36 +229,30 @@ func needsSyscallPackage(buildTags []string) bool {
|
||||
// means use the TinyGo version.
|
||||
func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
|
||||
paths := map[string]bool{
|
||||
"": true,
|
||||
"crypto/": true,
|
||||
"crypto/rand/": false,
|
||||
"crypto/tls/": false,
|
||||
"crypto/x509/": true,
|
||||
"crypto/x509/internal/": true,
|
||||
"crypto/x509/internal/macos/": false,
|
||||
"device/": false,
|
||||
"examples/": false,
|
||||
"internal/": true,
|
||||
"internal/abi/": false,
|
||||
"internal/binary/": false,
|
||||
"internal/bytealg/": false,
|
||||
"internal/cm/": false,
|
||||
"internal/futex/": false,
|
||||
"internal/fuzz/": false,
|
||||
"internal/reflectlite/": false,
|
||||
"internal/gclayout": false,
|
||||
"internal/task/": false,
|
||||
"internal/wasi/": false,
|
||||
"machine/": false,
|
||||
"net/": true,
|
||||
"net/http/": false,
|
||||
"os/": true,
|
||||
"reflect/": false,
|
||||
"runtime/": false,
|
||||
"sync/": true,
|
||||
"testing/": true,
|
||||
"tinygo/": false,
|
||||
"unique/": false,
|
||||
"": true,
|
||||
"crypto/": true,
|
||||
"crypto/rand/": false,
|
||||
"crypto/tls/": false,
|
||||
"device/": false,
|
||||
"examples/": false,
|
||||
"internal/": true,
|
||||
"internal/abi/": false,
|
||||
"internal/binary/": false,
|
||||
"internal/bytealg/": false,
|
||||
"internal/cm/": false,
|
||||
"internal/fuzz/": false,
|
||||
"internal/reflectlite/": false,
|
||||
"internal/task/": false,
|
||||
"internal/wasi/": false,
|
||||
"machine/": false,
|
||||
"net/": true,
|
||||
"net/http/": false,
|
||||
"os/": true,
|
||||
"reflect/": false,
|
||||
"runtime/": false,
|
||||
"sync/": true,
|
||||
"testing/": true,
|
||||
"unique/": false,
|
||||
}
|
||||
|
||||
if goMinor >= 19 {
|
||||
|
||||
+4
-16
@@ -418,12 +418,8 @@ func (p *Package) Check() error {
|
||||
packageName := p.ImportPath
|
||||
if p == p.program.MainPkg() {
|
||||
if p.Name != "main" {
|
||||
return Errors{p, []error{
|
||||
scanner.Error{
|
||||
Pos: p.program.fset.Position(p.Files[0].Name.Pos()),
|
||||
Msg: fmt.Sprintf("expected main package to have name \"main\", not %#v", p.Name),
|
||||
},
|
||||
}}
|
||||
// Sanity check. Should not ever trigger.
|
||||
panic("expected main package to have name 'main'")
|
||||
}
|
||||
packageName = "main"
|
||||
}
|
||||
@@ -432,15 +428,7 @@ func (p *Package) Check() error {
|
||||
if err, ok := err.(Errors); ok {
|
||||
return err
|
||||
}
|
||||
if len(typeErrors) != 0 {
|
||||
// Got type errors, so return them.
|
||||
return Errors{p, typeErrors}
|
||||
}
|
||||
// This can happen in some weird cases.
|
||||
// The only case I know is when compiling a Go 1.23 program, with a
|
||||
// TinyGo version that supports Go 1.23 but is compiled using Go 1.22.
|
||||
// So this should be pretty rare.
|
||||
return Errors{p, []error{err}}
|
||||
return Errors{p, typeErrors}
|
||||
}
|
||||
p.Pkg = typesPkg
|
||||
|
||||
@@ -485,7 +473,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
var initialCFlags []string
|
||||
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
|
||||
initialCFlags = append(initialCFlags, "-I"+p.Dir)
|
||||
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags, p.program.config.GOOS())
|
||||
generated, headerCode, cflags, ldflags, accessedFiles, errs := cgo.Process(files, p.program.workingDir, p.ImportPath, p.program.fset, initialCFlags)
|
||||
p.CFlags = append(initialCFlags, cflags...)
|
||||
p.CGoHeaders = headerCode
|
||||
for path, hash := range accessedFiles {
|
||||
|
||||
@@ -283,6 +283,46 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options
|
||||
// Tests are always run in the package directory.
|
||||
cmd.Dir = result.MainDir
|
||||
|
||||
// wasmtime is the default emulator used for `-target=wasip1`. wasmtime
|
||||
// is a WebAssembly runtime CLI with WASI enabled by default. However,
|
||||
// only stdio are allowed by default. For example, while STDOUT routes
|
||||
// to the host, other files don't. It also does not inherit environment
|
||||
// variables from the host. Some tests read testdata files, often from
|
||||
// outside the package directory. Other tests require temporary
|
||||
// writeable directories. We allow this by adding wasmtime flags below.
|
||||
if config.EmulatorName() == "wasmtime" {
|
||||
// At this point, The current working directory is at the package
|
||||
// directory. Ex. $GOROOT/src/compress/flate for compress/flate.
|
||||
// buildAndRun has already added arguments for wasmtime, that allow
|
||||
// read-access to files such as "testdata/huffman-zero.in".
|
||||
//
|
||||
// Ex. main(.wasm) --dir=. -- -test.v
|
||||
|
||||
// Below adds additional wasmtime flags in case a test reads files
|
||||
// outside its directory, like "../testdata/e.txt". This allows any
|
||||
// relative directory up to the module root, even if the test never
|
||||
// reads any files.
|
||||
//
|
||||
// Ex. run --dir=.. --dir=../.. --dir=../../..
|
||||
var dirs []string
|
||||
switch config.Target.GOOS {
|
||||
case "wasip1":
|
||||
dirs = dirsToModuleRootRel(result.MainDir, result.ModuleRoot)
|
||||
default:
|
||||
dirs = dirsToModuleRootAbs(result.MainDir, result.ModuleRoot)
|
||||
}
|
||||
|
||||
args := []string{"run"}
|
||||
for _, d := range dirs {
|
||||
args = append(args, "--dir="+d)
|
||||
}
|
||||
|
||||
args = append(args, "--env=PWD="+cmd.Dir)
|
||||
|
||||
args = append(args, cmd.Args[1:]...)
|
||||
cmd.Args = args
|
||||
}
|
||||
|
||||
// Run the test.
|
||||
start := time.Now()
|
||||
err = cmd.Run()
|
||||
@@ -769,6 +809,9 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error {
|
||||
// passes command line arguments and environment variables in a way appropriate
|
||||
// for the given emulator.
|
||||
func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) {
|
||||
|
||||
isSingleFile := strings.HasSuffix(pkgName, ".go")
|
||||
|
||||
// Determine whether we're on a system that supports environment variables
|
||||
// and command line parameters (operating systems, WASI) or not (baremetal,
|
||||
// WebAssembly in the browser). If we're on a system without an environment,
|
||||
@@ -781,7 +824,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
needsEnvInVars = true
|
||||
}
|
||||
}
|
||||
var args, env []string
|
||||
var args, emuArgs, env []string
|
||||
var extraCmdEnv []string
|
||||
if needsEnvInVars {
|
||||
runtimeGlobals := make(map[string]string)
|
||||
@@ -801,6 +844,21 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
"runtime": runtimeGlobals,
|
||||
}
|
||||
}
|
||||
} else if config.EmulatorName() == "wasmtime" {
|
||||
for _, v := range environmentVars {
|
||||
emuArgs = append(emuArgs, "--env", v)
|
||||
}
|
||||
if len(cmdArgs) != 0 {
|
||||
// Use of '--' argument no longer necessary as of Wasmtime v14:
|
||||
// https://github.com/bytecodealliance/wasmtime/pull/6946
|
||||
// args = append(args, "--")
|
||||
args = append(args, cmdArgs...)
|
||||
}
|
||||
|
||||
// Set this for nicer backtraces during tests, but don't override the user.
|
||||
if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok {
|
||||
extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1")
|
||||
}
|
||||
} else {
|
||||
// Pass environment variables and command line parameters as usual.
|
||||
// This also works on qemu-aarch64 etc.
|
||||
@@ -843,61 +901,27 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
return result, err
|
||||
}
|
||||
|
||||
name, emulator = emulator[0], emulator[1:]
|
||||
name = emulator[0]
|
||||
|
||||
// wasmtime is a WebAssembly runtime CLI with WASI enabled by default.
|
||||
// By default, only stdio is allowed. For example, while STDOUT routes
|
||||
// to the host, other files don't. It also does not inherit environment
|
||||
// variables from the host. Some tests read testdata files, often from
|
||||
// outside the package directory. Other tests require temporary
|
||||
// writeable directories. We allow this by adding wasmtime flags below.
|
||||
if name == "wasmtime" {
|
||||
var emuArgs []string
|
||||
|
||||
// Extract the wasmtime subcommand (e.g. "run" or "serve")
|
||||
if len(emulator) > 1 {
|
||||
emuArgs = append(emuArgs, emulator[0])
|
||||
emulator = emulator[1:]
|
||||
}
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
|
||||
// Below adds additional wasmtime flags in case a test reads files
|
||||
// outside its directory, like "../testdata/e.txt". This allows any
|
||||
// relative directory up to the module root, even if the test never
|
||||
// reads any files.
|
||||
if config.TestConfig.CompileTestBinary {
|
||||
// Set working directory to package dir
|
||||
wd = result.MainDir
|
||||
|
||||
// Add relative dirs (../, ../..) up to module root (for wasip1)
|
||||
dirs := dirsToModuleRootRel(result.MainDir, result.ModuleRoot)
|
||||
|
||||
// Add absolute dirs up to module root (for wasip2)
|
||||
dirs = append(dirs, dirsToModuleRootAbs(result.MainDir, result.ModuleRoot)...)
|
||||
|
||||
for _, d := range dirs {
|
||||
emuArgs = append(emuArgs, "--dir="+d)
|
||||
}
|
||||
} else {
|
||||
// Wasmtime needs some special flags to pass environment variables
|
||||
// and allow reading from the current directory.
|
||||
switch config.Options.Target {
|
||||
case "wasip1":
|
||||
emuArgs = append(emuArgs, "--dir=.")
|
||||
case "wasip2":
|
||||
dir := result.MainDir
|
||||
if isSingleFile {
|
||||
cwd, _ := os.Getwd()
|
||||
dir = cwd
|
||||
}
|
||||
emuArgs = append(emuArgs, "--dir="+dir)
|
||||
emuArgs = append(emuArgs, "--env=PWD="+dir)
|
||||
}
|
||||
|
||||
emuArgs = append(emuArgs, "--dir="+wd)
|
||||
emuArgs = append(emuArgs, "--env=PWD="+wd)
|
||||
for _, v := range environmentVars {
|
||||
emuArgs = append(emuArgs, "--env", v)
|
||||
}
|
||||
|
||||
// Set this for nicer backtraces during tests, but don't override the user.
|
||||
if _, ok := os.LookupEnv("WASMTIME_BACKTRACE_DETAILS"); !ok {
|
||||
extraCmdEnv = append(extraCmdEnv, "WASMTIME_BACKTRACE_DETAILS=1")
|
||||
}
|
||||
|
||||
emulator = append(emuArgs, emulator...)
|
||||
}
|
||||
|
||||
args = append(emulator, args...)
|
||||
emuArgs = append(emuArgs, emulator[1:]...)
|
||||
args = append(emuArgs, args...)
|
||||
}
|
||||
var cmd *exec.Cmd
|
||||
if ctx != nil {
|
||||
@@ -927,7 +951,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
|
||||
// Run binary.
|
||||
if config.Options.PrintCommands != nil {
|
||||
config.Options.PrintCommands(cmd.Path, cmd.Args[1:]...)
|
||||
config.Options.PrintCommands(cmd.Path, cmd.Args...)
|
||||
}
|
||||
err = run(cmd, result)
|
||||
if err != nil {
|
||||
@@ -1232,169 +1256,36 @@ func getBMPPorts() (gdbPort, uartPort string, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
usageBuild = `Build compiles the packages named by the import paths, along with their
|
||||
dependencies, but it does not install the results. The output binary is
|
||||
specified using the -o parameter. The generated file type depends on the
|
||||
extension:
|
||||
|
||||
.o:
|
||||
Create a relocatable object file. You can use this option if you
|
||||
don't want to use the TinyGo build system or want to do other custom
|
||||
things.
|
||||
|
||||
.ll:
|
||||
Create textual LLVM IR, after optimization. This is mainly useful
|
||||
for debugging.
|
||||
|
||||
.bc:
|
||||
Create LLVM bitcode, after optimization. This may be useful for
|
||||
debugging or for linking into other programs using LTO.
|
||||
|
||||
.hex:
|
||||
Create an Intel HEX file to flash it to a microcontroller.
|
||||
|
||||
.bin:
|
||||
Similar, but create a binary file.
|
||||
|
||||
.wasm:
|
||||
Compile and link a WebAssembly file.
|
||||
|
||||
(all other) Compile and link the program into a regular executable. For
|
||||
microcontrollers, it is common to use the .elf file extension to indicate a
|
||||
linked ELF file is generated. For Linux, it is common to build binaries with no
|
||||
extension at all.`
|
||||
|
||||
usageRun = `Run the program, either directly on the host or in an emulated environment
|
||||
(depending on -target).`
|
||||
|
||||
usageFlash = `Flash the program to a microcontroller. Some common flags are described below.
|
||||
|
||||
-target={name}:
|
||||
Specifies the type of microcontroller that is used. The name of the
|
||||
microcontroller is given on the individual pages for each board type
|
||||
listed under Microcontrollers
|
||||
(https://tinygo.org/docs/reference/microcontrollers/).
|
||||
Examples: "arduino-nano", "d1mini", "xiao".
|
||||
|
||||
-monitor:
|
||||
Start the serial monitor (see below) immediately after
|
||||
flashing. However, some microcontrollers need a split second
|
||||
or two to configure the serial port after flashing, and
|
||||
using the "-monitor" flag can fail because the serial
|
||||
monitor starts too quickly. In that case, use the "tinygo
|
||||
monitor" command explicitly.`
|
||||
|
||||
usageMonitor = `Start the serial monitor on the serial port that is connected to the
|
||||
microcontroller. If there is only a single board attached to the host computer,
|
||||
the default values for various options should be sufficient. In other
|
||||
situations, particularly if you have multiple microcontrollers attached, some
|
||||
parameters may need to be overridden using the following flags:
|
||||
|
||||
-port={port}:
|
||||
If there are multiple microcontroller attached, an error
|
||||
message will display a list of potential serial ports. The
|
||||
appropriate port can be specified by this flag. On Linux,
|
||||
the port will be something like /dev/ttyUSB0 or /dev/ttyACM1.
|
||||
On MacOS, the port will look like /dev/cu.usbserial-1420. On
|
||||
Windows, the port will be something like COM1 or COM31.
|
||||
|
||||
-baudrate={rate}:
|
||||
The default baud rate is 115200. Boards using the AVR
|
||||
processor (e.g. Arduino Nano, Arduino Mega 2560) use 9600
|
||||
instead.
|
||||
|
||||
-target={name}:
|
||||
If you have more than one microcontrollers attached, you can
|
||||
sometimes just specify the target name and let tinygo
|
||||
monitor figure out the port. Sometimes, this does not work
|
||||
and you have to explicitly use the -port flag.
|
||||
|
||||
The serial monitor intercepts several control characters for its own use instead of sending them
|
||||
to the microcontroller:
|
||||
|
||||
Control-C: terminates the tinygo monitor
|
||||
Control-Z: suspends the tinygo monitor and drops back into shell
|
||||
Control-\: terminates the tinygo monitor with a stack trace
|
||||
Control-S: flow control, suspends output to the console
|
||||
Control-Q: flow control, resumes output to the console
|
||||
Control-@: thrown away by tinygo monitor
|
||||
|
||||
Note: If you are using os.Stdin on the microcontroller, you may find that a CR
|
||||
character on the host computer (also known as Enter, ^M, or \r) is transmitted
|
||||
to the microcontroller without conversion, so os.Stdin returns a \r character
|
||||
instead of the expected \n (also known as ^J, NL, or LF) to indicate
|
||||
end-of-line. You may be able to get around this problem by hitting Control-J in
|
||||
tinygo monitor to transmit the \n end-of-line character.`
|
||||
|
||||
usageGdb = `Build the program, optionally flash it to a microcontroller if it is a remote
|
||||
target, and drop into a GDB shell. From there you can set breakpoints, start the
|
||||
program with "run" or "continue" ("run" for a local program, continue for
|
||||
on-chip debugging), single-step, show a backtrace, break and resume the program
|
||||
with Ctrl-C/"continue", etc. You may need to install extra tools (like openocd
|
||||
and arm-none-eabi-gdb) to be able to do this. Also, you may need a dedicated
|
||||
debugger to be able to debug certain boards if no debugger is integrated. Some
|
||||
boards (like the BBC micro:bit and most professional evaluation boards) have an
|
||||
integrated debugger.`
|
||||
|
||||
usageClean = `Clean the cache directory, normally stored in $HOME/.cache/tinygo. This is not
|
||||
normally needed.`
|
||||
|
||||
usageHelp = `Print a short summary of the available commands, plus a list of command flags.`
|
||||
usageVersion = `Print the version of the command and the version of the used $GOROOT.`
|
||||
usageEnv = `Print a list of environment variables that affect TinyGo (as a shell script).
|
||||
If one or more variable names are given as arguments, env prints the value of
|
||||
each on a new line.`
|
||||
|
||||
usageDefault = `TinyGo is a Go compiler for small places.
|
||||
version: %s
|
||||
usage: %s <command> [arguments]
|
||||
commands:
|
||||
build: compile packages and dependencies
|
||||
run: compile and run immediately
|
||||
test: test packages
|
||||
flash: compile and flash to the device
|
||||
gdb: run/flash and immediately enter GDB
|
||||
lldb: run/flash and immediately enter LLDB
|
||||
monitor: open communication port
|
||||
ports: list available serial ports
|
||||
env: list environment variables used during build
|
||||
list: run go list using the TinyGo root
|
||||
clean: empty cache directory (%s)
|
||||
targets: list targets
|
||||
info: show info for specified target
|
||||
version: show version
|
||||
help: print this help text`
|
||||
)
|
||||
|
||||
var (
|
||||
commandHelp = map[string]string{
|
||||
"build": usageBuild,
|
||||
"run": usageRun,
|
||||
"flash": usageFlash,
|
||||
"monitor": usageMonitor,
|
||||
"gdb": usageGdb,
|
||||
"clean": usageClean,
|
||||
"help": usageHelp,
|
||||
"version": usageVersion,
|
||||
"env": usageEnv,
|
||||
}
|
||||
)
|
||||
|
||||
func usage(command string) {
|
||||
val, ok := commandHelp[command]
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, usageDefault, goenv.Version(), os.Args[0], goenv.Get("GOCACHE"))
|
||||
switch command {
|
||||
default:
|
||||
fmt.Fprintln(os.Stderr, "TinyGo is a Go compiler for small places.")
|
||||
fmt.Fprintln(os.Stderr, "version:", goenv.Version())
|
||||
fmt.Fprintf(os.Stderr, "usage: %s <command> [arguments]\n", os.Args[0])
|
||||
fmt.Fprintln(os.Stderr, "\ncommands:")
|
||||
fmt.Fprintln(os.Stderr, " build: compile packages and dependencies")
|
||||
fmt.Fprintln(os.Stderr, " run: compile and run immediately")
|
||||
fmt.Fprintln(os.Stderr, " test: test packages")
|
||||
fmt.Fprintln(os.Stderr, " flash: compile and flash to the device")
|
||||
fmt.Fprintln(os.Stderr, " gdb: run/flash and immediately enter GDB")
|
||||
fmt.Fprintln(os.Stderr, " lldb: run/flash and immediately enter LLDB")
|
||||
fmt.Fprintln(os.Stderr, " monitor: open communication port")
|
||||
fmt.Fprintln(os.Stderr, " ports: list available serial ports")
|
||||
fmt.Fprintln(os.Stderr, " env: list environment variables used during build")
|
||||
fmt.Fprintln(os.Stderr, " list: run go list using the TinyGo root")
|
||||
fmt.Fprintln(os.Stderr, " clean: empty cache directory ("+goenv.Get("GOCACHE")+")")
|
||||
fmt.Fprintln(os.Stderr, " targets: list targets")
|
||||
fmt.Fprintln(os.Stderr, " info: show info for specified target")
|
||||
fmt.Fprintln(os.Stderr, " version: show version")
|
||||
fmt.Fprintln(os.Stderr, " help: print this help text")
|
||||
|
||||
if flag.Parsed() {
|
||||
fmt.Fprintln(os.Stderr, "\nflags:")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
fmt.Fprintln(os.Stderr, "\nfor more details, see https://tinygo.org/docs/reference/usage/")
|
||||
} else {
|
||||
fmt.Fprintln(os.Stderr, val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleCompilerError(err error) {
|
||||
@@ -1439,20 +1330,19 @@ func (m globalValuesFlag) Set(value string) error {
|
||||
|
||||
// parseGoLinkFlag parses the -ldflags parameter. Its primary purpose right now
|
||||
// is the -X flag, for setting the value of global string variables.
|
||||
func parseGoLinkFlag(flagsString string) (map[string]map[string]string, string, error) {
|
||||
func parseGoLinkFlag(flagsString string) (map[string]map[string]string, error) {
|
||||
set := flag.NewFlagSet("link", flag.ExitOnError)
|
||||
globalVarValues := make(globalValuesFlag)
|
||||
set.Var(globalVarValues, "X", "Set the value of the string variable to the given value.")
|
||||
extLDFlags := set.String("extldflags", "", "additional flags to pass to external linker")
|
||||
flags, err := shlex.Split(flagsString)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
err = set.Parse(flags)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
return nil, err
|
||||
}
|
||||
return map[string]map[string]string(globalVarValues), *extLDFlags, nil
|
||||
return map[string]map[string]string(globalVarValues), nil
|
||||
}
|
||||
|
||||
// getListOfPackages returns a standard list of packages for a given list that might
|
||||
@@ -1502,14 +1392,13 @@ func main() {
|
||||
var tags buildutil.TagsFlag
|
||||
flag.Var(&tags, "tags", "a space-separated list of extra build tags")
|
||||
target := flag.String("target", "", "chip/board name or JSON target specification file")
|
||||
buildMode := flag.String("buildmode", "", "build mode to use (default, c-shared)")
|
||||
var stackSize uint64
|
||||
flag.Func("stack-size", "goroutine stack size (if unknown at compile time)", func(s string) error {
|
||||
size, err := bytesize.Parse(s)
|
||||
stackSize = uint64(size)
|
||||
return err
|
||||
})
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full, html)")
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full)")
|
||||
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines")
|
||||
printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
|
||||
printCommands := flag.Bool("x", false, "Print commands")
|
||||
@@ -1572,7 +1461,6 @@ func main() {
|
||||
|
||||
// Early command processing, before commands are interpreted by the Go flag
|
||||
// library.
|
||||
handleChdirFlag()
|
||||
switch command {
|
||||
case "clang", "ld.lld", "wasm-ld":
|
||||
err := builder.RunTool(command, os.Args[2:]...)
|
||||
@@ -1585,7 +1473,7 @@ func main() {
|
||||
}
|
||||
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
globalVarValues, extLDFlags, err := parseGoLinkFlag(*ldflags)
|
||||
globalVarValues, err := parseGoLinkFlag(*ldflags)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
@@ -1611,7 +1499,6 @@ func main() {
|
||||
GOARM: goenv.Get("GOARM"),
|
||||
GOMIPS: goenv.Get("GOMIPS"),
|
||||
Target: *target,
|
||||
BuildMode: *buildMode,
|
||||
StackSize: stackSize,
|
||||
Opt: *opt,
|
||||
GC: *gc,
|
||||
@@ -1646,14 +1533,6 @@ func main() {
|
||||
options.PrintCommands = printCommand
|
||||
}
|
||||
|
||||
if extLDFlags != "" {
|
||||
options.ExtLDFlags, err = shlex.Split(extLDFlags)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "could not parse -extldflags:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
err = options.Verify()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
@@ -2067,56 +1946,3 @@ type outputEntry struct {
|
||||
stderr bool
|
||||
data []byte
|
||||
}
|
||||
|
||||
// handleChdirFlag handles the -C flag before doing anything else.
|
||||
// The -C flag must be the first flag on the command line, to make it easy to find
|
||||
// even with commands that have custom flag parsing.
|
||||
// handleChdirFlag handles the flag by chdir'ing to the directory
|
||||
// and then removing that flag from the command line entirely.
|
||||
//
|
||||
// We have to handle the -C flag this way for two reasons:
|
||||
//
|
||||
// 1. Toolchain selection needs to be in the right directory to look for go.mod and go.work.
|
||||
//
|
||||
// 2. A toolchain switch later on reinvokes the new go command with the same arguments.
|
||||
// The parent toolchain has already done the chdir; the child must not try to do it again.
|
||||
|
||||
func handleChdirFlag() {
|
||||
used := 2 // b.c. command at os.Args[1]
|
||||
if used >= len(os.Args) {
|
||||
return
|
||||
}
|
||||
|
||||
var dir string
|
||||
switch a := os.Args[used]; {
|
||||
default:
|
||||
return
|
||||
|
||||
case a == "-C", a == "--C":
|
||||
if used+1 >= len(os.Args) {
|
||||
return
|
||||
}
|
||||
dir = os.Args[used+1]
|
||||
os.Args = slicesDelete(os.Args, used, used+2)
|
||||
|
||||
case strings.HasPrefix(a, "-C="), strings.HasPrefix(a, "--C="):
|
||||
_, dir, _ = strings.Cut(a, "=")
|
||||
os.Args = slicesDelete(os.Args, used, used+1)
|
||||
}
|
||||
|
||||
if err := os.Chdir(dir); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "cannot chdir:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// go1.19 compatibility: lacks slices package
|
||||
func slicesDelete[S ~[]E, E any](s S, i, j int) S {
|
||||
_ = s[i:j:len(s)] // bounds check
|
||||
|
||||
if i == j {
|
||||
return s
|
||||
}
|
||||
|
||||
return append(s[:i], s[j:]...)
|
||||
}
|
||||
|
||||
+23
-352
@@ -6,7 +6,6 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"io"
|
||||
@@ -15,16 +14,13 @@ import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aykevl/go-wasm"
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
"github.com/tinygo-org/tinygo/builder"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/diagnostics"
|
||||
@@ -79,7 +75,6 @@ func TestBuild(t *testing.T) {
|
||||
"oldgo/",
|
||||
"print.go",
|
||||
"reflect.go",
|
||||
"signal.go",
|
||||
"slice.go",
|
||||
"sort.go",
|
||||
"stdlib.go",
|
||||
@@ -218,7 +213,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
// isWebAssembly := strings.HasPrefix(spec.Triple, "wasm")
|
||||
isWASI := strings.HasPrefix(options.Target, "wasi")
|
||||
isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
|
||||
isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu"
|
||||
|
||||
for _, name := range tests {
|
||||
if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
|
||||
@@ -283,13 +277,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if isWebAssembly || isBaremetal || options.GOOS == "windows" {
|
||||
switch name {
|
||||
case "signal.go":
|
||||
// Signals only work on POSIX-like systems.
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
name := name // redefine to avoid race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@@ -405,13 +392,17 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
// of the path.
|
||||
path := TESTDATA + "/" + name
|
||||
// Get the expected output for this test.
|
||||
expectedOutputPath := path[:len(path)-3] + ".txt"
|
||||
txtpath := path[:len(path)-3] + ".txt"
|
||||
pkgName := "./" + path
|
||||
if path[len(path)-1] == '/' {
|
||||
expectedOutputPath = path + "out.txt"
|
||||
txtpath = path + "out.txt"
|
||||
options.Directory = path
|
||||
pkgName = "."
|
||||
}
|
||||
expected, err := os.ReadFile(txtpath)
|
||||
if err != nil {
|
||||
t.Fatal("could not read expected output file:", err)
|
||||
}
|
||||
|
||||
config, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
@@ -429,14 +420,14 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
|
||||
t.Log(line)
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
t.Logf("output:\n%s", stdout.String())
|
||||
}
|
||||
t.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
actual := stdout.Bytes()
|
||||
// putchar() prints CRLF, convert it to LF.
|
||||
actual := bytes.Replace(stdout.Bytes(), []byte{'\r', '\n'}, []byte{'\n'}, -1)
|
||||
expected = bytes.Replace(expected, []byte{'\r', '\n'}, []byte{'\n'}, -1) // for Windows
|
||||
|
||||
if config.EmulatorName() == "simavr" {
|
||||
// Strip simavr log formatting.
|
||||
actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
|
||||
@@ -451,12 +442,17 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
}
|
||||
|
||||
// Check whether the command ran successfully.
|
||||
fail := false
|
||||
if err != nil {
|
||||
t.Error("failed to run:", err)
|
||||
t.Log("failed to run:", err)
|
||||
fail = true
|
||||
} else if !bytes.Equal(expected, actual) {
|
||||
t.Logf("output did not match (expected %d bytes, got %d bytes):", len(expected), len(actual))
|
||||
t.Logf(string(Diff("expected", expected, "actual", actual)))
|
||||
fail = true
|
||||
}
|
||||
checkOutput(t, expectedOutputPath, actual)
|
||||
|
||||
if t.Failed() {
|
||||
if fail {
|
||||
r := bufio.NewReader(bytes.NewReader(actual))
|
||||
for {
|
||||
line, err := r.ReadString('\n')
|
||||
@@ -474,21 +470,20 @@ func TestWebAssembly(t *testing.T) {
|
||||
t.Parallel()
|
||||
type testCase struct {
|
||||
name string
|
||||
target string
|
||||
panicStrategy string
|
||||
imports []string
|
||||
}
|
||||
for _, tc := range []testCase{
|
||||
// Test whether there really are no imports when using -panic=trap. This
|
||||
// tests the bugfix for https://github.com/tinygo-org/tinygo/issues/4161.
|
||||
{name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}},
|
||||
{name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}},
|
||||
{name: "panic-default", imports: []string{"wasi_snapshot_preview1.fd_write"}},
|
||||
{name: "panic-trap", panicStrategy: "trap", imports: []string{}},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget(tc.target, sema)
|
||||
options := optionsFromTarget("wasi", sema)
|
||||
options.PanicStrategy = tc.panicStrategy
|
||||
config, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
@@ -520,7 +515,7 @@ func TestWebAssembly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !stringSlicesEqual(imports, tc.imports) {
|
||||
if !slices.Equal(imports, tc.imports) {
|
||||
t.Errorf("import list not as expected!\nexpected: %v\nactual: %v", tc.imports, imports)
|
||||
}
|
||||
}
|
||||
@@ -528,330 +523,6 @@ func TestWebAssembly(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func stringSlicesEqual(s1, s2 []string) bool {
|
||||
// We can use slices.Equal once we drop support for Go 1.20 (it was added in
|
||||
// Go 1.21).
|
||||
if len(s1) != len(s2) {
|
||||
return false
|
||||
}
|
||||
for i, s := range s1 {
|
||||
if s != s2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestWasmExport(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
target string
|
||||
buildMode string
|
||||
scheduler string
|
||||
file string
|
||||
noOutput bool
|
||||
command bool // call _start (command mode) instead of _initialize
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
// "command mode" WASI
|
||||
{
|
||||
name: "WASIp1-command",
|
||||
target: "wasip1",
|
||||
command: true,
|
||||
},
|
||||
// "reactor mode" WASI (with -buildmode=c-shared)
|
||||
{
|
||||
name: "WASIp1-reactor",
|
||||
target: "wasip1",
|
||||
buildMode: "c-shared",
|
||||
},
|
||||
// Make sure reactor mode also works without a scheduler.
|
||||
{
|
||||
name: "WASIp1-reactor-noscheduler",
|
||||
target: "wasip1",
|
||||
buildMode: "c-shared",
|
||||
scheduler: "none",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
},
|
||||
// Test -target=wasm-unknown with the default build mode (which is
|
||||
// c-shared).
|
||||
{
|
||||
name: "wasm-unknown-reactor",
|
||||
target: "wasm-unknown",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
noOutput: true, // wasm-unknown cannot produce output
|
||||
},
|
||||
// Test -target=wasm-unknown with -buildmode=default, which makes it run
|
||||
// in command mode.
|
||||
{
|
||||
name: "wasm-unknown-command",
|
||||
target: "wasm-unknown",
|
||||
buildMode: "default",
|
||||
file: "wasmexport-noscheduler.go",
|
||||
noOutput: true, // wasm-unknown cannot produce output
|
||||
command: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Build the wasm binary.
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget(tc.target, sema)
|
||||
options.BuildMode = tc.buildMode
|
||||
options.Scheduler = tc.scheduler
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
filename := "wasmexport.go"
|
||||
if tc.file != "" {
|
||||
filename = tc.file
|
||||
}
|
||||
result, err := builder.Build("testdata/"+filename, ".wasm", tmpdir, buildConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to build binary:", err)
|
||||
}
|
||||
|
||||
// Read the wasm binary back into memory.
|
||||
data, err := os.ReadFile(result.Binary)
|
||||
if err != nil {
|
||||
t.Fatal("could not read wasm binary: ", err)
|
||||
}
|
||||
|
||||
// Set up the wazero runtime.
|
||||
output := &bytes.Buffer{}
|
||||
ctx := context.Background()
|
||||
r := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfigInterpreter())
|
||||
defer r.Close(ctx)
|
||||
config := wazero.NewModuleConfig().
|
||||
WithStdout(output).WithStderr(output).
|
||||
WithStartFunctions()
|
||||
|
||||
// Prepare for testing.
|
||||
var mod api.Module
|
||||
mustCall := func(results []uint64, err error) []uint64 {
|
||||
if err != nil {
|
||||
t.Error("failed to run function:", err)
|
||||
}
|
||||
return results
|
||||
}
|
||||
checkResult := func(name string, results []uint64, expected []uint64) {
|
||||
if len(results) != len(expected) {
|
||||
t.Errorf("%s: expected %v but got %v", name, expected, results)
|
||||
}
|
||||
for i, result := range results {
|
||||
if result != expected[i] {
|
||||
t.Errorf("%s: expected %v but got %v", name, expected, results)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
runTests := func() {
|
||||
// Test an exported function without params or return value.
|
||||
checkResult("hello()", mustCall(mod.ExportedFunction("hello").Call(ctx)), nil)
|
||||
|
||||
// Test that we can call an exported function more than once.
|
||||
checkResult("add(3, 5)", mustCall(mod.ExportedFunction("add").Call(ctx, 3, 5)), []uint64{8})
|
||||
checkResult("add(7, 9)", mustCall(mod.ExportedFunction("add").Call(ctx, 7, 9)), []uint64{16})
|
||||
checkResult("add(6, 1)", mustCall(mod.ExportedFunction("add").Call(ctx, 6, 1)), []uint64{7})
|
||||
|
||||
// Test that imported functions can call exported functions
|
||||
// again.
|
||||
checkResult("reentrantCall(2, 3)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 2, 3)), []uint64{5})
|
||||
checkResult("reentrantCall(1, 8)", mustCall(mod.ExportedFunction("reentrantCall").Call(ctx, 1, 8)), []uint64{9})
|
||||
}
|
||||
|
||||
// Add wasip1 module.
|
||||
wasi_snapshot_preview1.MustInstantiate(ctx, r)
|
||||
|
||||
// Add custom "tester" module.
|
||||
callOutside := func(a, b int32) int32 {
|
||||
results, err := mod.ExportedFunction("add").Call(ctx, uint64(a), uint64(b))
|
||||
if err != nil {
|
||||
t.Error("could not call exported add function:", err)
|
||||
}
|
||||
return int32(results[0])
|
||||
}
|
||||
callTestMain := func() {
|
||||
runTests()
|
||||
}
|
||||
builder := r.NewHostModuleBuilder("tester")
|
||||
builder.NewFunctionBuilder().WithFunc(callOutside).Export("callOutside")
|
||||
builder.NewFunctionBuilder().WithFunc(callTestMain).Export("callTestMain")
|
||||
_, err = builder.Instantiate(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Parse and instantiate the wasm.
|
||||
mod, err = r.InstantiateWithConfig(ctx, data, config)
|
||||
if err != nil {
|
||||
t.Fatal("could not instantiate wasm module:", err)
|
||||
}
|
||||
|
||||
// Initialize the module and run the tests.
|
||||
if tc.command {
|
||||
// Call _start (the entry point), which calls
|
||||
// tester.callTestMain, which then runs all the tests.
|
||||
_, err := mod.ExportedFunction("_start").Call(ctx)
|
||||
if err != nil {
|
||||
if exitErr, ok := err.(*sys.ExitError); ok && exitErr.ExitCode() == 0 {
|
||||
// Exited with code 0. Nothing to worry about.
|
||||
} else {
|
||||
t.Error("failed to run _start:", err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Run the _initialize call, because this is reactor mode wasm.
|
||||
mustCall(mod.ExportedFunction("_initialize").Call(ctx))
|
||||
runTests()
|
||||
}
|
||||
|
||||
// Check that the output matches the expected output.
|
||||
// (Skip this for wasm-unknown because it can't produce output).
|
||||
if !tc.noOutput {
|
||||
checkOutput(t, "testdata/wasmexport.txt", output.Bytes())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test js.FuncOf (for syscall/js).
|
||||
// This test might be extended in the future to cover more cases in syscall/js.
|
||||
func TestWasmFuncOf(t *testing.T) {
|
||||
// Build the wasm binary.
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := builder.Build("testdata/wasmfunc.go", ".wasm", tmpdir, buildConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to build binary:", err)
|
||||
}
|
||||
|
||||
// Test the resulting binary using NodeJS.
|
||||
output := &bytes.Buffer{}
|
||||
cmd := exec.Command("node", "testdata/wasmfunc.js", result.Binary, buildConfig.BuildMode())
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
t.Error("failed to run node:", err)
|
||||
}
|
||||
checkOutput(t, "testdata/wasmfunc.txt", output.Bytes())
|
||||
}
|
||||
|
||||
// Test //go:wasmexport in JavaScript (using NodeJS).
|
||||
func TestWasmExportJS(t *testing.T) {
|
||||
t.Parallel()
|
||||
type testCase struct {
|
||||
name string
|
||||
buildMode string
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{name: "default"},
|
||||
{name: "c-shared", buildMode: "c-shared"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Build the wasm binary.
|
||||
tmpdir := t.TempDir()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
options.BuildMode = tc.buildMode
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
result, err := builder.Build("testdata/wasmexport-noscheduler.go", ".wasm", tmpdir, buildConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to build binary:", err)
|
||||
}
|
||||
|
||||
// Test the resulting binary using NodeJS.
|
||||
output := &bytes.Buffer{}
|
||||
cmd := exec.Command("node", "testdata/wasmexport.js", result.Binary, buildConfig.BuildMode())
|
||||
cmd.Stdout = output
|
||||
cmd.Stderr = output
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
t.Error("failed to run node:", err)
|
||||
}
|
||||
checkOutput(t, "testdata/wasmexport.txt", output.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Test whether Go.run() (in wasm_exec.js) normally returns and returns the
|
||||
// right exit code.
|
||||
func TestWasmExit(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
output string
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{name: "normal", output: "exit code: 0\n"},
|
||||
{name: "exit-0", output: "exit code: 0\n"},
|
||||
{name: "exit-0-sleep", output: "slept\nexit code: 0\n"},
|
||||
{name: "exit-1", output: "exit code: 1\n"},
|
||||
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
buildConfig, err := builder.NewConfig(&options)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
buildConfig.Target.Emulator = "node testdata/wasmexit.js {}"
|
||||
output := &bytes.Buffer{}
|
||||
_, err = buildAndRun("testdata/wasmexit.go", buildConfig, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
|
||||
return cmd.Run()
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expected := "wasmexit test: " + tc.name + "\n" + tc.output
|
||||
checkOutputData(t, []byte(expected), output.Bytes())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Check whether the output of a test equals the expected output.
|
||||
func checkOutput(t *testing.T, filename string, actual []byte) {
|
||||
expectedOutput, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
t.Fatal("could not read output file:", err)
|
||||
}
|
||||
checkOutputData(t, expectedOutput, actual)
|
||||
}
|
||||
|
||||
func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
|
||||
expectedOutput = bytes.ReplaceAll(expectedOutput, []byte("\r\n"), []byte("\n"))
|
||||
actual = bytes.ReplaceAll(actual, []byte("\r\n"), []byte("\n"))
|
||||
|
||||
if !bytes.Equal(actual, expectedOutput) {
|
||||
t.Errorf("output did not match (expected %d bytes, got %d bytes):", len(expectedOutput), len(actual))
|
||||
t.Error(string(Diff("expected", expectedOutput, "actual", actual)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || tkey
|
||||
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3
|
||||
|
||||
// If you update the above build constraint, you'll probably also need to update
|
||||
// src/runtime/rand_hwrng.go.
|
||||
|
||||
@@ -1,185 +0,0 @@
|
||||
package macos
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Exported symbols copied from Big Go, but stripped of functionality.
|
||||
// Allows building of crypto/x509 on macOS.
|
||||
|
||||
const (
|
||||
ErrSecCertificateExpired = -67818
|
||||
ErrSecHostNameMismatch = -67602
|
||||
ErrSecNotTrusted = -67843
|
||||
)
|
||||
|
||||
var ErrNoTrustSettings = errors.New("no trust settings found")
|
||||
var SecPolicyAppleSSL = StringToCFString("1.2.840.113635.100.1.3") // defined by POLICYMACRO
|
||||
var SecPolicyOid = StringToCFString("SecPolicyOid")
|
||||
var SecTrustSettingsPolicy = StringToCFString("kSecTrustSettingsPolicy")
|
||||
var SecTrustSettingsPolicyString = StringToCFString("kSecTrustSettingsPolicyString")
|
||||
var SecTrustSettingsResultKey = StringToCFString("kSecTrustSettingsResult")
|
||||
|
||||
func CFArrayAppendValue(array CFRef, val CFRef) {}
|
||||
|
||||
func CFArrayGetCount(array CFRef) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFDataGetBytePtr(data CFRef) uintptr {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFDataGetLength(data CFRef) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFDataToSlice(data CFRef) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func CFEqual(a, b CFRef) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func CFErrorGetCode(errRef CFRef) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFNumberGetValue(num CFRef) (int32, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func CFRelease(ref CFRef) {}
|
||||
|
||||
func CFStringToString(ref CFRef) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func ReleaseCFArray(array CFRef) {}
|
||||
|
||||
func SecCertificateCopyData(cert CFRef) ([]byte, error) {
|
||||
return nil, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustEvaluateWithError(trustObj CFRef) (int, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustGetCertificateCount(trustObj CFRef) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func SecTrustGetResult(trustObj CFRef, result CFRef) (CFRef, CFRef, error) {
|
||||
return 0, 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustSetVerifyDate(trustObj CFRef, dateRef CFRef) error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
type CFRef uintptr
|
||||
|
||||
func BytesToCFData(b []byte) CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFArrayCreateMutable() CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFArrayGetValueAtIndex(array CFRef, index int) CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFDateCreate(seconds float64) CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFDictionaryGetValueIfPresent(dict CFRef, key CFString) (value CFRef, ok bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func CFErrorCopyDescription(errRef CFRef) CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
func CFStringCreateExternalRepresentation(strRef CFRef) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecCertificateCreateWithData(b []byte) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecPolicyCreateSSL(name string) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustCreateWithCertificates(certs CFRef, policies CFRef) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustEvaluate(trustObj CFRef) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustGetCertificateAtIndex(trustObj CFRef, i int) (CFRef, error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustSettingsCopyCertificates(domain SecTrustSettingsDomain) (certArray CFRef, err error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func SecTrustSettingsCopyTrustSettings(cert CFRef, domain SecTrustSettingsDomain) (trustSettings CFRef, err error) {
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
|
||||
func TimeToCFDateRef(t time.Time) CFRef {
|
||||
return 0
|
||||
}
|
||||
|
||||
type CFString CFRef
|
||||
|
||||
func StringToCFString(s string) CFString {
|
||||
return 0
|
||||
}
|
||||
|
||||
type OSStatus struct {
|
||||
// Has unexported fields.
|
||||
}
|
||||
|
||||
func (s OSStatus) Error() string
|
||||
|
||||
type SecTrustResultType int32
|
||||
|
||||
const (
|
||||
SecTrustResultInvalid SecTrustResultType = iota
|
||||
SecTrustResultProceed
|
||||
SecTrustResultConfirm // deprecated
|
||||
SecTrustResultDeny
|
||||
SecTrustResultUnspecified
|
||||
SecTrustResultRecoverableTrustFailure
|
||||
SecTrustResultFatalTrustFailure
|
||||
SecTrustResultOtherError
|
||||
)
|
||||
|
||||
type SecTrustSettingsDomain int32
|
||||
|
||||
const (
|
||||
SecTrustSettingsDomainUser SecTrustSettingsDomain = iota
|
||||
SecTrustSettingsDomainAdmin
|
||||
SecTrustSettingsDomainSystem
|
||||
)
|
||||
|
||||
type SecTrustSettingsResult int32
|
||||
|
||||
const (
|
||||
SecTrustSettingsResultInvalid SecTrustSettingsResult = iota
|
||||
SecTrustSettingsResultTrustRoot
|
||||
SecTrustSettingsResultTrustAsRoot
|
||||
SecTrustSettingsResultDeny
|
||||
SecTrustSettingsResultUnspecified
|
||||
)
|
||||
@@ -1,139 +0,0 @@
|
||||
//go:build tkey
|
||||
|
||||
// Hand written file based on https://github.com/tillitis/tkey-libs/blob/main/include/tkey/tk1_mem.h
|
||||
|
||||
package tkey
|
||||
|
||||
import (
|
||||
"runtime/volatile"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Peripherals
|
||||
var (
|
||||
TRNG = (*TRNG_Type)(unsafe.Pointer(TK1_MMIO_TRNG_BASE))
|
||||
|
||||
TIMER = (*TIMER_Type)(unsafe.Pointer(TK1_MMIO_TIMER_BASE))
|
||||
|
||||
UDS = (*UDS_Type)(unsafe.Pointer(TK1_MMIO_UDS_BASE))
|
||||
|
||||
UART = (*UART_Type)(unsafe.Pointer(TK1_MMIO_UART_BASE))
|
||||
|
||||
TOUCH = (*TOUCH_Type)(unsafe.Pointer(TK1_MMIO_TOUCH_BASE))
|
||||
|
||||
TK1 = (*TK1_Type)(unsafe.Pointer(TK1_MMIO_TK1_BASE))
|
||||
)
|
||||
|
||||
// Memory sections
|
||||
const (
|
||||
TK1_ROM_BASE uintptr = 0x00000000
|
||||
|
||||
TK1_RAM_BASE uintptr = 0x40000000
|
||||
|
||||
TK1_MMIO_BASE uintptr = 0xc0000000
|
||||
|
||||
TK1_MMIO_TRNG_BASE uintptr = 0xc0000000
|
||||
|
||||
TK1_MMIO_TIMER_BASE uintptr = 0xc1000000
|
||||
|
||||
TK1_MMIO_UDS_BASE uintptr = 0xc2000000
|
||||
|
||||
TK1_MMIO_UART_BASE uintptr = 0xc3000000
|
||||
|
||||
TK1_MMIO_TOUCH_BASE uintptr = 0xc4000000
|
||||
|
||||
TK1_MMIO_FW_RAM_BASE uintptr = 0xd0000000
|
||||
|
||||
TK1_MMIO_TK1_BASE uintptr = 0xff000000
|
||||
)
|
||||
|
||||
// Memory section sizes
|
||||
const (
|
||||
TK1_RAM_SIZE uintptr = 0x20000
|
||||
|
||||
TK1_MMIO_SIZE uintptr = 0x3fffffff
|
||||
)
|
||||
|
||||
type TRNG_Type struct {
|
||||
_ [36]byte
|
||||
STATUS volatile.Register32
|
||||
_ [88]byte
|
||||
ENTROPY volatile.Register32
|
||||
}
|
||||
|
||||
type TIMER_Type struct {
|
||||
_ [32]byte
|
||||
CTRL volatile.Register32
|
||||
STATUS volatile.Register32
|
||||
PRESCALER volatile.Register32
|
||||
TIMER volatile.Register32
|
||||
}
|
||||
|
||||
type UDS_Type struct {
|
||||
_ [64]byte
|
||||
DATA [8]volatile.Register32
|
||||
}
|
||||
|
||||
type UART_Type struct {
|
||||
_ [128]byte
|
||||
RX_STATUS volatile.Register32
|
||||
RX_DATA volatile.Register32
|
||||
RX_BYTES volatile.Register32
|
||||
_ [116]byte
|
||||
TX_STATUS volatile.Register32
|
||||
TX_DATA volatile.Register32
|
||||
}
|
||||
|
||||
type TOUCH_Type struct {
|
||||
_ [36]byte
|
||||
STATUS volatile.Register32
|
||||
}
|
||||
|
||||
type TK1_Type struct {
|
||||
NAME0 volatile.Register32
|
||||
NAME1 volatile.Register32
|
||||
VERSION volatile.Register32
|
||||
_ [16]byte
|
||||
SWITCH_APP volatile.Register32
|
||||
_ [4]byte
|
||||
LED volatile.Register32
|
||||
GPIO volatile.Register16
|
||||
APP_ADDR volatile.Register32
|
||||
APP_SIZE volatile.Register32
|
||||
BLAKE2S volatile.Register32
|
||||
_ [72]byte
|
||||
CDI_FIRST [8]volatile.Register32
|
||||
_ [32]byte
|
||||
UDI_FIRST [2]volatile.Register32
|
||||
_ [62]byte
|
||||
RAM_ADDR_RAND volatile.Register16
|
||||
_ [2]byte
|
||||
RAM_DATA_RAND volatile.Register16
|
||||
_ [126]byte
|
||||
CPU_MON_CTRL volatile.Register16
|
||||
_ [2]byte
|
||||
CPU_MON_FIRST volatile.Register32
|
||||
CPU_MON_LAST volatile.Register32
|
||||
_ [60]byte
|
||||
SYSTEM_RESET volatile.Register16
|
||||
_ [66]byte
|
||||
SPI_EN volatile.Register32
|
||||
SPI_XFER volatile.Register32
|
||||
SPI_DATA volatile.Register32
|
||||
}
|
||||
|
||||
const (
|
||||
TK1_MMIO_TIMER_CTRL_START_BIT = 0
|
||||
TK1_MMIO_TIMER_CTRL_STOP_BIT = 1
|
||||
TK1_MMIO_TIMER_CTRL_START = 1 << TK1_MMIO_TIMER_CTRL_START_BIT
|
||||
TK1_MMIO_TIMER_CTRL_STOP = 1 << TK1_MMIO_TIMER_CTRL_STOP_BIT
|
||||
|
||||
TK1_MMIO_TK1_LED_R_BIT = 2
|
||||
TK1_MMIO_TK1_LED_G_BIT = 1
|
||||
TK1_MMIO_TK1_LED_B_BIT = 0
|
||||
|
||||
TK1_MMIO_TK1_GPIO1_BIT = 0
|
||||
TK1_MMIO_TK1_GPIO2_BIT = 1
|
||||
TK1_MMIO_TK1_GPIO3_BIT = 2
|
||||
TK1_MMIO_TK1_GPIO4_BIT = 3
|
||||
)
|
||||
@@ -7,13 +7,15 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// change these to test a different UART or pins if available
|
||||
var (
|
||||
uart = machine.Serial
|
||||
tx = machine.UART_TX_PIN
|
||||
rx = machine.UART_RX_PIN
|
||||
)
|
||||
|
||||
func main() {
|
||||
// use default settings for UART
|
||||
uart.Configure(machine.UARTConfig{})
|
||||
uart.Configure(machine.UARTConfig{TX: tx, RX: rx})
|
||||
uart.Write([]byte("Echo console enabled. Type something then press enter:\r\n"))
|
||||
|
||||
input := make([]byte, 64)
|
||||
|
||||
@@ -13,11 +13,11 @@ func main() {
|
||||
Stereo: true,
|
||||
})
|
||||
|
||||
data := make([]uint16, 64)
|
||||
data := make([]uint32, 64)
|
||||
|
||||
for {
|
||||
// get the next group of samples
|
||||
machine.I2S0.ReadMono(data)
|
||||
machine.I2S0.Read(data)
|
||||
|
||||
println("data", data[0], data[1], data[2], data[4], "...")
|
||||
}
|
||||
|
||||
@@ -30,8 +30,3 @@ func (littleEndian) AppendUint16(b []byte, v uint16) []byte {
|
||||
func (littleEndian) Uint32(b []byte) uint32 {
|
||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||||
}
|
||||
|
||||
func (littleEndian) Uint64(b []byte) uint64 {
|
||||
return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 |
|
||||
uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56
|
||||
}
|
||||
|
||||
+4
-28
@@ -2,11 +2,6 @@ package cm
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// AnyInteger is a type constraint for any integer type.
|
||||
type AnyInteger interface {
|
||||
~int | ~uint | ~uintptr | ~int8 | ~uint8 | ~int16 | ~uint16 | ~int32 | ~uint32 | ~int64 | ~uint64
|
||||
}
|
||||
|
||||
// Reinterpret reinterprets the bits of type From into type T.
|
||||
// Will panic if the size of From is smaller than the size of To.
|
||||
func Reinterpret[T, From any](from From) (to T) {
|
||||
@@ -24,19 +19,19 @@ func LowerString[S ~string](s S) (*byte, uint32) {
|
||||
}
|
||||
|
||||
// LiftString lifts Core WebAssembly types into a [string].
|
||||
func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len AnyInteger](data Data, len Len) T {
|
||||
func LiftString[T ~string, Data unsafe.Pointer | uintptr | *uint8, Len uint | uintptr | uint32 | uint64](data Data, len Len) T {
|
||||
return T(unsafe.String((*uint8)(unsafe.Pointer(data)), int(len)))
|
||||
}
|
||||
|
||||
// LowerList lowers a [List] into a pair of Core WebAssembly types.
|
||||
func LowerList[L AnyList[T], T any](list L) (*T, uint32) {
|
||||
func LowerList[L ~struct{ list[T] }, T any](list L) (*T, uint32) {
|
||||
l := (*List[T])(unsafe.Pointer(&list))
|
||||
return l.data, uint32(l.len)
|
||||
}
|
||||
|
||||
// LiftList lifts Core WebAssembly types into a [List].
|
||||
func LiftList[L AnyList[T], T any, Data unsafe.Pointer | uintptr | *T, Len AnyInteger](data Data, len Len) L {
|
||||
return L(NewList((*T)(unsafe.Pointer(data)), len))
|
||||
func LiftList[L List[T], T any, Data unsafe.Pointer | uintptr | *T, Len uint | uintptr | uint32 | uint64](data Data, len Len) L {
|
||||
return L(NewList((*T)(unsafe.Pointer(data)), uint(len)))
|
||||
}
|
||||
|
||||
// BoolToU32 converts a value whose underlying type is [bool] into a [uint32].
|
||||
@@ -89,25 +84,6 @@ func F64ToU64(v float64) uint64 { return *(*uint64)(unsafe.Pointer(&v)) }
|
||||
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
|
||||
func U64ToF64(v uint64) float64 { return *(*float64)(unsafe.Pointer(&v)) }
|
||||
|
||||
// F32ToU64 maps the bits of a [float32] into a [uint64].
|
||||
// Used to lower a [float32] into a Core WebAssembly i64 when required by the [Canonical ABI].
|
||||
//
|
||||
// [float32]: https://pkg.go.dev/builtin#float32
|
||||
// [uint64]: https://pkg.go.dev/builtin#uint64
|
||||
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
|
||||
func F32ToU64(v float32) uint64 { return uint64(*(*uint32)(unsafe.Pointer(&v))) }
|
||||
|
||||
// U64ToF32 maps the bits of a [uint64] into a [float32].
|
||||
// Used to lift a Core WebAssembly i64 into a [float32] when required by the [Canonical ABI].
|
||||
//
|
||||
// [uint64]: https://pkg.go.dev/builtin#uint64
|
||||
// [float32]: https://pkg.go.dev/builtin#float32
|
||||
// [Canonical ABI]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md
|
||||
func U64ToF32(v uint64) float32 {
|
||||
truncated := uint32(v)
|
||||
return *(*float32)(unsafe.Pointer(&truncated))
|
||||
}
|
||||
|
||||
// PointerToU32 converts a pointer of type *T into a [uint32].
|
||||
// Used to lower a pointer into a Core WebAssembly i32 as specified in the [Canonical ABI].
|
||||
//
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package cm
|
||||
|
||||
// HostLayout marks a struct as using host memory layout.
|
||||
// See [structs.HostLayout] in Go 1.23 or later.
|
||||
type HostLayout struct {
|
||||
_ hostLayout // prevent accidental conversion with plain struct{}
|
||||
}
|
||||
|
||||
type hostLayout struct{}
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build go1.23
|
||||
|
||||
package cm
|
||||
|
||||
import "structs"
|
||||
|
||||
// HostLayout marks a struct as using host memory layout.
|
||||
// See [structs.HostLayout] in Go 1.23 or later.
|
||||
type HostLayout = structs.HostLayout
|
||||
+8
-20
@@ -4,25 +4,14 @@ import "unsafe"
|
||||
|
||||
// List represents a Component Model list.
|
||||
// The binary representation of list<T> is similar to a Go slice minus the cap field.
|
||||
type List[T any] struct {
|
||||
_ HostLayout
|
||||
list[T]
|
||||
}
|
||||
|
||||
// AnyList is a type constraint for generic functions that accept any [List] type.
|
||||
type AnyList[T any] interface {
|
||||
~struct {
|
||||
_ HostLayout
|
||||
list[T]
|
||||
}
|
||||
}
|
||||
type List[T any] struct{ list[T] }
|
||||
|
||||
// NewList returns a List[T] from data and len.
|
||||
func NewList[T any, Len AnyInteger](data *T, len Len) List[T] {
|
||||
func NewList[T any](data *T, len uint) List[T] {
|
||||
return List[T]{
|
||||
list: list[T]{
|
||||
list[T]{
|
||||
data: data,
|
||||
len: uintptr(len),
|
||||
len: len,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -31,16 +20,15 @@ func NewList[T any, Len AnyInteger](data *T, len Len) List[T] {
|
||||
// The underlying slice data is not copied, and the resulting List points at the
|
||||
// same array storage as the slice.
|
||||
func ToList[S ~[]T, T any](s S) List[T] {
|
||||
return NewList[T](unsafe.SliceData([]T(s)), uintptr(len(s)))
|
||||
return NewList[T](unsafe.SliceData([]T(s)), uint(len(s)))
|
||||
}
|
||||
|
||||
// list represents the internal representation of a Component Model list.
|
||||
// It is intended to be embedded in a [List], so embedding types maintain
|
||||
// the methods defined on this type.
|
||||
type list[T any] struct {
|
||||
_ HostLayout
|
||||
data *T
|
||||
len uintptr
|
||||
len uint
|
||||
}
|
||||
|
||||
// Slice returns a Go slice representing the List.
|
||||
@@ -54,7 +42,7 @@ func (l list[T]) Data() *T {
|
||||
}
|
||||
|
||||
// Len returns the length of the list.
|
||||
// TODO: should this return an int instead of a uintptr?
|
||||
func (l list[T]) Len() uintptr {
|
||||
// TODO: should this return an int instead of a uint?
|
||||
func (l list[T]) Len() uint {
|
||||
return l.len
|
||||
}
|
||||
|
||||
@@ -3,10 +3,7 @@ package cm
|
||||
// Option represents a Component Model [option<T>] type.
|
||||
//
|
||||
// [option<T>]: https://component-model.bytecodealliance.org/design/wit.html#options
|
||||
type Option[T any] struct {
|
||||
_ HostLayout
|
||||
option[T]
|
||||
}
|
||||
type Option[T any] struct{ option[T] }
|
||||
|
||||
// None returns an [Option] representing the none case,
|
||||
// equivalent to the zero value.
|
||||
@@ -17,7 +14,7 @@ func None[T any]() Option[T] {
|
||||
// Some returns an [Option] representing the some case.
|
||||
func Some[T any](v T) Option[T] {
|
||||
return Option[T]{
|
||||
option: option[T]{
|
||||
option[T]{
|
||||
isSome: true,
|
||||
some: v,
|
||||
},
|
||||
@@ -28,7 +25,6 @@ func Some[T any](v T) Option[T] {
|
||||
// The first byte is a bool representing none or some,
|
||||
// followed by storage for the associated type T.
|
||||
type option[T any] struct {
|
||||
_ HostLayout
|
||||
isSome bool
|
||||
some T
|
||||
}
|
||||
@@ -46,14 +42,3 @@ func (o *option[T]) Some() *T {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Value returns T if o represents the some case,
|
||||
// or the zero value of T if o represents the none case.
|
||||
// This does not have a pointer receiver, so it can be chained.
|
||||
func (o option[T]) Value() T {
|
||||
if !o.isSome {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
return o.some
|
||||
}
|
||||
|
||||
@@ -17,22 +17,10 @@ type BoolResult bool
|
||||
// Result represents a result sized to hold the Shape type.
|
||||
// The size of the Shape type must be greater than or equal to the size of OK and Err types.
|
||||
// For results with two zero-length types, use [BoolResult].
|
||||
type Result[Shape, OK, Err any] struct {
|
||||
_ HostLayout
|
||||
result[Shape, OK, Err]
|
||||
}
|
||||
|
||||
// AnyResult is a type constraint for generic functions that accept any [Result] type.
|
||||
type AnyResult[Shape, OK, Err any] interface {
|
||||
~struct {
|
||||
_ HostLayout
|
||||
result[Shape, OK, Err]
|
||||
}
|
||||
}
|
||||
type Result[Shape, OK, Err any] struct{ result[Shape, OK, Err] }
|
||||
|
||||
// result represents the internal representation of a Component Model result type.
|
||||
type result[Shape, OK, Err any] struct {
|
||||
_ HostLayout
|
||||
isErr bool
|
||||
_ [0]OK
|
||||
_ [0]Err
|
||||
@@ -71,16 +59,6 @@ func (r *result[Shape, OK, Err]) Err() *Err {
|
||||
return (*Err)(unsafe.Pointer(&r.data))
|
||||
}
|
||||
|
||||
// Result returns (OK, zero value of Err, false) if r represents the OK case,
|
||||
// or (zero value of OK, Err, true) if r represents the error case.
|
||||
// This does not have a pointer receiver, so it can be chained.
|
||||
func (r result[Shape, OK, Err]) Result() (ok OK, err Err, isErr bool) {
|
||||
if r.isErr {
|
||||
return ok, *(*Err)(unsafe.Pointer(&r.data)), true
|
||||
}
|
||||
return *(*OK)(unsafe.Pointer(&r.data)), err, false
|
||||
}
|
||||
|
||||
// This function is sized so it can be inlined and optimized away.
|
||||
func (r *result[Shape, OK, Err]) validate() {
|
||||
var shape Shape
|
||||
@@ -110,8 +88,8 @@ func (r *result[Shape, OK, Err]) validate() {
|
||||
|
||||
// OK returns an OK result with shape Shape and type OK and Err.
|
||||
// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument.
|
||||
func OK[R AnyResult[Shape, OK, Err], Shape, OK, Err any](ok OK) R {
|
||||
var r Result[Shape, OK, Err]
|
||||
func OK[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](ok OK) R {
|
||||
var r struct{ result[Shape, OK, Err] }
|
||||
r.validate()
|
||||
r.isErr = ResultOK
|
||||
*((*OK)(unsafe.Pointer(&r.data))) = ok
|
||||
@@ -120,8 +98,8 @@ func OK[R AnyResult[Shape, OK, Err], Shape, OK, Err any](ok OK) R {
|
||||
|
||||
// Err returns an error result with shape Shape and type OK and Err.
|
||||
// Pass Result[OK, OK, Err] or Result[Err, OK, Err] as the first type argument.
|
||||
func Err[R AnyResult[Shape, OK, Err], Shape, OK, Err any](err Err) R {
|
||||
var r Result[Shape, OK, Err]
|
||||
func Err[R ~struct{ result[Shape, OK, Err] }, Shape, OK, Err any](err Err) R {
|
||||
var r struct{ result[Shape, OK, Err] }
|
||||
r.validate()
|
||||
r.isErr = ResultErr
|
||||
*((*Err)(unsafe.Pointer(&r.data))) = err
|
||||
|
||||
@@ -4,7 +4,6 @@ package cm
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple[T0, T1 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
}
|
||||
@@ -13,7 +12,6 @@ type Tuple[T0, T1 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple3[T0, T1, T2 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -23,7 +21,6 @@ type Tuple3[T0, T1, T2 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple4[T0, T1, T2, T3 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -34,7 +31,6 @@ type Tuple4[T0, T1, T2, T3 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple5[T0, T1, T2, T3, T4 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -46,7 +42,6 @@ type Tuple5[T0, T1, T2, T3, T4 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple6[T0, T1, T2, T3, T4, T5 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -59,7 +54,6 @@ type Tuple6[T0, T1, T2, T3, T4, T5 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -73,7 +67,6 @@ type Tuple7[T0, T1, T2, T3, T4, T5, T6 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -88,7 +81,6 @@ type Tuple8[T0, T1, T2, T3, T4, T5, T6, T7 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -104,7 +96,6 @@ type Tuple9[T0, T1, T2, T3, T4, T5, T6, T7, T8 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -121,7 +112,6 @@ type Tuple10[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -139,7 +129,6 @@ type Tuple11[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -158,7 +147,6 @@ type Tuple12[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -178,7 +166,6 @@ type Tuple13[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct {
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -199,7 +186,6 @@ type Tuple14[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] str
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
@@ -221,7 +207,6 @@ type Tuple15[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any
|
||||
//
|
||||
// [Component Model tuple]: https://component-model.bytecodealliance.org/design/wit.html#tuples
|
||||
type Tuple16[T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct {
|
||||
_ HostLayout
|
||||
F0 T0
|
||||
F1 T1
|
||||
F2 T2
|
||||
|
||||
@@ -12,18 +12,7 @@ type Discriminant interface {
|
||||
// Variant represents a loosely-typed Component Model variant.
|
||||
// Shape and Align must be non-zero sized types. To create a variant with no associated
|
||||
// types, use an enum.
|
||||
type Variant[Tag Discriminant, Shape, Align any] struct {
|
||||
_ HostLayout
|
||||
variant[Tag, Shape, Align]
|
||||
}
|
||||
|
||||
// AnyVariant is a type constraint for generic functions that accept any [Variant] type.
|
||||
type AnyVariant[Tag Discriminant, Shape, Align any] interface {
|
||||
~struct {
|
||||
_ HostLayout
|
||||
variant[Tag, Shape, Align]
|
||||
}
|
||||
}
|
||||
type Variant[Tag Discriminant, Shape, Align any] struct{ variant[Tag, Shape, Align] }
|
||||
|
||||
// NewVariant returns a [Variant] with tag of type Disc, storage and GC shape of type Shape,
|
||||
// aligned to type Align, with a value of type T.
|
||||
@@ -37,7 +26,7 @@ func NewVariant[Tag Discriminant, Shape, Align any, T any](tag Tag, data T) Vari
|
||||
|
||||
// New returns a [Variant] with tag of type Disc, storage and GC shape of type Shape,
|
||||
// aligned to type Align, with a value of type T.
|
||||
func New[V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V {
|
||||
func New[V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any, T any](tag Tag, data T) V {
|
||||
validateVariant[Tag, Shape, Align, T]()
|
||||
var v variant[Tag, Shape, Align]
|
||||
v.tag = tag
|
||||
@@ -46,7 +35,7 @@ func New[V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any, T
|
||||
}
|
||||
|
||||
// Case returns a non-nil *T if the [Variant] case is equal to tag, otherwise it returns nil.
|
||||
func Case[T any, V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align any](v *V, tag Tag) *T {
|
||||
func Case[T any, V ~struct{ variant[Tag, Shape, Align] }, Tag Discriminant, Shape, Align any](v *V, tag Tag) *T {
|
||||
validateVariant[Tag, Shape, Align, T]()
|
||||
v2 := (*variant[Tag, Shape, Align])(unsafe.Pointer(v))
|
||||
if v2.tag == tag {
|
||||
@@ -58,7 +47,6 @@ func Case[T any, V AnyVariant[Tag, Shape, Align], Tag Discriminant, Shape, Align
|
||||
// variant is the internal representation of a Component Model variant.
|
||||
// Shape and Align must be non-zero sized types.
|
||||
type variant[Tag Discriminant, Shape, Align any] struct {
|
||||
_ HostLayout
|
||||
tag Tag
|
||||
_ [0]Align
|
||||
data Shape // [unsafe.Sizeof(*(*Shape)(unsafe.Pointer(nil)))]byte
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
package futex
|
||||
|
||||
// Cross platform futex implementation.
|
||||
// Futexes are supported on all major operating systems and on WebAssembly.
|
||||
//
|
||||
// For more information, see: https://outerproduct.net/futex-dictionary.html
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// A futex is a way for userspace to wait with the pointer as the key, and for
|
||||
// another thread to wake one or all waiting threads keyed on the same pointer.
|
||||
//
|
||||
// A futex does not change the underlying value, it only reads it before going
|
||||
// to sleep (atomically) to prevent lost wake-ups.
|
||||
type Futex struct {
|
||||
atomic.Uint32
|
||||
}
|
||||
|
||||
// Atomically check for cmp to still be equal to the futex value and if so, go
|
||||
// to sleep. Return true if we were definitely awoken by a call to Wake or
|
||||
// WakeAll, and false if we can't be sure of that.
|
||||
func (f *Futex) Wait(cmp uint32) bool {
|
||||
tinygo_futex_wait((*uint32)(unsafe.Pointer(&f.Uint32)), cmp)
|
||||
|
||||
// We *could* detect a zero return value from the futex system call which
|
||||
// would indicate we got awoken by a Wake or WakeAll call. However, this is
|
||||
// what the manual page has to say:
|
||||
//
|
||||
// > Note that a wake-up can also be caused by common futex usage patterns
|
||||
// > in unrelated code that happened to have previously used the futex
|
||||
// > word's memory location (e.g., typical futex-based implementations of
|
||||
// > Pthreads mutexes can cause this under some conditions). Therefore,
|
||||
// > callers should always conservatively assume that a return value of 0
|
||||
// > can mean a spurious wake-up, and use the futex word's value (i.e., the
|
||||
// > user-space synchronization scheme) to decide whether to continue to
|
||||
// > block or not.
|
||||
//
|
||||
// I'm not sure whether we do anything like pthread does, so to be on the
|
||||
// safe side we say we don't know whether the wakeup was spurious or not and
|
||||
// return false.
|
||||
return false
|
||||
}
|
||||
|
||||
// Like Wait, but times out after the number of nanoseconds in timeout.
|
||||
func (f *Futex) WaitUntil(cmp uint32, timeout uint64) {
|
||||
tinygo_futex_wait_timeout((*uint32)(unsafe.Pointer(&f.Uint32)), cmp, timeout)
|
||||
}
|
||||
|
||||
// Wake a single waiter.
|
||||
func (f *Futex) Wake() {
|
||||
tinygo_futex_wake((*uint32)(unsafe.Pointer(&f.Uint32)))
|
||||
}
|
||||
|
||||
// Wake all waiters.
|
||||
func (f *Futex) WakeAll() {
|
||||
tinygo_futex_wake_all((*uint32)(unsafe.Pointer(&f.Uint32)))
|
||||
}
|
||||
|
||||
//export tinygo_futex_wait
|
||||
func tinygo_futex_wait(addr *uint32, cmp uint32)
|
||||
|
||||
//export tinygo_futex_wait_timeout
|
||||
func tinygo_futex_wait_timeout(addr *uint32, cmp uint32, timeout uint64)
|
||||
|
||||
//export tinygo_futex_wake
|
||||
func tinygo_futex_wake(addr *uint32)
|
||||
|
||||
//export tinygo_futex_wake_all
|
||||
func tinygo_futex_wake_all(addr *uint32)
|
||||
@@ -1,49 +0,0 @@
|
||||
//go:build none
|
||||
|
||||
// This file is manually included, to avoid CGo which would cause a circular
|
||||
// import.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// This API isn't documented by Apple, but it is used by LLVM libc++ (so should
|
||||
// be stable) and has been documented extensively here:
|
||||
// https://outerproduct.net/futex-dictionary.html
|
||||
|
||||
int __ulock_wait(uint32_t operation, void *addr, uint64_t value, uint32_t timeout_us);
|
||||
int __ulock_wait2(uint32_t operation, void *addr, uint64_t value, uint64_t timeout_ns, uint64_t value2);
|
||||
int __ulock_wake(uint32_t operation, void *addr, uint64_t wake_value);
|
||||
|
||||
// Operation code.
|
||||
#define UL_COMPARE_AND_WAIT 1
|
||||
|
||||
// Flags to the operation value.
|
||||
#define ULF_WAKE_ALL 0x00000100
|
||||
#define ULF_NO_ERRNO 0x01000000
|
||||
|
||||
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
|
||||
__ulock_wait(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
|
||||
// Make sure that an accidental use of a zero timeout is not treated as an
|
||||
// infinite timeout. Return if it's zero since it wouldn't be waiting for
|
||||
// any significant time anyway.
|
||||
// Probably unnecessary, but guards against potential bugs.
|
||||
if (timeout == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: __ulock_wait2 is available since MacOS 11.
|
||||
// I think that's fine, since the version before that (MacOS 10.15) is EOL
|
||||
// since 2022. Though if needed, we could certainly use __ulock_wait instead
|
||||
// and deal with the smaller timeout value.
|
||||
__ulock_wait2(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, (uint64_t)cmp, timeout, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wake(uint32_t *addr) {
|
||||
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO, addr, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wake_all(uint32_t *addr) {
|
||||
__ulock_wake(UL_COMPARE_AND_WAIT|ULF_NO_ERRNO|ULF_WAKE_ALL, addr, 0);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//go:build none
|
||||
|
||||
// This file is manually included, to avoid CGo which would cause a circular
|
||||
// import.
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define FUTEX_WAIT 0
|
||||
#define FUTEX_WAKE 1
|
||||
#define FUTEX_PRIVATE_FLAG 128
|
||||
|
||||
void tinygo_futex_wait(uint32_t *addr, uint32_t cmp) {
|
||||
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wait_timeout(uint32_t *addr, uint32_t cmp, uint64_t timeout) {
|
||||
struct timespec ts = {0};
|
||||
ts.tv_sec = timeout / 1000000000;
|
||||
ts.tv_nsec = timeout % 1000000000;
|
||||
syscall(SYS_futex, addr, FUTEX_WAIT|FUTEX_PRIVATE_FLAG, cmp, &ts, NULL, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wake(uint32_t *addr) {
|
||||
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, 1, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
void tinygo_futex_wake_all(uint32_t *addr) {
|
||||
syscall(SYS_futex, addr, FUTEX_WAKE|FUTEX_PRIVATE_FLAG, INT_MAX, NULL, NULL, 0);
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package gclayout
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Internal constants for gc layout
|
||||
// See runtime/gc_precise.go
|
||||
|
||||
var (
|
||||
NoPtrs unsafe.Pointer
|
||||
Pointer unsafe.Pointer
|
||||
String unsafe.Pointer
|
||||
Slice unsafe.Pointer
|
||||
)
|
||||
|
||||
func init() {
|
||||
var sizeBits uintptr
|
||||
|
||||
switch unsafe.Sizeof(uintptr(0)) {
|
||||
case 8:
|
||||
sizeBits = 6
|
||||
case 4:
|
||||
sizeBits = 5
|
||||
case 2:
|
||||
sizeBits = 4
|
||||
}
|
||||
|
||||
var sizeShift = sizeBits + 1
|
||||
|
||||
NoPtrs = unsafe.Pointer(uintptr(0b0<<sizeShift) | uintptr(0b1<<1) | uintptr(1))
|
||||
Pointer = unsafe.Pointer(uintptr(0b1<<sizeShift) | uintptr(0b1<<1) | uintptr(1))
|
||||
String = unsafe.Pointer(uintptr(0b01<<sizeShift) | uintptr(0b10<<1) | uintptr(1))
|
||||
Slice = unsafe.Pointer(uintptr(0b001<<sizeShift) | uintptr(0b11<<1) | uintptr(1))
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package task
|
||||
|
||||
// Atomics implementation for cooperative systems. The atomic types here aren't
|
||||
// actually atomic, they assume that accesses cannot be interrupted by a
|
||||
// different goroutine or interrupt happening at the same time.
|
||||
|
||||
type atomicIntegerType interface {
|
||||
uintptr | uint32 | uint64
|
||||
}
|
||||
|
||||
type pseudoAtomic[T atomicIntegerType] struct {
|
||||
v T
|
||||
}
|
||||
|
||||
func (x *pseudoAtomic[T]) Add(delta T) T { x.v += delta; return x.v }
|
||||
func (x *pseudoAtomic[T]) Load() T { return x.v }
|
||||
func (x *pseudoAtomic[T]) Store(val T) { x.v = val }
|
||||
func (x *pseudoAtomic[T]) CompareAndSwap(old, new T) (swapped bool) {
|
||||
if x.v != old {
|
||||
return false
|
||||
}
|
||||
x.v = new
|
||||
return true
|
||||
}
|
||||
func (x *pseudoAtomic[T]) Swap(new T) (old T) {
|
||||
old = x.v
|
||||
x.v = new
|
||||
return
|
||||
}
|
||||
|
||||
// Uintptr is an atomic uintptr when multithreading is enabled, and a plain old
|
||||
// uintptr otherwise.
|
||||
type Uintptr = pseudoAtomic[uintptr]
|
||||
|
||||
// Uint32 is an atomic uint32 when multithreading is enabled, and a plain old
|
||||
// uint32 otherwise.
|
||||
type Uint32 = pseudoAtomic[uint32]
|
||||
|
||||
// Uint64 is an atomic uint64 when multithreading is enabled, and a plain old
|
||||
// uint64 otherwise.
|
||||
type Uint64 = pseudoAtomic[uint64]
|
||||
@@ -1,44 +0,0 @@
|
||||
package task
|
||||
|
||||
// A futex is a way for userspace to wait with the pointer as the key, and for
|
||||
// another thread to wake one or all waiting threads keyed on the same pointer.
|
||||
//
|
||||
// A futex does not change the underlying value, it only reads it before to prevent
|
||||
// lost wake-ups.
|
||||
type Futex struct {
|
||||
Uint32
|
||||
waiters Stack
|
||||
}
|
||||
|
||||
// Atomically check for cmp to still be equal to the futex value and if so, go
|
||||
// to sleep. Return true if we were definitely awoken by a call to Wake or
|
||||
// WakeAll, and false if we can't be sure of that.
|
||||
func (f *Futex) Wait(cmp uint32) (awoken bool) {
|
||||
if f.Uint32.v != cmp {
|
||||
return false
|
||||
}
|
||||
|
||||
// Push the current goroutine onto the waiter stack.
|
||||
f.waiters.Push(Current())
|
||||
|
||||
// Pause until the waiters are awoken by Wake/WakeAll.
|
||||
Pause()
|
||||
|
||||
// We were awoken by a call to Wake or WakeAll. There is no chance for
|
||||
// spurious wakeups.
|
||||
return true
|
||||
}
|
||||
|
||||
// Wake a single waiter.
|
||||
func (f *Futex) Wake() {
|
||||
if t := f.waiters.Pop(); t != nil {
|
||||
scheduleTask(t)
|
||||
}
|
||||
}
|
||||
|
||||
// Wake all waiters.
|
||||
func (f *Futex) WakeAll() {
|
||||
for t := f.waiters.Pop(); t != nil; t = f.waiters.Pop() {
|
||||
scheduleTask(t)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package task
|
||||
|
||||
// PMutex is a real mutex on systems that can be either preemptive or threaded,
|
||||
// and a dummy lock on other (purely cooperative) systems.
|
||||
//
|
||||
// It is mainly useful for short operations that need a lock when threading may
|
||||
// be involved, but which do not need a lock with a purely cooperative
|
||||
// scheduler.
|
||||
type PMutex struct {
|
||||
}
|
||||
|
||||
func (m *PMutex) Lock() {
|
||||
}
|
||||
|
||||
func (m *PMutex) Unlock() {
|
||||
}
|
||||
@@ -26,30 +26,7 @@ type Task struct {
|
||||
DeferFrame unsafe.Pointer
|
||||
}
|
||||
|
||||
// DataUint32 returns the Data field as a uint32. The value is only valid after
|
||||
// setting it through SetDataUint32 or by storing to it using DataAtomicUint32.
|
||||
func (t *Task) DataUint32() uint32 {
|
||||
return *(*uint32)(unsafe.Pointer(&t.Data))
|
||||
}
|
||||
|
||||
// SetDataUint32 updates the uint32 portion of the Data field (which could be
|
||||
// the first 4 or last 4 bytes depending on the architecture endianness).
|
||||
func (t *Task) SetDataUint32(val uint32) {
|
||||
*(*uint32)(unsafe.Pointer(&t.Data)) = val
|
||||
}
|
||||
|
||||
// DataAtomicUint32 returns the Data field as an atomic-if-needed Uint32 value.
|
||||
func (t *Task) DataAtomicUint32() *Uint32 {
|
||||
return (*Uint32)(unsafe.Pointer(&t.Data))
|
||||
}
|
||||
|
||||
// getGoroutineStackSize is a compiler intrinsic that returns the stack size for
|
||||
// the given function and falls back to the default stack size. It is replaced
|
||||
// with a load from a special section just before codegen.
|
||||
func getGoroutineStackSize(fn uintptr) uintptr
|
||||
|
||||
//go:linkname runtime_alloc runtime.alloc
|
||||
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
||||
|
||||
//go:linkname scheduleTask runtime.scheduleTask
|
||||
func scheduleTask(*Task)
|
||||
|
||||
@@ -35,16 +35,11 @@ type state struct {
|
||||
type stackState struct {
|
||||
// asyncify is the stack pointer of the asyncify stack.
|
||||
// This starts from the bottom and grows upwards.
|
||||
asyncifysp unsafe.Pointer
|
||||
asyncifysp uintptr
|
||||
|
||||
// asyncify is stack pointer of the C stack.
|
||||
// This starts from the top and grows downwards.
|
||||
csp unsafe.Pointer
|
||||
|
||||
// Pointer to the first (lowest address) of the stack. It must never be
|
||||
// overwritten. It can be checked from time to time to see whether a stack
|
||||
// overflow happened in the past.
|
||||
canaryPtr *uintptr
|
||||
csp uintptr
|
||||
}
|
||||
|
||||
// start creates and starts a new goroutine with the given function and arguments.
|
||||
@@ -52,7 +47,7 @@ type stackState struct {
|
||||
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
t := &Task{}
|
||||
t.state.initialize(fn, args, stackSize)
|
||||
scheduleTask(t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
//export tinygo_launch
|
||||
@@ -68,20 +63,17 @@ func (s *state) initialize(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
s.args = args
|
||||
|
||||
// Create a stack.
|
||||
stack := runtime_alloc(stackSize, nil)
|
||||
|
||||
// Set up the stack canary, a random number that should be checked when
|
||||
// switching from the task back to the scheduler. The stack canary pointer
|
||||
// points to the first word of the stack. If it has changed between now and
|
||||
// the next stack switch, there was a stack overflow.
|
||||
s.canaryPtr = (*uintptr)(stack)
|
||||
*s.canaryPtr = stackCanary
|
||||
stack := make([]uintptr, stackSize/unsafe.Sizeof(uintptr(0)))
|
||||
|
||||
// Calculate stack base addresses.
|
||||
s.asyncifysp = unsafe.Add(stack, unsafe.Sizeof(uintptr(0)))
|
||||
s.csp = unsafe.Add(stack, stackSize)
|
||||
s.asyncifysp = uintptr(unsafe.Pointer(&stack[0]))
|
||||
s.csp = uintptr(unsafe.Pointer(&stack[0])) + uintptr(len(stack))*unsafe.Sizeof(uintptr(0))
|
||||
stack[0] = stackCanary
|
||||
}
|
||||
|
||||
//go:linkname runqueuePushBack runtime.runqueuePushBack
|
||||
func runqueuePushBack(*Task)
|
||||
|
||||
// currentTask is the current running task, or nil if currently in the scheduler.
|
||||
var currentTask *Task
|
||||
|
||||
@@ -93,11 +85,14 @@ func Current() *Task {
|
||||
// Pause suspends the current task and returns to the scheduler.
|
||||
// This function may only be called when running on a goroutine stack, not when running on the system stack.
|
||||
func Pause() {
|
||||
if *currentTask.state.canaryPtr != stackCanary {
|
||||
// This is mildly unsafe but this is also the only place we can do this.
|
||||
if *(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) != stackCanary {
|
||||
runtimePanic("stack overflow")
|
||||
}
|
||||
|
||||
currentTask.state.unwind()
|
||||
|
||||
*(*uintptr)(unsafe.Pointer(currentTask.state.asyncifysp)) = stackCanary
|
||||
}
|
||||
|
||||
//export tinygo_unwind
|
||||
@@ -118,7 +113,7 @@ func (t *Task) Resume() {
|
||||
}
|
||||
currentTask = prevTask
|
||||
t.gcData.swap()
|
||||
if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
|
||||
if t.state.asyncifysp > t.state.csp {
|
||||
runtimePanic("stack overflow")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,12 +101,18 @@ func swapTask(oldStack uintptr, newStack *uintptr)
|
||||
//go:extern tinygo_startTask
|
||||
var startTask [0]uint8
|
||||
|
||||
//go:linkname runqueuePushBack runtime.runqueuePushBack
|
||||
func runqueuePushBack(*Task)
|
||||
|
||||
//go:linkname runtime_alloc runtime.alloc
|
||||
func runtime_alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
||||
|
||||
// start creates and starts a new goroutine with the given function and arguments.
|
||||
// The new goroutine is scheduled to run later.
|
||||
func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
t := &Task{}
|
||||
t.state.initialize(fn, args, stackSize)
|
||||
scheduleTask(t)
|
||||
runqueuePushBack(t)
|
||||
}
|
||||
|
||||
// OnSystemStack returns whether the caller is running on the system stack.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Do not reorder instructions to insert a branch delay slot.
|
||||
// We know what we're doing, and will manually fill the branch delay slot.
|
||||
.set noreorder
|
||||
|
||||
.section .text.tinygo_startTask
|
||||
.global tinygo_startTask
|
||||
.type tinygo_startTask, %function
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +0,0 @@
|
||||
// Code generated by wit-bindgen-go. DO NOT EDIT.
|
||||
|
||||
package environment
|
||||
|
||||
import (
|
||||
"internal/cm"
|
||||
)
|
||||
|
||||
// This file contains wasmimport and wasmexport declarations for "wasi:cli@0.2.0".
|
||||
|
||||
//go:wasmimport wasi:cli/environment@0.2.0 get-environment
|
||||
//go:noescape
|
||||
func wasmimport_GetEnvironment(result *cm.List[[2]string])
|
||||
|
||||
//go:wasmimport wasi:cli/environment@0.2.0 get-arguments
|
||||
//go:noescape
|
||||
func wasmimport_GetArguments(result *cm.List[string])
|
||||
|
||||
//go:wasmimport wasi:cli/environment@0.2.0 initial-cwd
|
||||
//go:noescape
|
||||
func wasmimport_InitialCWD(result *cm.Option[string])
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user