Compare commits

..

2 Commits

Author SHA1 Message Date
Ayke van Laethem 88db2c3594 usb/msc: wait for interrupt instead of polling a flag
This should make usb/msc a whole lot more efficient by pausing the
worker goroutine and waiting for an interrupt to unpause it instead of
waiting in a loop and sleeping for 0.1ms each cycle.

In other words, this should make it both faster (no unnecessary delay
due to the time.Sleep) and more efficient (no polling).
2026-05-25 17:08:01 +02:00
Ayke van Laethem fb2755d18f internal/task: add Waiter for waiting for a flag from interrupts
This provides an abstraction to allow goroutines to wait for an event
from an interrupt, and for interrupts to send such an event and know
whether the goroutine is still working on the previous event.
2026-05-25 17:06:23 +02:00
313 changed files with 2309 additions and 11706 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -131,7 +131,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }}) - name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }} run: go install -tags=llvm${{ matrix.version }}
-72
View File
@@ -1,72 +0,0 @@
# This CI job checks whether at least the smoke tests pass for the oldest
# Go/LLVM version we claim to support.
name: Version compatibility test
on:
pull_request:
push:
branches:
- dev
- release
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test-compat:
runs-on: ubuntu-22.04 # this must be a specific version for the apt install below
env:
# Oldest versions currently supported by TinyGo
LLVM: "15"
Go: "1.24" # when updating this, also update minorMin in builder/config.go
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: ${{ env.Go }}
cache: true
- name: Install LLVM
run: |
echo 'deb https://apt.llvm.org/jammy/ llvm-toolchain-jammy-${{ env.LLVM }} main' | sudo tee /etc/apt/sources.list.d/llvm.list
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install --no-install-recommends -y \
llvm-${{ env.LLVM }}-dev \
clang-${{ env.LLVM }} \
libclang-${{ env.LLVM }}-dev \
lld-${{ env.LLVM }} \
binaryen
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-20-linux-compat
path: llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
run: make llvm-source
- name: Save LLVM source cache
uses: actions/cache/save@v5
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
with:
key: ${{ steps.cache-llvm-source.outputs.cache-primary-key }}
path: llvm-project/compiler-rt
- name: Go cache
uses: actions/cache@v5
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: go-build-${{ env.Go }}-llvm${{ env.LLVM }}-${{ hashFiles('go.sum') }}
- name: Build TinyGo
run: go install -tags=llvm${{ env.LLVM }}
- run: tinygo version
- run: make gen-device -j4
- run: go test -tags=llvm${{ env.LLVM }} -short -skip=TestErrors
- run: make smoketest XTENSA=0
+4 -24
View File
@@ -12,24 +12,6 @@ concurrency:
cancel-in-progress: true cancel-in-progress: true
jobs: jobs:
go-mod-tidy:
# Check that go.sum is up to date.
runs-on: ubuntu-slim
steps:
- name: Checkout
uses: actions/checkout@v6
with:
submodules: true
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.26.4'
cache: true
- name: Run go mod tidy
run: go mod tidy
- name: Check go.mod and go.sum are up to date
run: git diff --exit-code
build-linux: build-linux:
# Build Linux binaries, ready for release. # Build Linux binaries, ready for release.
# This runs inside an Alpine Linux container so we can more easily create a # This runs inside an Alpine Linux container so we can more easily create a
@@ -160,7 +142,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -204,12 +186,12 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: '22' node-version: '18'
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 uses: bytecodealliance/actions/wasmtime/setup@v1
with: with:
@@ -284,8 +266,6 @@ jobs:
- run: make smoketest - run: make smoketest
- run: make wasmtest - run: make wasmtest
- run: make tinygo-test-baremetal - run: make tinygo-test-baremetal
- name: Check Go code formatting
run: make fmt-check lint
build-linux-cross: build-linux-cross:
# Build ARM Linux binaries, ready for release. # Build ARM Linux binaries, ready for release.
# This intentionally uses an older Linux image, so that we compile against # This intentionally uses an older Linux image, so that we compile against
@@ -323,7 +303,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
+31 -7
View File
@@ -17,6 +17,12 @@ jobs:
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
steps: steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4 - uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
@@ -34,13 +40,13 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Restore cached LLVM source - name: Restore cached LLVM source
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
with: with:
key: llvm-source-20-windows-v3 key: llvm-source-20-windows-v1
path: | path: |
llvm-project/clang/lib/Headers llvm-project/clang/lib/Headers
llvm-project/clang/include llvm-project/clang/include
@@ -65,7 +71,7 @@ jobs:
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-build id: cache-llvm-build
with: with:
key: llvm-build-20-windows-v5 key: llvm-build-20-windows-v2
path: llvm-build path: llvm-build
- name: Build LLVM - name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true' if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -87,7 +93,7 @@ jobs:
- name: Cache Go cache - name: Cache Go cache
uses: actions/cache@v5 uses: actions/cache@v5
with: with:
key: go-cache-windows-v3-${{ hashFiles('go.mod') }} key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
path: | path: |
C:/Users/runneradmin/AppData/Local/go-build C:/Users/runneradmin/AppData/Local/go-build
C:/Users/runneradmin/go/pkg/mod C:/Users/runneradmin/go/pkg/mod
@@ -118,6 +124,12 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
needs: build-windows needs: build-windows
steps: steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4 - uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
@@ -129,7 +141,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -145,12 +157,18 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
needs: build-windows needs: build-windows
steps: steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -165,6 +183,12 @@ jobs:
runs-on: windows-2022 runs-on: windows-2022
needs: build-windows needs: build-windows
steps: steps:
- name: Configure pagefile
uses: al-cheb/configure-pagefile-action@v1.4
with:
minimum-size: 8GB
maximum-size: 24GB
disk-root: "C:"
- uses: MinoruSekine/setup-scoop@v4 - uses: MinoruSekine/setup-scoop@v4
- name: Install Dependencies - name: Install Dependencies
shell: bash shell: bash
@@ -176,7 +200,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.26.4' go-version: '1.26.2'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
+13 -46
View File
@@ -142,9 +142,6 @@ ifeq ($(OS),Windows_NT)
# PIC needs to be disabled for libclang to work. # PIC needs to be disabled for libclang to work.
LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF LLVM_OPTION += -DLLVM_ENABLE_PIC=OFF
# Statically link the C++ and GCC runtime into LLVM tools so they don't
# depend on MinGW DLLs that may not be on PATH when executed during the build.
LLVM_OPTION += '-DCMAKE_EXE_LINKER_FLAGS=-static-libgcc -static-libstdc++'
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++ CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
@@ -197,12 +194,6 @@ NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","") ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include CGO_CPPFLAGS+=$(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
CGO_CXXFLAGS=-std=c++17 CGO_CXXFLAGS=-std=c++17
ifneq ($(uname),Windows_NT)
# Disable GCC DWARF compression: lld built without zlib cannot link
# object files with ELFCOMPRESS_ZLIB debug sections.
CGO_CFLAGS+=-gz=none
CGO_CXXFLAGS+=-gz=none
endif
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA) CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_CONFIG_PREFIX) $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
endif endif
@@ -307,7 +298,7 @@ wasi-cm:
rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm rsync -rv --delete --exclude go.mod --exclude '*_test.go' --exclude '*_json.go' --exclude '*.md' --exclude LICENSE $(shell go list -m -f {{.Dir}} $(WASM_TOOLS_MODULE)/cm)/ ./src/internal/cm
# Check for Node.js used during WASM tests. # Check for Node.js used during WASM tests.
MIN_NODEJS_VERSION=22 MIN_NODEJS_VERSION=18
.PHONY: check-nodejs-version .PHONY: check-nodejs-version
check-nodejs-version: check-nodejs-version:
@@ -319,7 +310,7 @@ check-nodejs-version:
tinygo: ## Build the TinyGo compiler 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 @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_CFLAGS="$(CGO_CFLAGS)" 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" .
test: check-nodejs-version test: 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=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
@@ -388,22 +379,22 @@ TEST_PACKAGES_FAST = \
# archive/zip requires os.ReadAt, which is not yet supported on windows # archive/zip requires os.ReadAt, which is not yet supported on windows
# bytes requires mmap # bytes requires mmap
# compress/flate appears to hang on wasi # compress/flate appears to hang on wasi
# crypto/aes needs reflect.Type.Method(), not yet implemented # crypto/aes fails on wasi, needs panic()/recover()
# crypto/des fails on wasi, needs panic()/recover() # crypto/des fails on wasi, needs panic()/recover()
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic # crypto/hmac fails on wasi, it exits with a "slice out of range" panic
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows # debug/plan9obj requires os.ReadAt, which is not yet supported on windows
# encoding/xml takes a minute on linux and gives a stack overflow on wasi # encoding/xml takes a minute on linux and gives a stack overflow on wasi
# image fails on wasi, needs panic()/recover() # image requires recover(), which is not yet supported on wasi
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi # io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
# mime: fails on wasi, needs panic()/recover() # mime: fail on wasi; neds panic()/recover()
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK # mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
# mime/quotedprintable requires syscall.Faccessat # mime/quotedprintable requires syscall.Faccessat
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK # net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK # net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
# regexp/syntax: fails on wasip1, needs panic()/recover() # regexp/syntax: fails on wasip1; needs panic()/recover()
# strconv: fails on wasi, needs panic()/recover() # strconv requires recover() which is not yet supported on wasi
# text/tabwriter: fails on wasi, needs panic()/recover() # text/tabwriter requires recover(), which is not yet supported on wasi
# text/template/parse: fails on wasi, needs panic()/recover() # text/template/parse requires recover(), which is not yet supported on wasi
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi # testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
# Additional standard library packages that pass tests on individual platforms # Additional standard library packages that pass tests on individual platforms
@@ -413,7 +404,6 @@ TEST_PACKAGES_LINUX := \
context \ context \
crypto/aes \ crypto/aes \
crypto/des \ crypto/des \
crypto/ecdh \
crypto/hmac \ crypto/hmac \
debug/dwarf \ debug/dwarf \
debug/plan9obj \ debug/plan9obj \
@@ -429,7 +419,6 @@ TEST_PACKAGES_LINUX := \
os/user \ os/user \
regexp/syntax \ regexp/syntax \
strconv \ strconv \
testing/fstest \
text/tabwriter \ text/tabwriter \
text/template/parse text/template/parse
@@ -440,11 +429,7 @@ TEST_PACKAGES_WINDOWS := \
compress/flate \ compress/flate \
crypto/des \ crypto/des \
crypto/hmac \ crypto/hmac \
image \
mime \
regexp/syntax \
strconv \ strconv \
text/tabwriter \
text/template/parse \ text/template/parse \
$(nil) $(nil)
@@ -493,12 +478,10 @@ report-stdlib-tests-pass:
ifeq ($(uname),Darwin) ifeq ($(uname),Darwin)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
TEST_IOFS := true TEST_IOFS := true
TEST_ENCODING_XML := true
endif endif
ifeq ($(uname),Linux) ifeq ($(uname),Linux)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
TEST_IOFS := true TEST_IOFS := true
TEST_ENCODING_XML := true
endif endif
ifeq ($(OS),Windows_NT) ifeq ($(OS),Windows_NT)
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS) TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
@@ -513,11 +496,8 @@ TEST_ADDITIONAL_FLAGS ?=
.PHONY: tinygo-test .PHONY: tinygo-test
tinygo-test: tinygo-test:
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented. @# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm. @# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW) $(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
endif
@# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also @# io/fs requires os.ReadDir, not yet supported on windows or wasi. It also
@# requires a large stack-size. Hence, io/fs is only run conditionally. @# requires a large stack-size. Hence, io/fs is only run conditionally.
@# For more details, see the comments on issue #3143. @# For more details, see the comments on issue #3143.
@@ -762,8 +742,6 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1 $(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=xiao-ble-plus examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac $(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
@@ -800,8 +778,6 @@ endif
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1 $(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico -gc=leaking examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1 $(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
@@ -911,8 +887,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial $(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=stm32u031 examples/empty
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinky1 $(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/blinky1
@$(MD5SUM) test.hex @$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial $(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial
@@ -973,11 +947,6 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest $(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
# xiao-esp32c6
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinky1
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=xiao-esp32c6 examples/blinkm
@$(MD5SUM) test.bin
# xiao-esp32s3 # xiao-esp32s3
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1 $(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
@@ -998,8 +967,6 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc $(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc
@$(MD5SUM) test.bin @$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
@$(MD5SUM) test.bin
endif endif
# esp32c3-supermini # esp32c3-supermini
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1 $(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
@@ -1196,8 +1163,8 @@ endif
@cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src @cp -rp lib/wasi-libc/libc-top-half/musl/src/unistd build/release/tinygo/lib/wasi-libc/libc-top-half/musl/src
@cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half @cp -rp lib/wasi-libc/libc-top-half/sources build/release/tinygo/lib/wasi-libc/libc-top-half
@cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit @cp -rp lib/wasi-cli/wit build/release/tinygo/lib/wasi-cli/wit
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
@cp -rp ${LLVM_PROJECTDIR}/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins @cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
@cp -rp src build/release/tinygo/src @cp -rp src build/release/tinygo/src
@cp -rp targets build/release/tinygo/targets @cp -rp targets build/release/tinygo/targets
+11 -25
View File
@@ -14,7 +14,6 @@ import (
"fmt" "fmt"
"go/types" "go/types"
"hash/crc32" "hash/crc32"
"maps"
"math/bits" "math/bits"
"os" "os"
"os/exec" "os/exec"
@@ -138,7 +137,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if _, ok := globalValues[pkgPath]; !ok { if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{} globalValues[pkgPath] = map[string]string{}
} }
maps.Copy(globalValues[pkgPath], vals) for k, v := range vals {
globalValues[pkgPath][k] = v
}
} }
// Check for a libc dependency. // Check for a libc dependency.
@@ -277,6 +278,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
var embedFileObjects []*compileJob var embedFileObjects []*compileJob
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg // necessary to avoid a race condition
var undefinedGlobals []string var undefinedGlobals []string
for name := range globalValues[pkg.Pkg.Path()] { for name := range globalValues[pkg.Pkg.Path()] {
@@ -773,6 +775,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// TODO: do this as part of building the package to be able to link the // TODO: do this as part of building the package to be able to link the
// bitcode files together. // bitcode files together.
for _, pkg := range lprogram.Sorted() { for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles { for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.OriginalDir(), filename) abspath := filepath.Join(pkg.OriginalDir(), filename)
job := &compileJob{ job := &compileJob{
@@ -839,25 +842,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
} }
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU()) ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
switch config.LinkerFlavor() { if config.GOOS() == "windows" {
case "coff": // Options for the MinGW wrapper for the lld COFF linker.
// Options for driving ld.lld in PE/COFF mode.
ldflags = append(ldflags, ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel), "-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto")) "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
case "darwin": } else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker. // Options for the ld64-compatible lld linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel), "--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto")) "-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
case "gnu": } else {
// Options for the ELF linker. // Options for the ELF linker.
ldflags = append(ldflags, ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel), "--lto-O"+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"), "--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
) )
default:
return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor())
} }
if config.CodeModel() != "default" { if config.CodeModel() != "default" {
ldflags = append(ldflags, ldflags = append(ldflags,
@@ -914,7 +914,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
} }
// Run wasm-opt for wasm binaries // Run wasm-opt for wasm binaries
if arch, _, _ := strings.Cut(config.Triple(), "-"); arch == "wasm32" { if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
optLevel, _, _ := config.OptLevel() optLevel, _, _ := config.OptLevel()
opt := "-" + optLevel opt := "-" + optLevel
@@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if err != nil { if err != nil {
return result, err return result, err
} }
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266": case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
// Special format for the ESP family of chips (parsed by the ROM // Special format for the ESP family of chips (parsed by the ROM
// bootloader). // bootloader).
result.Binary = filepath.Join(tmpdir, "main"+outext) result.Binary = filepath.Join(tmpdir, "main"+outext)
@@ -1352,14 +1352,6 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
} }
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize() baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
// Account for the bytes that tinygo_swapTask pushes onto the goroutine stack
// on every context switch. The static analysis correctly traces Go calls,
// but it cannot see into the assembly-level register push.
var contextSwitchOverhead uint64
if swapFuncs, ok := functions["tinygo_swapTask"]; ok && len(swapFuncs) == 1 {
contextSwitchOverhead = swapFuncs[0].FrameSize
}
sizes := make(map[string]functionStackSize) sizes := make(map[string]functionStackSize)
// Add the reset handler function, for convenience. The reset handler runs // Add the reset handler function, for convenience. The reset handler runs
@@ -1408,12 +1400,6 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
// overflow will occur even before the goroutine is started. // overflow will occur even before the goroutine is started.
stackSize = baseStackSize stackSize = baseStackSize
} }
if stackSizeType == stacksize.Bounded {
// Add the overhead of context switching. This is needed because the
// context switch (tinygo_swapTask) pushes callee-saved registers
// onto the current stack, which is not seen by the static analysis.
stackSize += contextSwitchOverhead
}
sizes[name] = functionStackSize{ sizes[name] = functionStackSize{
stackSize: stackSize, stackSize: stackSize,
stackSizeType: stackSizeType, stackSizeType: stackSizeType,
+1 -1
View File
@@ -28,7 +28,6 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4", "cortex-m4",
"cortex-m7", "cortex-m7",
"esp32c3", "esp32c3",
"esp32c6",
"esp32s3", "esp32s3",
"fe310", "fe310",
"gameboy-advance", "gameboy-advance",
@@ -47,6 +46,7 @@ func TestClangAttributes(t *testing.T) {
targetNames = append(targetNames, "esp32", "esp8266") targetNames = append(targetNames, "esp32", "esp8266")
} }
for _, targetName := range targetNames { for _, targetName := range targetNames {
targetName := targetName
t.Run(targetName, func(t *testing.T) { t.Run(targetName, func(t *testing.T) {
testClangAttributes(t, &compileopts.Options{Target: targetName}) testClangAttributes(t, &compileopts.Options{Target: targetName})
}) })
+1 -1
View File
@@ -281,7 +281,7 @@ func parseDepFile(s string) ([]string, error) {
s = strings.ReplaceAll(s, "\\\n", " ") s = strings.ReplaceAll(s, "\\\n", " ")
// Only use the first line, which is expected to begin with "deps:". // Only use the first line, which is expected to begin with "deps:".
line, _, _ := strings.Cut(s, "\n") line := strings.SplitN(s, "\n", 2)[0]
if !strings.HasPrefix(line, "deps:") { if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix") return nil, errors.New("readDepFile: expected 'deps:' prefix")
} }
+1 -1
View File
@@ -17,7 +17,7 @@ import (
var commands = map[string][]string{} var commands = map[string][]string{}
func init() { func init() {
llvmMajor, _, _ := strings.Cut(llvm.Version, ".") llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor} commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"} commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"} commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
+1 -1
View File
@@ -25,7 +25,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
} }
// Version range supported by TinyGo. // Version range supported by TinyGo.
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml const minorMin = 19
const minorMax = 26 const minorMax = 26
// Check that we support this Go toolchain version. // Check that we support this Go toolchain version.
+1 -1
View File
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
return &compileJob{ return &compileJob{
description: "compile Darwin libSystem.dylib", description: "compile Darwin libSystem.dylib",
run: func(job *compileJob) (err error) { run: func(job *compileJob) (err error) {
arch, _, _ := strings.Cut(config.Triple(), "-") arch := strings.Split(config.Triple(), "-")[0]
job.result = filepath.Join(tmpdir, "libSystem.dylib") job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o") objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s") inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
+2 -14
View File
@@ -100,24 +100,12 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip_id := map[string]uint16{ chip_id := map[string]uint16{
"esp32": 0x0000, "esp32": 0x0000,
"esp32c3": 0x0005, "esp32c3": 0x0005,
"esp32c6": 0x000d,
"esp32s3": 0x0009, "esp32s3": 0x0009,
}[chip] }[chip]
// SPI flash speed/size byte (byte 3 of header):
// Upper nibble = flash size, lower nibble = flash frequency.
// The espflasher auto-detects and patches the flash size (upper nibble),
// but the frequency (lower nibble) must be correct per chip.
spiSpeedSize := map[string]uint8{
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
}[chip]
// Image header. // Image header.
switch chip { switch chip {
case "esp32", "esp32c3", "esp32s3", "esp32c6": case "esp32", "esp32c3", "esp32s3":
// Header format: // Header format:
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71 // https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
// Note: not adding a SHA256 hash as the binary is modified by // Note: not adding a SHA256 hash as the binary is modified by
@@ -139,7 +127,7 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
magic: 0xE9, magic: 0xE9,
segment_count: byte(len(segments)), segment_count: byte(len(segments)),
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
spi_speed_size: spiSpeedSize, spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
entry_addr: uint32(inf.Entry), entry_addr: uint32(inf.Entry),
wp_pin: 0xEE, // disable WP pin wp_pin: 0xEE, // disable WP pin
chip_id: chip_id, chip_id: chip_id,
+2 -2
View File
@@ -195,11 +195,11 @@ type intHeap struct {
sort.IntSlice sort.IntSlice
} }
func (h *intHeap) Push(x any) { func (h *intHeap) Push(x interface{}) {
h.IntSlice = append(h.IntSlice, x.(int)) h.IntSlice = append(h.IntSlice, x.(int))
} }
func (h *intHeap) Pop() any { func (h *intHeap) Pop() interface{} {
x := h.IntSlice[len(h.IntSlice)-1] x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1] h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x return x
+2 -1
View File
@@ -218,7 +218,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
return err return err
} }
// Store this archive in the cache. // Store this archive in the cache.
return robustRename(f.Name(), archiveFilePath) return os.Rename(f.Name(), archiveFilePath)
}, },
} }
@@ -232,6 +232,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
} }
for _, path := range paths { for _, path := range paths {
// Strip leading "../" parts off the path. // Strip leading "../" parts off the path.
path := path
cleanpath := path cleanpath := path
for strings.HasPrefix(cleanpath, "../") { for strings.HasPrefix(cleanpath, "../") {
cleanpath = cleanpath[3:] cleanpath = cleanpath[3:]
+2 -2
View File
@@ -28,8 +28,8 @@ func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
if err != nil { if err != nil {
return err return err
} }
lines := strings.SplitSeq(string(data), "\n") lines := strings.Split(string(data), "\n")
for line := range lines { for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") { if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line) matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1] value := matches[1]
-9
View File
@@ -1,9 +0,0 @@
//go:build !windows
package builder
import "os"
func robustRename(oldpath, newpath string) error {
return os.Rename(oldpath, newpath)
}
-44
View File
@@ -1,44 +0,0 @@
package builder
import (
"errors"
"math/rand"
"os"
"syscall"
"time"
)
const robustRenameTimeout = 2 * time.Second
func robustRename(oldpath, newpath string) error {
var bestErr error
start := time.Now()
nextSleep := time.Millisecond
for {
err := os.Rename(oldpath, newpath)
if err == nil || !isEphemeralRenameError(err) {
return err
}
if bestErr == nil {
bestErr = err
}
if d := time.Since(start) + nextSleep; d >= robustRenameTimeout {
return bestErr
}
time.Sleep(nextSleep)
nextSleep += time.Duration(rand.Int63n(int64(nextSleep)))
}
}
func isEphemeralRenameError(err error) bool {
var errno syscall.Errno
if errors.As(err, &errno) {
switch errno {
case syscall.Errno(2), // ERROR_FILE_NOT_FOUND
syscall.Errno(5), // ERROR_ACCESS_DENIED
syscall.Errno(32): // ERROR_SHARING_VIOLATION
return true
}
}
return false
}
+2 -8
View File
@@ -501,9 +501,8 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
Align: section.Addralign, Align: section.Addralign,
Type: memoryStack, Type: memoryStack,
}) })
} else if section.Flags&elf.SHF_WRITE != 0 { } else {
// Regular .bss section. Zero-initialized RAM is always // Regular .bss section.
// writable, so require SHF_WRITE here.
sections = append(sections, memorySection{ sections = append(sections, memorySection{
Address: section.Addr, Address: section.Addr,
Size: section.Size, Size: section.Size,
@@ -511,11 +510,6 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
Type: memoryBSS, Type: memoryBSS,
}) })
} }
// Other (non-writable) SHT_NOBITS sections are address-space
// placeholders that occupy no RAM, such as the ESP linker
// script's .irom_dummy / .rodata_dummy sections which reserve
// the flash-mapped XIP virtual address ranges. They must not be
// counted as bss/RAM usage.
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 { } else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
// .text // .text
sections = append(sections, memorySection{ sections = append(sections, memorySection{
+6 -4
View File
@@ -42,15 +42,16 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 3771, 309, 0, 2260}, {"hifive1b", "examples/echo", 3680, 280, 0, 2252},
{"microbit", "examples/serial", 2832, 368, 8, 2256}, {"microbit", "examples/serial", 2694, 342, 8, 2248},
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488}, {"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
// output varies by binaryen version. // output varies by binaryen version.
} }
for _, tc := range tests { for _, tc := range tests {
tc := tc
t.Run(tc.target+"/"+tc.path, func(t *testing.T) { t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
t.Parallel() t.Parallel()
@@ -84,6 +85,7 @@ func TestSizeFull(t *testing.T) {
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task" pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
for _, target := range tests { for _, target := range tests {
target := target
t.Run(target, func(t *testing.T) { t.Run(target, func(t *testing.T) {
t.Parallel() t.Parallel()
@@ -97,7 +99,7 @@ func TestSizeFull(t *testing.T) {
t.Fatal("could not read program size:", err) t.Fatal("could not read program size:", err)
} }
for _, pkg := range sizes.sortedPackageNames() { for _, pkg := range sizes.sortedPackageNames() {
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" { if pkg == "(padding)" || pkg == "(unknown)" {
// TODO: correctly attribute all unknown binary size. // TODO: correctly attribute all unknown binary size.
continue continue
} }
+3 -5
View File
@@ -116,16 +116,14 @@ func parseLLDErrors(text string) error {
// This can happen in some cases like with CGo and //go:linkname tricker. // This can happen in some cases like with CGo and //go:linkname tricker.
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil { if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
symbolName := matches[2] symbolName := matches[2]
for line := range strings.SplitSeq(message, "\n") { for _, line := range strings.Split(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line) matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil { if matches != nil {
parsedError = true parsedError = true
line, _ := strconv.Atoi(matches[3]) line, _ := strconv.Atoi(matches[3])
// TODO: detect common mistakes like -gc=none?
msg := "linker could not find symbol " + symbolName msg := "linker could not find symbol " + symbolName
switch symbolName { if symbolName == "runtime.alloc_noheap" {
case "runtime.alloc":
msg = "object allocated on the heap with -gc=none"
case "runtime.alloc_noheap":
msg = "object allocated on the heap in //go:noheap function" msg = "object allocated on the heap in //go:noheap function"
} }
linkErrors = append(linkErrors, scanner.Error{ linkErrors = append(linkErrors, scanner.Error{
+1 -1
View File
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
} }
bl.SetNumBlocks(len(blocks)) bl.SetNumBlocks(len(blocks))
for i := range blocks { for i := 0; i < len(blocks); i++ {
bl.SetBlockNo(i) bl.SetBlockNo(i)
bl.SetData(blocks[i]) bl.SetData(blocks[i])
+30 -10
View File
@@ -26,6 +26,10 @@ import (
"golang.org/x/tools/go/ast/astutil" "golang.org/x/tools/go/ast/astutil"
) )
// Function that's only defined in Go 1.22.
var setASTFileFields = func(f *ast.File, start, end token.Pos) {
}
// cgoPackage holds all CGo-related information of a package. // cgoPackage holds all CGo-related information of a package.
type cgoPackage struct { type cgoPackage struct {
generated *ast.File generated *ast.File
@@ -40,7 +44,7 @@ type cgoPackage struct {
tokenFiles map[string]*token.File tokenFiles map[string]*token.File
definedGlobally map[string]ast.Node definedGlobally map[string]ast.Node
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
anonDecls map[any]string anonDecls map[interface{}]string
cflags []string // CFlags from #cgo lines cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte visitedFiles map[string][]byte
@@ -259,7 +263,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
tokenFiles: map[string]*token.File{}, tokenFiles: map[string]*token.File{},
definedGlobally: map[string]ast.Node{}, definedGlobally: map[string]ast.Node{},
noescapingFuncs: map[string]*noescapingFunc{}, noescapingFuncs: map[string]*noescapingFunc{},
anonDecls: map[any]string{}, anonDecls: map[interface{}]string{},
visitedFiles: map[string][]byte{}, visitedFiles: map[string][]byte{},
} }
@@ -302,7 +306,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Find `import "C"` C fragments in the file. // Find `import "C"` C fragments in the file.
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
for i, f := range files { for i, f := range files {
var cgoHeader strings.Builder var cgoHeader string
for i := 0; i < len(f.Decls); i++ { for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i] decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl) genDecl, ok := decl.(*ast.GenDecl)
@@ -337,8 +341,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
// Iterate through all parts of the CGo header. Note that every // // Iterate through all parts of the CGo header. Note that every //
// line is a new comment. // line is a new comment.
position := fset.Position(genDecl.Doc.Pos()) position := fset.Position(genDecl.Doc.Pos())
var fragment strings.Builder fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
for _, comment := range genDecl.Doc.List { for _, comment := range genDecl.Doc.List {
// Find all #cgo lines, extract and use their contents, and // Find all #cgo lines, extract and use their contents, and
// replace the lines with spaces (to preserve locations). // replace the lines with spaces (to preserve locations).
@@ -355,13 +358,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} else { // comment } else { // comment
c = " " + c[2:len(c)-2] c = " " + c[2:len(c)-2]
} }
fragment.WriteString(c) fragment += c + "\n"
fragment.WriteByte('\n')
} }
cgoHeader.WriteString(fragment.String()) cgoHeader += fragment
} }
p.cgoHeaders[i] = cgoHeader.String() p.cgoHeaders[i] = cgoHeader
} }
// Define CFlags that will be used while parsing the package. // Define CFlags that will be used while parsing the package.
@@ -652,6 +654,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{ X: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "union", Name: "union",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: pos, NamePos: pos,
@@ -705,6 +708,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{ X: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -760,6 +764,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -806,6 +811,11 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
}, },
}, },
Type: &ast.StarExpr{ Type: &ast.StarExpr{
@@ -813,6 +823,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -870,6 +881,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -952,6 +964,11 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: &ast.Object{
Kind: ast.Var,
Name: "s",
Decl: nil,
},
}, },
}, },
Type: &ast.StarExpr{ Type: &ast.StarExpr{
@@ -959,6 +976,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: typeName, Name: typeName,
Obj: nil,
}, },
}, },
}, },
@@ -979,6 +997,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{ {
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "value", Name: "value",
Obj: nil,
}, },
}, },
Type: bitfield.field.Type, Type: bitfield.field.Type,
@@ -996,6 +1015,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{ X: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
Name: "s", Name: "s",
Obj: nil,
}, },
Sel: &ast.Ident{ Sel: &ast.Ident{
NamePos: bitfield.pos, NamePos: bitfield.pos,
@@ -1219,7 +1239,7 @@ func getPos(node ast.Node) token.Pos {
// getUnnamedDeclName creates a name (with the given prefix) for the given C // getUnnamedDeclName creates a name (with the given prefix) for the given C
// declaration. This is used for structs, unions, and enums that are often // declaration. This is used for structs, unions, and enums that are often
// defined without a name and used in a typedef. // defined without a name and used in a typedef.
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string { func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
if name, ok := p.anonDecls[itf]; ok { if name, ok := p.anonDecls[itf]; ok {
return name return name
} }
+17
View File
@@ -0,0 +1,17 @@
//go:build go1.22
package cgo
// Code specifically for Go 1.22.
import (
"go/ast"
"go/token"
)
func init() {
setASTFileFields = func(f *ast.File, start, end token.Pos) {
f.FileStart = start
f.FileEnd = end
}
}
+1
View File
@@ -45,6 +45,7 @@ func TestCGo(t *testing.T) {
"flags", "flags",
"const", "const",
} { } {
name := name // avoid a race condition
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
// Read the AST in memory. // Read the AST in memory.
path := filepath.Join("testdata", name+".go") path := filepath.Join("testdata", name+".go")
+63 -5
View File
@@ -160,7 +160,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
pos := f.getClangLocationPosition(location, unit) pos := f.getClangLocationPosition(location, unit)
f.addError(pos, severity+": "+spelling) f.addError(pos, severity+": "+spelling)
} }
for i := range numDiagnostics { for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i)) diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic) addDiagnostic(diagnostic)
@@ -217,6 +217,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
case C.CXCursor_FunctionDecl: case C.CXCursor_FunctionDecl:
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c)) numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
obj := &ast.Object{
Kind: ast.Fun,
Name: "_Cgo_" + name,
}
exportName := name exportName := name
localName := name localName := name
var stringSignature string var stringSignature string
@@ -254,6 +258,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + localName, Name: "_Cgo_" + localName,
Obj: obj,
}, },
Type: &ast.FuncType{ Type: &ast.FuncType{
Func: pos, Func: pos,
@@ -278,7 +283,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Text: strings.Join(doc, "\n"), Text: strings.Join(doc, "\n"),
}) })
} }
for i := range numArgs { for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i)) arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg)) argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i)) argType := C.clang_getArgType(cursorType, C.uint(i))
@@ -290,6 +295,11 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
{ {
NamePos: pos, NamePos: pos,
Name: argName, Name: argName,
Obj: &ast.Object{
Kind: ast.Var,
Name: argName,
Decl: decl,
},
}, },
}, },
Type: f.makeDecayingASTType(argType, pos), Type: f.makeDecayingASTType(argType, pos),
@@ -305,6 +315,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
} }
} }
obj.Decl = decl
return decl, stringSignature return decl, stringSignature
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl: case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
typ := f.makeASTRecordType(c, pos) typ := f.makeASTRecordType(c, pos)
@@ -314,27 +325,39 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
// Convert to a single-field struct type. // Convert to a single-field struct type.
typeExpr = f.makeUnionField(typ) typeExpr = f.makeUnionField(typ)
} }
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: typ.pos, NamePos: typ.pos,
Name: typeName, Name: typeName,
Obj: obj,
}, },
Type: typeExpr, Type: typeExpr,
} }
obj.Decl = typeSpec
return typeSpec, typ return typeSpec, typ
case C.CXCursor_TypedefDecl: case C.CXCursor_TypedefDecl:
typeName := "_Cgo_" + name typeName := "_Cgo_" + name
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c) underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
obj := &ast.Object{
Kind: ast.Typ,
Name: typeName,
}
typeSpec := &ast.TypeSpec{ typeSpec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: typeName, Name: typeName,
Obj: obj,
}, },
Type: f.makeASTType(underlyingType, pos), Type: f.makeASTType(underlyingType, pos),
} }
if underlyingType.kind != C.CXType_Enum { if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos typeSpec.Assign = pos
} }
obj.Decl = typeSpec
return typeSpec, nil return typeSpec, nil
case C.CXCursor_VarDecl: case C.CXCursor_VarDecl:
cursorType := C.tinygo_clang_getCursorType(c) cursorType := C.tinygo_clang_getCursorType(c)
@@ -353,13 +376,19 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
}, },
}, },
} }
obj := &ast.Object{
Kind: ast.Var,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "_Cgo_" + name,
Obj: obj,
}}, }},
Type: typeExpr, Type: typeExpr,
} }
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
case C.CXCursor_MacroDefinition: case C.CXCursor_MacroDefinition:
@@ -376,16 +405,26 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos, Lparen: token.NoPos,
Rparen: token.NoPos, Rparen: token.NoPos,
} }
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "_Cgo_" + name,
Obj: obj,
}}, }},
Values: []ast.Expr{expr}, Values: []ast.Expr{expr},
} }
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
case C.CXCursor_EnumDecl: case C.CXCursor_EnumDecl:
obj := &ast.Object{
Kind: ast.Typ,
Name: "_Cgo_" + name,
}
underlying := C.tinygo_clang_getEnumDeclIntegerType(c) underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
// TODO: gc's CGo implementation uses types such as `uint32` for enums // TODO: gc's CGo implementation uses types such as `uint32` for enums
// instead of types such as C.int, which are used here. // instead of types such as C.int, which are used here.
@@ -393,10 +432,12 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "_Cgo_" + name,
Obj: obj,
}, },
Assign: pos, Assign: pos,
Type: f.makeASTType(underlying, pos), Type: f.makeASTType(underlying, pos),
} }
obj.Decl = typeSpec
return typeSpec, nil return typeSpec, nil
case C.CXCursor_EnumConstantDecl: case C.CXCursor_EnumConstantDecl:
value := C.tinygo_clang_getEnumConstantDeclValue(c) value := C.tinygo_clang_getEnumConstantDeclValue(c)
@@ -411,13 +452,19 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Lparen: token.NoPos, Lparen: token.NoPos,
Rparen: token.NoPos, Rparen: token.NoPos,
} }
obj := &ast.Object{
Kind: ast.Con,
Name: "_Cgo_" + name,
}
valueSpec := &ast.ValueSpec{ valueSpec := &ast.ValueSpec{
Names: []*ast.Ident{{ Names: []*ast.Ident{{
NamePos: pos, NamePos: pos,
Name: "_Cgo_" + name, Name: "_Cgo_" + name,
Obj: obj,
}}, }},
Values: []ast.Expr{expr}, Values: []ast.Expr{expr},
} }
obj.Decl = valueSpec
gen.Specs = append(gen.Specs, valueSpec) gen.Specs = append(gen.Specs, valueSpec)
return gen, nil return gen, nil
default: default:
@@ -534,7 +581,7 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
// Get the precise location in the source code. Used for uniquely identifying // Get the precise location in the source code. Used for uniquely identifying
// source locations. // source locations.
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) any { func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
clangLocation := C.tinygo_clang_getCursorLocation(cursor) clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile var file C.CXFile
var line C.unsigned var line C.unsigned
@@ -592,8 +639,7 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
Package: f.Pos(0), Package: f.Pos(0),
Name: ast.NewIdent(p.packageName), Name: ast.NewIdent(p.packageName),
} }
astFile.FileStart = f.Pos(0) setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
astFile.FileEnd = f.Pos(int(size))
p.cgoFiles = append(p.cgoFiles, astFile) p.cgoFiles = append(p.cgoFiles, astFile)
} }
positionFile := p.tokenFiles[filename] positionFile := p.tokenFiles[filename]
@@ -910,16 +956,22 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
} }
// Construct an *ast.TypeSpec for this type. // Construct an *ast.TypeSpec for this type.
obj := &ast.Object{
Kind: ast.Typ,
Name: name,
}
spec := &ast.TypeSpec{ spec := &ast.TypeSpec{
Name: &ast.Ident{ Name: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: name, Name: name,
Obj: obj,
}, },
Type: &ast.Ident{ Type: &ast.Ident{
NamePos: pos, NamePos: pos,
Name: goName, Name: goName,
}, },
} }
obj.Decl = spec
return spec return spec
} }
@@ -1052,6 +1104,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
pos: prevField.Names[0].NamePos, pos: prevField.Names[0].NamePos,
}) })
prevField.Names[0].Name = bitfieldName prevField.Names[0].Name = bitfieldName
prevField.Names[0].Obj.Name = bitfieldName
} }
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1] prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
prevBitfield.endBit = bitfieldOffset prevBitfield.endBit = bitfieldOffset
@@ -1068,6 +1121,11 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
{ {
NamePos: pos, NamePos: pos,
Name: name, Name: name,
Obj: &ast.Object{
Kind: ast.Var,
Name: name,
Decl: field,
},
}, },
} }
fieldList.List = append(fieldList.List, field) fieldList.List = append(fieldList.List, field)
+4 -4
View File
@@ -12,17 +12,17 @@ import "C"
// C. It is useful if an API uses function pointers and you cannot pass a Go // C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer. // pointer but only a C pointer.
type refMap struct { type refMap struct {
refs map[unsafe.Pointer]any refs map[unsafe.Pointer]interface{}
lock sync.Mutex lock sync.Mutex
} }
// Put stores a value in the map. It can later be retrieved using Get. It must // Put stores a value in the map. It can later be retrieved using Get. It must
// be removed using Remove to avoid memory leaks. // be removed using Remove to avoid memory leaks.
func (m *refMap) Put(v any) unsafe.Pointer { func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
if m.refs == nil { if m.refs == nil {
m.refs = make(map[unsafe.Pointer]any, 1) m.refs = make(map[unsafe.Pointer]interface{}, 1)
} }
ref := C.malloc(1) ref := C.malloc(1)
m.refs[ref] = v m.refs[ref] = v
@@ -31,7 +31,7 @@ func (m *refMap) Put(v any) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same // Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put. // reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) any { func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock() m.lock.Lock()
defer m.lock.Unlock() defer m.lock.Unlock()
return m.refs[ref] return m.refs[ref]
+9 -20
View File
@@ -8,7 +8,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"slices"
"strconv" "strconv"
"strings" "strings"
@@ -141,7 +140,13 @@ func (c *Config) GC() string {
func (c *Config) NeedsStackObjects() bool { func (c *Config) NeedsStackObjects() bool {
switch c.GC() { switch c.GC() {
case "conservative", "custom", "precise", "boehm": case "conservative", "custom", "precise", "boehm":
return slices.Contains(c.BuildTags(), "tinygo.wasm") for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
default: default:
return false return false
} }
@@ -240,7 +245,7 @@ func (c *Config) RP2040BootPatch() bool {
// Return a canonicalized architecture name, so we don't have to deal with arm* // Return a canonicalized architecture name, so we don't have to deal with arm*
// vs thumb* vs arm64. // vs thumb* vs arm64.
func CanonicalArchName(triple string) string { func CanonicalArchName(triple string) string {
arch, _, _ := strings.Cut(triple, "-") arch := strings.Split(triple, "-")[0]
if arch == "arm64" { if arch == "arm64" {
return "aarch64" return "aarch64"
} }
@@ -461,22 +466,6 @@ func (c *Config) LDFlags() []string {
return ldflags return ldflags
} }
// LinkerFlavor returns how the configured linker should be driven.
// Usually this is derived from GOOS, but targets may override it explicitly.
func (c *Config) LinkerFlavor() string {
if c.Target.LinkerFlavor != "" {
return c.Target.LinkerFlavor
}
switch c.GOOS() {
case "windows":
return "coff"
case "darwin":
return "darwin"
default:
return "gnu"
}
}
// ExtraFiles returns the list of extra files to be built and linked with the // ExtraFiles returns the list of extra files to be built and linked with the
// executable. This can include extra C and assembly files. // executable. This can include extra C and assembly files.
func (c *Config) ExtraFiles() []string { func (c *Config) ExtraFiles() []string {
@@ -550,7 +539,7 @@ func (c *Config) Programmer() (method, openocdInterface string) {
case "openocd", "msd", "command", "adb": case "openocd", "msd", "command", "adb":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, c.Target.OpenOCDInterface return c.Options.Programmer, c.Target.OpenOCDInterface
case "bmp", "probe-rs": case "bmp":
// The -programmer flag only specifies the flash method. // The -programmer flag only specifies the flash method.
return c.Options.Programmer, "" return c.Options.Programmer, ""
default: default:
+16 -9
View File
@@ -3,7 +3,6 @@ package compileopts
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"slices"
"strings" "strings"
"time" "time"
) )
@@ -48,7 +47,6 @@ type Options struct {
Nobounds bool Nobounds bool
PrintSizes string PrintSizes string
PrintAllocs *regexp.Regexp // regexp string PrintAllocs *regexp.Regexp // regexp string
PrintAllocsCover bool // emit allocs in go coverage tool format
PrintStacks bool PrintStacks bool
Tags []string Tags []string
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
@@ -68,7 +66,7 @@ type Options struct {
// Verify performs a validation on the given options, raising an error if options are not valid. // Verify performs a validation on the given options, raising an error if options are not valid.
func (o *Options) Verify() error { func (o *Options) Verify() error {
if o.BuildMode != "" { if o.BuildMode != "" {
valid := slices.Contains(validBuildModeOptions, o.BuildMode) valid := isInArray(validBuildModeOptions, o.BuildMode)
if !valid { if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`, return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode, o.BuildMode,
@@ -76,7 +74,7 @@ func (o *Options) Verify() error {
} }
} }
if o.GC != "" { if o.GC != "" {
valid := slices.Contains(validGCOptions, o.GC) valid := isInArray(validGCOptions, o.GC)
if !valid { if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`, return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC, o.GC,
@@ -85,7 +83,7 @@ func (o *Options) Verify() error {
} }
if o.Scheduler != "" { if o.Scheduler != "" {
valid := slices.Contains(validSchedulerOptions, o.Scheduler) valid := isInArray(validSchedulerOptions, o.Scheduler)
if !valid { if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`, return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler, o.Scheduler,
@@ -94,7 +92,7 @@ func (o *Options) Verify() error {
} }
if o.Serial != "" { if o.Serial != "" {
valid := slices.Contains(validSerialOptions, o.Serial) valid := isInArray(validSerialOptions, o.Serial)
if !valid { if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`, return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial, o.Serial,
@@ -103,7 +101,7 @@ func (o *Options) Verify() error {
} }
if o.PrintSizes != "" { if o.PrintSizes != "" {
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes) valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid { if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`, return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes, o.PrintSizes,
@@ -112,7 +110,7 @@ func (o *Options) Verify() error {
} }
if o.PanicStrategy != "" { if o.PanicStrategy != "" {
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy) valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
if !valid { if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`, return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy, o.PanicStrategy,
@@ -121,10 +119,19 @@ func (o *Options) Verify() error {
} }
if o.Opt != "" { if o.Opt != "" {
if !slices.Contains(validOptOptions, o.Opt) { if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", ")) return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
} }
} }
return nil return nil
} }
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
-4
View File
@@ -38,7 +38,6 @@ type TargetSpec struct {
Scheduler string `json:"scheduler,omitempty"` Scheduler string `json:"scheduler,omitempty"`
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none) Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
Linker string `json:"linker,omitempty"` Linker string `json:"linker,omitempty"`
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin)
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt) RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc,omitempty"` Libc string `json:"libc,omitempty"`
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time. AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
@@ -68,7 +67,6 @@ type TargetSpec struct {
ADBPreCommands []string `json:"adb-pre-commands,omitempty"` ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
ADBPushRemote string `json:"adb-push-remote,omitempty"` ADBPushRemote string `json:"adb-push-remote,omitempty"`
ADBPostCommands []string `json:"adb-post-commands,omitempty"` ADBPostCommands []string `json:"adb-post-commands,omitempty"`
ProbeRSChip string `json:"probe-rs-chip,omitempty"`
CodeModel string `json:"code-model,omitempty"` CodeModel string `json:"code-model,omitempty"`
RelocationModel string `json:"relocation-model,omitempty"` RelocationModel string `json:"relocation-model,omitempty"`
WITPackage string `json:"wit-package,omitempty"` WITPackage string `json:"wit-package,omitempty"`
@@ -486,8 +484,6 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"--no-insert-timestamp", "--no-insert-timestamp",
"--no-dynamicbase", "--no-dynamicbase",
) )
spec.ExtraFiles = append(spec.ExtraFiles,
"src/runtime/runtime_windows.c")
case "wasm", "wasip1", "wasip2": case "wasm", "wasip1", "wasip2":
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS) return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
default: default:
-49
View File
@@ -112,52 +112,3 @@ func TestOverrideProperties(t *testing.T) {
} }
} }
func TestConfigLinkerFlavor(t *testing.T) {
tests := []struct {
name string
target *TargetSpec
goos string
want string
}{
{
name: "default gnu",
target: &TargetSpec{},
goos: "linux",
want: "gnu",
},
{
name: "default coff",
target: &TargetSpec{},
goos: "windows",
want: "coff",
},
{
name: "default darwin",
target: &TargetSpec{},
goos: "darwin",
want: "darwin",
},
{
name: "target override",
target: &TargetSpec{
LinkerFlavor: "coff",
},
goos: "linux",
want: "coff",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.target.GOOS = tc.goos
config := &Config{
Options: &Options{},
Target: tc.target,
}
if got := config.LinkerFlavor(); got != tc.want {
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
}
})
}
}
+8 -21
View File
@@ -241,35 +241,22 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
} }
} }
faultBlock := b.getRuntimeAssertBlock(blockPrefix, assertFunc) // Put the fault block at the end of the function and the next block at the
// current insert position.
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
nextBlock := b.insertBasicBlock(blockPrefix + ".next") nextBlock := b.insertBasicBlock(blockPrefix + ".next")
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
// Now branch to the out-of-bounds or the regular block. // Now branch to the out-of-bounds or the regular block.
b.CreateCondBr(assert, faultBlock, nextBlock) b.CreateCondBr(assert, faultBlock, nextBlock)
// Ok: assert didn't trigger so continue normally. // Fail: the assert triggered so panic.
b.SetInsertPointAtEnd(nextBlock) b.SetInsertPointAtEnd(faultBlock)
}
func (b *builder) getRuntimeAssertBlock(blockPrefix, assertFunc string) llvm.BasicBlock {
if b.runtimeAssertBlocks == nil {
b.runtimeAssertBlocks = make(map[string]llvm.BasicBlock)
}
if block := b.runtimeAssertBlocks[assertFunc]; !block.IsNil() {
return block
}
savedBlock := b.GetInsertBlock()
block := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
b.runtimeAssertBlocks[assertFunc] = block
b.SetInsertPointAtEnd(block)
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall(assertFunc, nil, "") b.createRuntimeCall(assertFunc, nil, "")
b.CreateUnreachable() b.CreateUnreachable()
b.SetInsertPointAtEnd(savedBlock)
return block // Ok: assert didn't trigger so continue normally.
b.SetInsertPointAtEnd(nextBlock)
} }
// extendInteger extends the value to at least targetType using a zero or sign // extendInteger extends the value to at least targetType using a zero or sign
+2 -2
View File
@@ -48,7 +48,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op") channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send. // Do the send.
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "") b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
// End the lifetime of the allocas. // End the lifetime of the allocas.
// This also works around a bug in CoroSplit, at least in LLVM 8: // This also works around a bug in CoroSplit, at least in LLVM 8:
@@ -101,7 +101,7 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
// createChanClose closes the given channel. // createChanClose closes the given channel.
func (b *builder) createChanClose(ch llvm.Value) { func (b *builder) createChanClose(ch llvm.Value) {
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "") b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
} }
// createSelect emits all IR necessary for a select statements. That's a // createSelect emits all IR necessary for a select statements. That's a
+21 -23
View File
@@ -90,10 +90,8 @@ type compilerContext struct {
astComments map[string]*ast.CommentGroup astComments map[string]*ast.CommentGroup
embedGlobals map[string][]*loader.EmbedFile embedGlobals map[string][]*loader.EmbedFile
pkg *types.Package pkg *types.Package
loaderPkg *loader.Package // current package being compiled (for AST access)
packageDir string // directory for this package packageDir string // directory for this package
runtimePkg *types.Package runtimePkg *types.Package
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
} }
// newCompilerContext returns a new compiler context ready for use, most // newCompilerContext returns a new compiler context ready for use, most
@@ -169,7 +167,7 @@ type builder struct {
dilocals map[*types.Var]llvm.Metadata dilocals map[*types.Var]llvm.Metadata
initInlinedAt llvm.Metadata // fake inlinedAt position initInlinedAt llvm.Metadata // fake inlinedAt position
initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations
allDeferFuncs []any allDeferFuncs []interface{}
deferFuncs map[*ssa.Function]int deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int deferInvokeFuncs map[string]int
deferClosureFuncs map[*ssa.Function]int deferClosureFuncs map[*ssa.Function]int
@@ -178,9 +176,6 @@ type builder struct {
deferBuiltinFuncs map[ssa.Value]deferBuiltin deferBuiltinFuncs map[ssa.Value]deferBuiltin
runDefersBlock []llvm.BasicBlock runDefersBlock []llvm.BasicBlock
afterDefersBlock []llvm.BasicBlock afterDefersBlock []llvm.BasicBlock
runtimeAssertBlocks map[string]llvm.BasicBlock
interfaceAssertBlock llvm.BasicBlock
} }
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder { func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
@@ -197,6 +192,16 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
} }
} }
// Return the runtime.alloc function variant.
// This is normally just "alloc", but is "alloc_noheap" if the //go:noheap
// pragma is used.
func (b *builder) allocFunc() string {
if b.info.noheap {
return "alloc_noheap"
}
return "alloc"
}
type blockInfo struct { type blockInfo struct {
// entry is the LLVM basic block corresponding to the start of this *ssa.Block. // entry is the LLVM basic block corresponding to the start of this *ssa.Block.
entry llvm.BasicBlock entry llvm.BasicBlock
@@ -299,18 +304,12 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.packageDir = pkg.OriginalDir() c.packageDir = pkg.OriginalDir()
c.embedGlobals = pkg.EmbedGlobals c.embedGlobals = pkg.EmbedGlobals
c.pkg = pkg.Pkg c.pkg = pkg.Pkg
c.loaderPkg = pkg
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog c.program = ssaPkg.Prog
// Convert AST to SSA. // Convert AST to SSA.
ssaPkg.Build() ssaPkg.Build()
// Assign names to function-local named types before compiling the
// package, so that types declared in different functions (or in
// different instantiations of a generic function) do not collide.
c.scanLocalTypes(ssaPkg)
// Initialize debug information. // Initialize debug information.
if c.Debug { if c.Debug {
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{ c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
@@ -870,9 +869,10 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
} }
// Create the function definition. // Create the function definition.
b := newBuilder(c, irbuilder, member) b := newBuilder(c, irbuilder, member)
if ok := b.defineMathOp(); ok { if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok {
// The body of this function (if there is one) is ignored and // The body of this function (if there is one) is ignored and
// replaced with a LLVM intrinsic call. // replaced with a LLVM intrinsic call.
b.defineMathOp()
continue continue
} }
if ok := b.defineMathBitsIntrinsic(); ok { if ok := b.defineMathBitsIntrinsic(); ok {
@@ -1895,12 +1895,6 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// not of the current function. // not of the current function.
useParentFrame = 1 useParentFrame = 1
} }
// Prevent inlining of functions that call recover(), matching the
// Go compiler's behavior. If this function were inlined into a
// deferred function, recover() would incorrectly succeed because
// the inlined code runs in the deferred function's context.
noinline := b.ctx.CreateEnumAttribute(llvm.AttributeKindID("noinline"), 0)
b.llvmFn.AddFunctionAttr(noinline)
return b.createRuntimeCall("_recover", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), useParentFrame, false)}, ""), nil return b.createRuntimeCall("_recover", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), useParentFrame, false)}, ""), nil
case "ssa:wrapnilchk": case "ssa:wrapnilchk":
// TODO: do an actual nil check? // TODO: do an actual nil check?
@@ -2156,10 +2150,13 @@ func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
if elementSize == 0 { if elementSize == 0 {
elementSize = 1 elementSize = 1
} }
maxSize := min( maxSize := maxPointerValue / elementSize
// len(slice) is an int. Make sure the length remains small enough to fit in // len(slice) is an int. Make sure the length remains small enough to fit in
// an int. // an int.
maxPointerValue/elementSize, maxIntegerValue) if maxSize > maxIntegerValue {
maxSize = maxIntegerValue
}
return maxSize return maxSize
} }
@@ -2186,8 +2183,9 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
} }
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos()) layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
align := b.targetData.ABITypeAlignment(typ) align := b.targetData.ABITypeAlignment(typ)
buf := b.createAlloc(sizeValue, layoutValue, align, expr.Comment) buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
return buf, nil return buf, nil
} else { } else {
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment) buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
@@ -2417,7 +2415,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
} }
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap") sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos()) layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf") slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign))) slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
// Extend or truncate if necessary. This is safe as we've already done // Extend or truncate if necessary. This is safe as we've already done
+3 -3
View File
@@ -188,9 +188,9 @@ func TestCompilerErrors(t *testing.T) {
t.Error(err) t.Error(err)
} }
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n") errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for line := range strings.SplitSeq(errorsFileString, "\n") { for _, line := range strings.Split(errorsFileString, "\n") {
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok { if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, after) expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
} }
} }
+12 -33
View File
@@ -60,6 +60,10 @@ func (b *builder) deferInitFunc() {
b.deferExprFuncs = make(map[ssa.Value]int) b.deferExprFuncs = make(map[ssa.Value]int)
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin) b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
// Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
if b.hasDeferFrame() { if b.hasDeferFrame() {
// Set up the defer frame with the current stack pointer. // Set up the defer frame with the current stack pointer.
// This assumes that the stack pointer doesn't move outside of the // This assumes that the stack pointer doesn't move outside of the
@@ -69,22 +73,12 @@ func (b *builder) deferInitFunc() {
// in the setjmp-like inline assembly. // in the setjmp-like inline assembly.
deferFrameType := b.getLLVMRuntimeType("deferFrame") deferFrameType := b.getLLVMRuntimeType("deferFrame")
b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf") b.deferFrame = b.CreateAlloca(deferFrameType, "deferframe.buf")
// The field index must match the DeferPtr field in runtime.deferFrame,
// defined in src/runtime/panic.go.
b.deferPtr = b.CreateInBoundsGEP(deferFrameType, b.deferFrame, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), 6, false), // DeferPtr field
}, "deferPtr")
stackPointer := b.readStackPointer() stackPointer := b.readStackPointer()
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "") b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
// Create the landing pad block, which is where control transfers after // Create the landing pad block, which is where control transfers after
// a panic. // a panic.
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad") b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
} else {
// Create defer list pointer.
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
} }
} }
@@ -116,8 +110,8 @@ func (b *builder) createLandingPad() {
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value { func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
// Construct inline assembly equivalents of setjmp. // Construct inline assembly equivalents of setjmp.
// The assembly works as follows: // The assembly works as follows:
// * Registers are either clobbered or, on 386, saved for longjmp to // * All registers (both callee-saved and caller saved) are clobbered
// restore if the ABI requires them to survive calls. // after the inline assembly returns.
// * The assembly stores the address just past the end of the assembly // * The assembly stores the address just past the end of the assembly
// into the jump buffer. // into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline // * The return value (eax, rax, r0, etc) is set to zero in the inline
@@ -130,12 +124,8 @@ func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
asmString = ` asmString = `
xorl %eax, %eax xorl %eax, %eax
movl $$1f, 4(%ebx) movl $$1f, 4(%ebx)
movl %ebx, 8(%ebx)
movl %esi, 12(%ebx)
movl %edi, 16(%ebx)
movl %ebp, 20(%ebx)
1:` 1:`
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}" constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses // This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions. // newer floating point instructions.
case "x86_64": case "x86_64":
@@ -247,17 +237,6 @@ func (b *builder) createInvokeCheckpoint() {
b.currentBlockInfo.exit = continueBB b.currentBlockInfo.exit = continueBB
} }
// createFaultCheckpoint is like createInvokeCheckpoint but for use in fault
// blocks (e.g., bounds check failures). Unlike createInvokeCheckpoint, it does
// not update currentBlockInfo.exit because the fault block is a dead-end that
// does not participate in phi node resolution.
func (b *builder) createFaultCheckpoint() {
isZero := b.createCheckpoint(b.deferFrame)
continueBB := b.insertBasicBlock("")
b.CreateCondBr(isZero, continueBB, b.landingpad)
b.SetInsertPointAtEnd(continueBB)
}
// isInLoop checks if there is a path from the current block to itself. // isInLoop checks if there is a path from the current block to itself.
// Use Tarjan's strongly connected components algorithm to search for cycles. // Use Tarjan's strongly connected components algorithm to search for cycles.
// A one-node SCC is a cycle iff there is an edge from the node to itself. // A one-node SCC is a cycle iff there is an edge from the node to itself.
@@ -509,7 +488,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
size := b.targetData.TypeAllocSize(deferredCallType) size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType) nilPtr := llvm.ConstNull(b.dataPtrType)
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call") alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
} }
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(alloca) b.trackPointer(alloca)
@@ -673,8 +652,8 @@ func (b *builder) createRunDefers() {
fn := callback.Fn.(*ssa.Function) fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType} valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
params := fn.Signature.Params() params := fn.Signature.Params()
for v := range params.Variables() { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(v.Type())) valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
} }
valueTypes = append(valueTypes, b.dataPtrType) // closure valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
@@ -699,8 +678,8 @@ func (b *builder) createRunDefers() {
//Get signature from call results //Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params() params := callback.Type().Underlying().(*types.Signature).Params()
for v := range params.Variables() { for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(v.Type())) valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
} }
deferredCallType := b.ctx.StructType(valueTypes, false) deferredCallType := b.ctx.StructType(valueTypes, false)
+2 -2
View File
@@ -81,8 +81,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
} }
} }
for v := range typ.Params().Variables() { for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(v.Type()) subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range c.expandFormalParamType(subType, "", nil) { for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType) paramTypes = append(paramTypes, info.llvmType)
} }
+8 -30
View File
@@ -5,38 +5,11 @@ package compiler
import ( import (
"go/token" "go/token"
"slices"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
// Heap-allocate a buffer of the given size. This will typically call
// runtime.alloc.
func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comment string) llvm.Value {
// Normally allocate using "runtime.alloc", but use "runtime.alloc_noheap"
// if the //go:noheap pragma is used.
allocFunc := "alloc"
if b.info.noheap {
allocFunc = "alloc_noheap"
}
// Allocs that don't allocate anything can return an architecture-specific
// sentinel value.
if !sizeValue.IsAConstantInt().IsNil() && sizeValue.ZExtValue() == 0 {
allocFunc = "alloc_zero"
}
// Make the runtime call.
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
if align != 0 {
// TODO: make sure all callsites set the correct alignment.
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
}
return call
}
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is // trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this. // one of the expressions that need this.
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) { func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
@@ -89,7 +62,7 @@ func (b *builder) trackValue(value llvm.Value) {
return return
} }
numElements := typ.StructElementTypesCount() numElements := typ.StructElementTypesCount()
for i := range numElements { for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "") subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue) b.trackValue(subValue)
} }
@@ -98,7 +71,7 @@ func (b *builder) trackValue(value llvm.Value) {
return return
} }
numElements := typ.ArrayLength() numElements := typ.ArrayLength()
for i := range numElements { for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "") subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue) b.trackValue(subValue)
} }
@@ -119,7 +92,12 @@ func typeHasPointers(t llvm.Type) bool {
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
return true return true
case llvm.StructTypeKind: case llvm.StructTypeKind:
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers) for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind: case llvm.ArrayTypeKind:
if t.ArrayLength() == 0 { if t.ArrayLength() == 0 {
return false return false
+3 -3
View File
@@ -97,7 +97,7 @@ func (b *builder) createGo(instr *ssa.Go) {
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature)) funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr) params = append(params, context, funcPtr)
hasContext = true hasContext = true
prefix = b.getFunctionInfo(b.fn).linkName prefix = b.fn.RelString(nil)
} }
paramBundle := b.emitPointerPack(params) paramBundle := b.emitPointerPack(params)
@@ -139,7 +139,7 @@ func (b *builder) createWasmExport() {
// Declare the exported function. // Declare the exported function.
paramTypes := b.llvmFnType.ParamTypes() paramTypes := b.llvmFnType.ParamTypes()
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false) exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
exportedFn := llvm.AddFunction(b.mod, b.getFunctionInfo(b.fn).linkName+suffix, exportedFnType) exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
b.addStandardAttributes(exportedFn) b.addStandardAttributes(exportedFn)
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn) llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport)) exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
@@ -414,7 +414,7 @@ func (c *compilerContext) createGoroutineStartWrapper(fnType llvm.Type, fn llvm.
// Extract parameters from the state object, and call the function // Extract parameters from the state object, and call the function
// that's being wrapped. // that's being wrapped.
var callParams []llvm.Value var callParams []llvm.Value
for i := range numParams { for i := 0; i < numParams; i++ {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{ gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false), llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
+10 -12
View File
@@ -146,14 +146,13 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
llvmArgs := []llvm.Value{} llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10) asm := "svc #" + strconv.FormatUint(num, 10)
var constraints strings.Builder constraints := "={r0}"
constraints.WriteString("={r0}")
for i, arg := range args[1:] { for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X arg = arg.(*ssa.MakeInterface).X
if i == 0 { if i == 0 {
constraints.WriteString(",0") constraints += ",0"
} else { } else {
constraints.WriteString(",{r" + strconv.Itoa(i) + "}") constraints += ",{r" + strconv.Itoa(i) + "}"
} }
llvmValue := b.getValue(arg, pos) llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue) llvmArgs = append(llvmArgs, llvmValue)
@@ -162,9 +161,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
// Implement the ARM calling convention by marking r1-r3 as // Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be // clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered. // marked as clobbered.
constraints.WriteString(",~{r1},~{r2},~{r3}") constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil return b.CreateCall(fnType, target, llvmArgs, ""), nil
} }
@@ -185,14 +184,13 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
llvmArgs := []llvm.Value{} llvmArgs := []llvm.Value{}
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
asm := "svc #" + strconv.FormatUint(num, 10) asm := "svc #" + strconv.FormatUint(num, 10)
var constraints strings.Builder constraints := "={x0}"
constraints.WriteString("={x0}")
for i, arg := range args[1:] { for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X arg = arg.(*ssa.MakeInterface).X
if i == 0 { if i == 0 {
constraints.WriteString(",0") constraints += ",0"
} else { } else {
constraints.WriteString(",{x" + strconv.Itoa(i) + "}") constraints += ",{x" + strconv.Itoa(i) + "}"
} }
llvmValue := b.getValue(arg, pos) llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue) llvmArgs = append(llvmArgs, llvmValue)
@@ -201,9 +199,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
// Implement the ARM64 calling convention by marking x1-x7 as // Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be // clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered. // marked as clobbered.
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}") constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false) target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil return b.CreateCall(fnType, target, llvmArgs, ""), nil
} }
+51 -333
View File
@@ -10,7 +10,6 @@ import (
"fmt" "fmt"
"go/token" "go/token"
"go/types" "go/types"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
@@ -145,8 +144,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
// For a non-interface type, it returns the number of exported methods. // For a non-interface type, it returns the number of exported methods.
// For an interface type, it returns the number of exported and unexported methods. // For an interface type, it returns the number of exported and unexported methods.
var numMethods int var numMethods int
for method := range ms.Methods() { for i := 0; i < ms.Len(); i++ {
if isInterface || method.Obj().Exported() { if isInterface || ms.At(i).Obj().Exported() {
numMethods++ numMethods++
} }
} }
@@ -166,7 +165,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
} }
} }
typeCodeName, isLocal := c.getTypeCodeName(typ) typeCodeName, isLocal := getTypeCodeName(typ)
globalName := "reflect/types.type:" + typeCodeName globalName := "reflect/types.type:" + typeCodeName
var global llvm.Value var global llvm.Value
if isLocal { if isLocal {
@@ -193,8 +192,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
} }
// Compute the method set value for types that support methods. // Compute the method set value for types that support methods.
var methods []*types.Func var methods []*types.Func
for method := range ms.Methods() { for i := 0; i < ms.Len(); i++ {
methods = append(methods, method.Obj().(*types.Func)) methods = append(methods, ms.At(i).Obj().(*types.Func))
} }
methodSetType := types.NewStruct([]*types.Var{ methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
@@ -490,7 +489,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
c.getTypeMethodSet(typ), c.getTypeMethodSet(typ),
}, typeFields...) }, typeFields...)
} }
alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4) alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 {
alignment = 4
}
globalValue := c.ctx.ConstStruct(typeFields, false) globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue) global.SetInitializer(globalValue)
if isLocal { if isLocal {
@@ -578,50 +580,20 @@ var basicTypeNames = [...]string{
// getTypeCodeName returns a name for this type that can be used in the // getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect // interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum. // package. See getTypeCodeNum.
// func getTypeCodeName(t types.Type) (string, bool) {
// isLocal is true when the type is declared inside a function body.
// Such types need a per-declaration (or per instantiation) suffix
// because their printed names are not unique.
//
// Ordinary function-local types (TypeName.Parent() != nil) are
// disambiguated lazily from their declaration position: every
// declaration in the package has a distinct (file, line, column)
// triple, and the position is taken un-//line-adjusted so it is
// stable across builds. Such types are only nameable inside their
// declaring package, so the name does not need to agree with anything
// computed in another package.
//
// Synthetic locals (TypeName.Parent() == nil), produced by generic
// instantiation, are pre-registered by scanLocalTypes because their
// names must agree across packages that materialize the same instance.
func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) {
switch t := types.Unalias(t).(type) { switch t := types.Unalias(t).(type) {
case *types.Named: case *types.Named:
tn := t.Obj() if t.Obj().Parent() != t.Obj().Pkg().Scope() {
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() { return "named:" + t.String() + "$local", true
// Package-scope or builtin: the printed name is unique. }
return "named:" + t.String(), false return "named:" + t.String(), false
}
if tn.Parent() != nil {
// Ordinary function-local type. Use the un-//line-adjusted
// declaration position as the disambiguator.
pos := c.program.Fset.PositionFor(tn.Pos(), false)
return fmt.Sprintf("named:%s$%s:%d:%d", t.String(), filepath.Base(pos.Filename), pos.Line, pos.Column), true
}
// Synthetic local from generic instantiation: must have been
// pre-registered by scanLocalTypes.
v := c.localTypeNames.At(t)
if v == nil {
panic("compiler: synthetic local type " + tn.Name() + " was not registered by scanLocalTypes")
}
return "named:" + v.(string), true
case *types.Array: case *types.Array:
s, isLocal := c.getTypeCodeName(t.Elem()) s, isLocal := getTypeCodeName(t.Elem())
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
case *types.Basic: case *types.Basic:
return "basic:" + basicTypeNames[t.Kind()], false return "basic:" + basicTypeNames[t.Kind()], false
case *types.Chan: case *types.Chan:
s, isLocal := c.getTypeCodeName(t.Elem()) s, isLocal := getTypeCodeName(t.Elem())
var dir string var dir string
switch t.Dir() { switch t.Dir() {
case types.SendOnly: case types.SendOnly:
@@ -641,7 +613,7 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
if !token.IsExported(name) { if !token.IsExported(name) {
name = t.Method(i).Pkg().Path() + "." + name name = t.Method(i).Pkg().Path() + "." + name
} }
s, local := c.getTypeCodeName(t.Method(i).Type()) s, local := getTypeCodeName(t.Method(i).Type())
if local { if local {
isLocal = true isLocal = true
} }
@@ -649,17 +621,17 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
} }
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
case *types.Map: case *types.Map:
keyType, keyLocal := c.getTypeCodeName(t.Key()) keyType, keyLocal := getTypeCodeName(t.Key())
elemType, elemLocal := c.getTypeCodeName(t.Elem()) elemType, elemLocal := getTypeCodeName(t.Elem())
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
case *types.Pointer: case *types.Pointer:
s, isLocal := c.getTypeCodeName(t.Elem()) s, isLocal := getTypeCodeName(t.Elem())
return "pointer:" + s, isLocal return "pointer:" + s, isLocal
case *types.Signature: case *types.Signature:
isLocal := false isLocal := false
params := make([]string, t.Params().Len()) params := make([]string, t.Params().Len())
for i := 0; i < t.Params().Len(); i++ { for i := 0; i < t.Params().Len(); i++ {
s, local := c.getTypeCodeName(t.Params().At(i).Type()) s, local := getTypeCodeName(t.Params().At(i).Type())
if local { if local {
isLocal = true isLocal = true
} }
@@ -667,7 +639,7 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
} }
results := make([]string, t.Results().Len()) results := make([]string, t.Results().Len())
for i := 0; i < t.Results().Len(); i++ { for i := 0; i < t.Results().Len(); i++ {
s, local := c.getTypeCodeName(t.Results().At(i).Type()) s, local := getTypeCodeName(t.Results().At(i).Type())
if local { if local {
isLocal = true isLocal = true
} }
@@ -675,7 +647,7 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
} }
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
case *types.Slice: case *types.Slice:
s, isLocal := c.getTypeCodeName(t.Elem()) s, isLocal := getTypeCodeName(t.Elem())
return "slice:" + s, isLocal return "slice:" + s, isLocal
case *types.Struct: case *types.Struct:
elems := make([]string, t.NumFields()) elems := make([]string, t.NumFields())
@@ -685,7 +657,7 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
if t.Field(i).Embedded() { if t.Field(i).Embedded() {
embedded = "#" embedded = "#"
} }
s, local := c.getTypeCodeName(t.Field(i).Type()) s, local := getTypeCodeName(t.Field(i).Type())
if local { if local {
isLocal = true isLocal = true
} }
@@ -700,232 +672,6 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
} }
} }
// scanLocalTypes assigns names to every synthetic *types.TypeName
// (TypeName.Parent() == nil) reachable from this package and stores
// them in c.localTypeNames.
//
// Synthetic TypeNames are produced by generic instantiation: two
// instantiations of the same generic function (e.g. F[int] and
// F[string]) produce TypeNames with the same printed name and the
// same source position, so each is named with the enclosing
// instance's RelString as prefix. RelString encodes the type
// arguments, matching Go's runtime behavior, where F[int].Inner and
// F[string].Inner are distinct types even when Inner does not mention
// the type parameter.
//
// A given instance may be materialized by several packages (the body
// of F[int] is compiled in every package that calls F[int]); its
// reflect/types.type:* global has LinkOnceODRLinkage and is merged by
// name at link time. The chosen name therefore depends only on
// intrinsic SSA properties (RelString and the raw token.Pos used as a
// sort key), so any package compiling the same instance produces the
// same identifier.
//
// Ordinary function-local TypeNames (TypeName.Parent() != nil) are
// not handled here: they are nameable only inside their declaring
// package, and getTypeCodeName derives a stable per-declaration name
// for them directly from their source position.
func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
// Locate every generic instance reachable from this package
// (including instances declared in imported packages and any
// function reached through an instance subtree).
var instances []*ssa.Function
seen := map[*ssa.Function]struct{}{}
var walk func(fn *ssa.Function, inInstance bool)
walk = func(fn *ssa.Function, inInstance bool) {
if fn == nil {
return
}
if _, ok := seen[fn]; ok {
return
}
// fn belongs to an instance subtree if it is itself an
// instantiation or if we reached it from one.
//
// len(TypeArgs()) is used instead of fn.Origin() because
// Origin() may call Build() on fn's declaring package, which
// would defeat per-package compilation.
isInstanceRoot := len(fn.TypeArgs()) > 0
if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg {
return
}
if fn.Blocks == nil && fn.AnonFuncs == nil {
return
}
seen[fn] = struct{}{}
isInInstance := inInstance || isInstanceRoot
if isInInstance {
instances = append(instances, fn)
}
for _, anon := range fn.AnonFuncs {
walk(anon, isInInstance)
}
var ops [10]*ssa.Value
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
for _, op := range instr.Operands(ops[:0]) {
if op == nil || *op == nil {
continue
}
if callee, ok := (*op).(*ssa.Function); ok {
walk(callee, isInInstance)
}
}
}
}
}
for _, member := range ssaPkg.Members {
switch m := member.(type) {
case *ssa.Function:
walk(m, false)
case *ssa.Type:
mset := c.program.MethodSets.MethodSet(m.Type())
for method := range mset.Methods() {
walk(c.program.MethodValue(method), false)
}
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
for method := range pmset.Methods() {
walk(c.program.MethodValue(method), false)
}
}
}
// Registration is first-writer-wins (a synthetic TypeName may be
// reachable from several instances), so visit instances in a
// deterministic order. Pos() is a defensive tiebreaker.
sort.Slice(instances, func(i, j int) bool {
ri, rj := instances[i].RelString(nil), instances[j].RelString(nil)
if ri != rj {
return ri < rj
}
return instances[i].Pos() < instances[j].Pos()
})
for _, fn := range instances {
c.registerSyntheticLocalTypes(fn)
}
}
// registerSyntheticLocalTypes walks every type reachable from fn's
// body and records each synthetic *types.Named (TypeName.Parent() ==
// nil) in c.localTypeNames. Each is named with fn.RelString as the
// owning function plus a per-function counter assigned in source
// order.
//
// First-writer-wins: a *types.Named already present in
// c.localTypeNames is left alone, so a synthetic type reachable from
// several instances keeps the name assigned by the first (in
// scanLocalTypes' deterministic order).
func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
var found []*types.Named
seen := map[types.Type]struct{}{}
var visit func(t types.Type)
visit = func(t types.Type) {
if t == nil {
return
}
if _, ok := seen[t]; ok {
return
}
seen[t] = struct{}{}
switch t := t.(type) {
case *types.Alias:
visit(types.Unalias(t))
case *types.Named:
tn := t.Obj()
if tn.Pkg() != nil && tn.Parent() == nil {
if c.localTypeNames.At(t) == nil {
// Reserve the slot so later calls within this
// scanLocalTypes invocation skip it; the final
// name is filled in after sorting, before any
// getTypeCodeName lookups happen.
c.localTypeNames.Set(t, "")
found = append(found, t)
}
}
targs := t.TypeArgs()
for t := range targs.Types() {
visit(t)
}
visit(t.Underlying())
case *types.Pointer:
visit(t.Elem())
case *types.Slice:
visit(t.Elem())
case *types.Array:
visit(t.Elem())
case *types.Chan:
visit(t.Elem())
case *types.Map:
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for field := range t.Fields() {
visit(field.Type())
}
case *types.Signature:
if p := t.Params(); p != nil {
for v := range p.Variables() {
visit(v.Type())
}
}
if r := t.Results(); r != nil {
for v := range r.Variables() {
visit(v.Type())
}
}
case *types.Tuple:
for v := range t.Variables() {
visit(v.Type())
}
case *types.Interface:
// A synthetic local type can be reachable only through a
// local interface's method signature, so descend into
// them. getTypeCodeName encodes those signatures into
// the interface's identifier, and the seen map breaks
// cycles formed by methods that mention the interface
// itself.
for method := range t.Methods() {
visit(method.Type())
}
}
}
for _, p := range fn.Params {
visit(p.Type())
}
for _, fv := range fn.FreeVars {
visit(fv.Type())
}
for _, l := range fn.Locals {
visit(l.Type())
}
var ops [10]*ssa.Value
for _, b := range fn.Blocks {
for _, instr := range b.Instrs {
if v, ok := instr.(ssa.Value); ok {
visit(v.Type())
}
for _, op := range instr.Operands(ops[:0]) {
if op != nil && *op != nil {
visit((*op).Type())
}
}
}
}
if len(found) == 0 {
return
}
// Sort by raw token.Pos: this gives a total order on declarations
// that is stable across builds and unaffected by //line directives
// (which only adjust the human-facing position from Fset.Position).
sort.Slice(found, func(i, j int) bool {
return found[i].Obj().Pos() < found[j].Obj().Pos()
})
enclosing := fn.RelString(nil)
for i, named := range found {
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
}
}
// getTypeMethodSet returns a reference (GEP) to a global method set. This // getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass. // method set should be unreferenced after the interface lowering pass.
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
@@ -936,7 +682,8 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Create method set. // Create method set.
var signatures, wrappers []llvm.Value var signatures, wrappers []llvm.Value
for method := range ms.Methods() { for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal) signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method) fn := c.program.MethodValue(method)
@@ -1022,7 +769,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum) commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
} }
} else { } else {
name, _ := b.getTypeCodeName(expr.AssertedType) name, _ := getTypeCodeName(expr.AssertedType)
globalName := "reflect/types.typeid:" + name globalName := "reflect/types.typeid:" + name
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() { if assertedTypeCodeGlobal.IsNil() {
@@ -1049,10 +796,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
prevBlock := b.GetInsertBlock() prevBlock := b.GetInsertBlock()
okBlock := b.insertBasicBlock("typeassert.ok") okBlock := b.insertBasicBlock("typeassert.ok")
if expr.CommaOk {
nextBlock := b.insertBasicBlock("typeassert.next") nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
b.CreateCondBr(commaOk, okBlock, nextBlock) b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was // Retrieve the value from the interface if the type assert was
@@ -1075,40 +820,17 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
phi := b.CreatePHI(assertedType, "typeassert.value") phi := b.CreatePHI(assertedType, "typeassert.value")
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock}) phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
if expr.CommaOk {
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
return tuple return tuple
} else { } else {
// Type assert without comma-ok. If it fails, panic. // This is kind of dirty as the branch above becomes mostly useless,
faultBlock := b.getInterfaceAssertBlock() // but hopefully this gets optimized away.
b.currentBlockInfo.exit = okBlock b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
b.CreateCondBr(commaOk, okBlock, faultBlock) return phi
// OK: extract the value from the interface.
b.SetInsertPointAtEnd(okBlock)
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
return itf
} }
return b.extractValueFromInterface(itf, assertedType)
}
}
func (b *builder) getInterfaceAssertBlock() llvm.BasicBlock {
if !b.interfaceAssertBlock.IsNil() {
return b.interfaceAssertBlock
}
savedBlock := b.GetInsertBlock()
block := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.throw")
b.interfaceAssertBlock = block
b.SetInsertPointAtEnd(block)
if b.hasDeferFrame() {
b.createFaultCheckpoint()
}
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "")
b.CreateUnreachable()
b.SetInsertPointAtEnd(savedBlock)
return block
} }
// getMethodsString returns a string to be used in the "tinygo-methods" string // getMethodsString returns a string to be used in the "tinygo-methods" string
@@ -1135,7 +857,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
if !token.IsExported(name) { if !token.IsExported(name) {
name = method.Pkg().Path() + "." + name name = method.Pkg().Path() + "." + name
} }
s, _ := c.getTypeCodeName(method.Type()) s, _ := getTypeCodeName(method.Type())
globalName := "reflect/types.signature:" + name + ":" + s globalName := "reflect/types.signature:" + name + ":" + s
value := c.mod.NamedGlobal(globalName) value := c.mod.NamedGlobal(globalName)
if value.IsNil() { if value.IsNil() {
@@ -1179,14 +901,14 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// thunk is declared, not defined: it will be defined by the interface lowering // thunk is declared, not defined: it will be defined by the interface lowering
// pass. // pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying()) s, _ := getTypeCodeName(instr.Value.Type().Underlying())
fnName := s + "." + instr.Method.Name() + "$invoke" fnName := s + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName) llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
sig := instr.Method.Type().(*types.Signature) sig := instr.Method.Type().(*types.Signature)
var paramTuple []*types.Var var paramTuple []*types.Var
for v := range sig.Params().Variables() { for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, v) paramTuple = append(paramTuple, sig.Params().At(i))
} }
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer]))
llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false))
@@ -1204,7 +926,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
// by the interface lowering pass as a type-ID comparison chain, avoiding the // by the interface lowering pass as a type-ID comparison chain, avoiding the
// need for runtime.typeImplementsMethodSet at compile time. // need for runtime.typeImplementsMethodSet at compile time.
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value { func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
s, _ := b.getTypeCodeName(intf) s, _ := getTypeCodeName(intf)
fnName := s + ".$typeassert" fnName := s + ".$typeassert"
llvmFn := b.mod.NamedFunction(fnName) llvmFn := b.mod.NamedFunction(fnName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
@@ -1303,38 +1025,34 @@ func methodSignature(method *types.Func) string {
// () string // () string
// (string, int) (int, error) // (string, int) (int, error)
func signature(sig *types.Signature) string { func signature(sig *types.Signature) string {
var s strings.Builder s := ""
if sig.Params().Len() == 0 { if sig.Params().Len() == 0 {
s.WriteString("()") s += "()"
} else { } else {
s.WriteString("(") s += "("
i := 0 for i := 0; i < sig.Params().Len(); i++ {
for v := range sig.Params().Variables() {
if i > 0 { if i > 0 {
s.WriteString(", ") s += ", "
} }
s.WriteString(typestring(v.Type())) s += typestring(sig.Params().At(i).Type())
i++
} }
s.WriteString(")") s += ")"
} }
if sig.Results().Len() == 0 { if sig.Results().Len() == 0 {
// keep as-is // keep as-is
} else if sig.Results().Len() == 1 { } else if sig.Results().Len() == 1 {
s.WriteString(" " + typestring(sig.Results().At(0).Type())) s += " " + typestring(sig.Results().At(0).Type())
} else { } else {
s.WriteString(" (") s += " ("
i := 0 for i := 0; i < sig.Results().Len(); i++ {
for v := range sig.Results().Variables() {
if i > 0 { if i > 0 {
s.WriteString(", ") s += ", "
} }
s.WriteString(typestring(v.Type())) s += typestring(sig.Results().At(i).Type())
i++
} }
s.WriteString(")") s += ")"
} }
return s.String() return s
} }
// typestring returns a stable (human-readable) type string for the given type // typestring returns a stable (human-readable) type string for the given type
+8 -42
View File
@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/tinygo-org/tinygo/compiler/llvmutil"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -167,22 +166,12 @@ func (b *builder) createMachineKeepAliveImpl() {
} }
var mathToLLVMMapping = map[string]string{ var mathToLLVMMapping = map[string]string{
"math.Acos": "llvm.acos.f64",
"math.Asin": "llvm.asin.f64",
"math.Atan": "llvm.atan.f64",
"math.Atan2": "llvm.atan2.f64",
"math.Ceil": "llvm.ceil.f64", "math.Ceil": "llvm.ceil.f64",
"math.Cos": "llvm.cos.f64",
"math.Cosh": "llvm.cosh.f64",
"math.Exp": "llvm.exp.f64", "math.Exp": "llvm.exp.f64",
"math.Exp2": "llvm.exp2.f64", "math.Exp2": "llvm.exp2.f64",
"math.Floor": "llvm.floor.f64", "math.Floor": "llvm.floor.f64",
"math.Log": "llvm.log.f64", "math.Log": "llvm.log.f64",
"math.Sin": "llvm.sin.f64",
"math.Sinh": "llvm.sinh.f64",
"math.Sqrt": "llvm.sqrt.f64", "math.Sqrt": "llvm.sqrt.f64",
"math.Tan": "llvm.tan.f64",
"math.Tanh": "llvm.tanh.f64",
"math.Trunc": "llvm.trunc.f64", "math.Trunc": "llvm.trunc.f64",
} }
@@ -196,40 +185,18 @@ var mathToLLVMMapping = map[string]string{
// float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is // float32(math.Sqrt(float64(v))) to a 32-bit floating point operation, which is
// beneficial on architectures where 64-bit floating point operations are (much) // beneficial on architectures where 64-bit floating point operations are (much)
// more expensive than 32-bit ones. // more expensive than 32-bit ones.
func (b *builder) defineMathOp() bool { func (b *builder) defineMathOp() {
llvmName, ok := mathToLLVMMapping[b.fn.RelString(nil)]
if !ok {
return false
}
if strings.HasSuffix(b.Triple, "-wasi") || llvmutil.Version() < 19 {
// We don't have a real libc for wasip2. Until that is fixed, we need to
// limit math intrinsics on WASI to a subset supported natively in
// WebAssembly.
// Also, since we don't know the specific libc we will target, disallow
// these for all WASI targets.
//
// We also need to limit ourselves to LLVM 19 and above for the extended
// set of math intrinsics, see:
// https://discourse.llvm.org/t/rfc-all-the-math-intrinsics/78294
switch b.fn.Name() {
case "Ceil", "Exp", "Exp2", "Floor", "Log", "Sqrt", "Trunc":
default:
return false
}
}
b.createFunctionStart(true) b.createFunctionStart(true)
llvmName := mathToLLVMMapping[b.fn.RelString(nil)]
if llvmName == "" {
panic("unreachable: unknown math operation") // sanity check
}
llvmFn := b.mod.NamedFunction(llvmName) llvmFn := b.mod.NamedFunction(llvmName)
if llvmFn.IsNil() { if llvmFn.IsNil() {
// The intrinsic doesn't exist yet, so declare it. // The intrinsic doesn't exist yet, so declare it.
var llvmType llvm.Type // At the moment, all supported intrinsics have the form "double
switch b.fn.Name() { // foo(double %x)" so we can hardcode the signature here.
case "Atan2": llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
// double atan2(double %y, double %x)
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType(), b.ctx.DoubleType()}, false)
default:
// double foo(double %x)
llvmType = llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
}
llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType) llvmFn = llvm.AddFunction(b.mod, llvmName, llvmType)
} }
// Create a call to the intrinsic. // Create a call to the intrinsic.
@@ -239,7 +206,6 @@ func (b *builder) defineMathOp() bool {
} }
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "") result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
b.CreateRet(result) b.CreateRet(result)
return true
} }
func (b *builder) defineCryptoIntrinsic() bool { func (b *builder) defineCryptoIntrinsic() bool {
+8 -4
View File
@@ -129,7 +129,11 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Packed data is bigger than a pointer, so allocate it on the heap. // Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false) sizeValue := llvm.ConstInt(b.uintptrType, size, false)
align := b.targetData.ABITypeAlignment(packedType) align := b.targetData.ABITypeAlignment(packedType)
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "") packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
sizeValue,
llvm.ConstNull(b.dataPtrType),
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
if b.NeedsStackObjects { if b.NeedsStackObjects {
b.trackPointer(packedAlloc) b.trackPointer(packedAlloc)
} }
@@ -206,7 +210,7 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
globalType := llvm.ArrayType(elementType, len(buf)) globalType := llvm.ArrayType(elementType, len(buf))
global := llvm.AddGlobal(c.mod, globalType, name) global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType) value := llvm.Undef(globalType)
for i := range buf { for i := 0; i < len(buf); i++ {
ch := uint64(buf[i]) ch := uint64(buf[i])
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "") value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
} }
@@ -390,7 +394,7 @@ func (c *compilerContext) buildPointerBitmap(
return return
} }
elementSize /= ptrAlign elementSize /= ptrAlign
for i := range len { for i := 0; i < len; i++ {
c.buildPointerBitmap( c.buildPointerBitmap(
dst, dst,
ptrAlign, ptrAlign,
@@ -417,7 +421,7 @@ func (c *compilerContext) archFamily() string {
// features string is not one for an ARM architecture. // features string is not one for an ARM architecture.
func (c *compilerContext) isThumb() bool { func (c *compilerContext) isThumb() bool {
var isThumb, isNotThumb bool var isThumb, isNotThumb bool
for feature := range strings.SplitSeq(c.Features, ",") { for _, feature := range strings.Split(c.Features, ",") {
if feature == "+thumb-mode" { if feature == "+thumb-mode" {
isThumb = true isThumb = true
} }
+6 -8
View File
@@ -7,7 +7,6 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"strings"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@@ -134,7 +133,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 { if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string // key is a string
params := []llvm.Value{m, key, valueAlloca} params := []llvm.Value{m, key, valueAlloca}
b.createRuntimeInvoke("hashmapStringSet", params, "") b.createRuntimeCall("hashmapStringSet", params, "")
} else { } else {
// Key stored at actual type. // Key stored at actual type.
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key") keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
@@ -144,7 +143,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
fnName = "hashmapGenericSet" fnName = "hashmapGenericSet"
} }
params := []llvm.Value{m, keyAlloca, valueAlloca} params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeInvoke(fnName, params, "") b.createRuntimeCall(fnName, params, "")
b.emitLifetimeEnd(keyAlloca, keySize) b.emitLifetimeEnd(keyAlloca, keySize)
} }
b.emitLifetimeEnd(valueAlloca, valueSize) b.emitLifetimeEnd(valueAlloca, valueSize)
@@ -294,15 +293,14 @@ func hashmapCanonicalTypeName(t types.Type) string {
} }
return t.String() return t.String()
case *types.Struct: case *types.Struct:
var s strings.Builder s := "struct{"
s.WriteString("struct{")
for i := 0; i < t.NumFields(); i++ { for i := 0; i < t.NumFields(); i++ {
if i > 0 { if i > 0 {
s.WriteString("; ") s += "; "
} }
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type())) s += hashmapCanonicalTypeName(t.Field(i).Type())
} }
return s.String() + "}" return s + "}"
case *types.Array: case *types.Array:
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem())) return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
} }
+2 -1
View File
@@ -29,7 +29,8 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
// is the largest of the values unsafe.Alignof(x.f) for each // is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1." // field f of x, but at least 1."
max := int64(1) max := int64(1)
for f := range t.Fields() { for i := 0; i < t.NumFields(); i++ {
f := t.Field(i)
if a := s.Alignof(f.Type()); a > max { if a := s.Alignof(f.Type()); a > max {
max = a max = a
} }
+26 -130
View File
@@ -8,8 +8,6 @@ import (
"go/ast" "go/ast"
"go/token" "go/token"
"go/types" "go/types"
"path/filepath"
"slices"
"strconv" "strconv"
"strings" "strings"
@@ -87,8 +85,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
retType = c.getLLVMType(fn.Signature.Results().At(0).Type()) retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
} else { } else {
results := make([]llvm.Type, 0, fn.Signature.Results().Len()) results := make([]llvm.Type, 0, fn.Signature.Results().Len())
for v := range fn.Signature.Results().Variables() { for i := 0; i < fn.Signature.Results().Len(); i++ {
results = append(results, c.getLLVMType(v.Type())) results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
} }
retType = c.ctx.StructType(results, false) retType = c.ctx.StructType(results, false)
} }
@@ -164,7 +162,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape": case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)) llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero": case "runtime.alloc", "runtime.alloc_noheap":
// Tell the optimizer that runtime.alloc is an allocator, meaning that it // Tell the optimizer that runtime.alloc is an allocator, meaning that it
// returns values that are never null and never alias to an existing value. // returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} { for _, attrName := range []string{"noalias", "nonnull"} {
@@ -324,12 +322,6 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
linkName: f.RelString(nil), linkName: f.RelString(nil),
} }
// RelString is not unique for local type arguments, so add a suffix
// when needed.
if suffix := c.localTypeArgsSuffix(f); suffix != "" {
info.linkName += suffix
}
// Check for a few runtime functions that are treated specially. // Check for a few runtime functions that are treated specially.
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" { if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
info.linkName = "_initialize" info.linkName = "_initialize"
@@ -354,42 +346,6 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
return info return info
} }
func (c *compilerContext) localTypeArgsSuffix(f *ssa.Function) string {
typeArgs := f.TypeArgs()
if len(typeArgs) == 0 {
return ""
}
var hasLocal bool
parts := make([]string, len(typeArgs))
for i, ta := range typeArgs {
name, isLocal := c.getTypeCodeName(ta)
if isLocal {
hasLocal = true
}
// A function-local type alias (e.g. `type F = float64` inside a
// function body) is invisible to getTypeCodeName because it calls
// types.Unalias first. Two callers that use distinct aliases with
// the same name (e.g. Go 1.27's internal/strconv.ftoa32 and ftoa64
// both declare a local `type F = ...`) then produce identical
// RelStrings for their shortFloat[F] instantiations and collide on
// mod.NamedFunction. Treat these aliases as local so the suffix
// disambiguates them.
if alias, ok := ta.(*types.Alias); ok {
if obj := alias.Obj(); obj.Pkg() != nil && obj.Parent() != obj.Pkg().Scope() {
hasLocal = true
pos := c.program.Fset.PositionFor(obj.Pos(), false)
parts[i] = fmt.Sprintf("%s$alias:%s:%d:%d", name, filepath.Base(pos.Filename), pos.Line, pos.Column)
continue
}
}
parts[i] = name
}
if !hasLocal {
return ""
}
return "$localtype:" + strings.Join(parts, ",")
}
// parsePragmas is used by getFunctionInfo to parse function pragmas such as // parsePragmas is used by getFunctionInfo to parse function pragmas such as
// //export or //go:noinline. // //export or //go:noinline.
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) { func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
@@ -416,51 +372,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
} }
} }
// Also scan file-level //go:linkname directives. These appear as
// free-standing comments in *ast.File.Comments (not attached to any
// declaration), and are used by modern golang.org/x/sys/unix and others.
// Function-attached directives (above) take precedence — we only add
// file-level ones if no doc-comment linkname was found for this function.
//
// TODO: the hasUnsafeImport gate enforced downstream (see the
// //go:linkname case below) is package-level. gc enforces it per
// file, on the file containing the directive. For file-level
// linknames this is more important than for function-attached ones,
// because the directive can live in a file separate from the
// function. A stricter implementation would check whether the file
// returned by fileForFunc imports "unsafe", not whether any file in
// the package does.
hasFunctionLinkname := false
for _, comment := range pragmas {
if strings.HasPrefix(comment.Text, "//go:linkname ") {
parts := strings.Fields(comment.Text)
if len(parts) == 3 && parts[1] == f.Name() {
hasFunctionLinkname = true
break
}
}
}
if !hasFunctionLinkname {
if file := c.fileForFunc(f); file != nil {
for _, group := range file.Comments {
// Skip the function's own doc comment — already handled above.
if decl, ok := syntax.(*ast.FuncDecl); ok && group == decl.Doc {
continue
}
for _, comment := range group.List {
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
continue
}
parts := strings.Fields(comment.Text)
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
pragmas = append(pragmas, comment)
}
}
}
}
// Parse each pragma. // Parse each pragma.
for _, comment := range pragmas { for _, comment := range pragmas {
parts := strings.Fields(comment.Text) parts := strings.Fields(comment.Text)
@@ -478,7 +389,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.wasmName = info.linkName info.wasmName = info.linkName
info.exported = true info.exported = true
case "//go:interrupt": case "//go:interrupt":
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) { if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true info.interrupt = true
} }
case "//go:wasm-module": case "//go:wasm-module":
@@ -540,14 +451,14 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
// This is a slightly looser requirement than what gc uses: gc // This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a // requires the file to import "unsafe", not the package as a
// whole. // whole.
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) { if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2] info.linkName = parts[2]
} }
case "//go:section": case "//go:section":
// Only enable go:section when the package imports "unsafe". // Only enable go:section when the package imports "unsafe".
// go:section also implies go:noinline since inlining could // go:section also implies go:noinline since inlining could
// move the code to a different section than that requested. // move the code to a different section than that requested.
if len(parts) == 2 && slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) { if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1] info.section = parts[1]
info.inline = inlineNone info.inline = inlineNone
} }
@@ -556,7 +467,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
// runtime functions. // runtime functions.
// This is somewhat dangerous and thus only imported in packages // This is somewhat dangerous and thus only imported in packages
// that import unsafe. // that import unsafe.
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) { if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true info.nobounds = true
} }
case "//go:noescape": case "//go:noescape":
@@ -656,8 +567,8 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
hasHostLayout = false // package structs added in go1.23 hasHostLayout = false // package structs added in go1.23
} }
} }
for field := range typ.Fields() { for i := 0; i < typ.NumFields(); i++ {
ftyp := field.Type() ftyp := typ.Field(i).Type()
if types.Unalias(ftyp).String() == "structs.HostLayout" { if types.Unalias(ftyp).String() == "structs.HostLayout" {
hasHostLayout = true hasHostLayout = true
continue continue
@@ -689,8 +600,8 @@ func getParams(sig *types.Signature) []*types.Var {
if sig.Recv() != nil { if sig.Recv() != nil {
params = append(params, sig.Recv()) params = append(params, sig.Recv())
} }
for v := range sig.Params().Variables() { for i := 0; i < sig.Params().Len(); i++ {
params = append(params, v) params = append(params, sig.Params().At(i))
} }
return params return params
} }
@@ -755,34 +666,6 @@ type globalInfo struct {
section string // go:section section string // go:section
} }
// fileForFunc returns the *ast.File that contains the declaration of f, or
// nil if it cannot be determined. File-level pragmas are only consulted for
// functions in the package currently being compiled — functions imported from
// other packages have their file-level pragmas processed when those packages
// are compiled.
func (c *compilerContext) fileForFunc(f *ssa.Function) *ast.File {
if c.loaderPkg == nil || f.Pkg == nil || f.Pkg.Pkg != c.loaderPkg.Pkg {
return nil
}
syntax := f.Syntax()
if f.Origin() != nil {
syntax = f.Origin().Syntax()
}
if syntax == nil {
return nil
}
pos := syntax.Pos()
if !pos.IsValid() {
return nil
}
for _, file := range c.loaderPkg.Files {
if file.FileStart <= pos && pos < file.FileEnd {
return file
}
}
return nil
}
// loadASTComments loads comments on globals from the AST, for use later in the // loadASTComments loads comments on globals from the AST, for use later in the
// program. In particular, they are required for //go:extern pragmas on globals. // program. In particular, they are required for //go:extern pragmas on globals.
func (c *compilerContext) loadASTComments(pkg *loader.Package) { func (c *compilerContext) loadASTComments(pkg *loader.Package) {
@@ -821,7 +704,10 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName) llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
// Set alignment from the //go:align comment. // Set alignment from the //go:align comment.
alignment := max(info.align, c.targetData.ABITypeAlignment(llvmType)) alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 { if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0). // Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360 // See: https://stackoverflow.com/a/108360
@@ -895,7 +781,7 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext,
// This is a slightly looser requirement than what gc uses: gc // This is a slightly looser requirement than what gc uses: gc
// requires the file to import "unsafe", not the package as a // requires the file to import "unsafe", not the package as a
// whole. // whole.
if slices.Contains(g.Pkg.Pkg.Imports(), types.Unsafe) { if hasUnsafeImport(g.Pkg.Pkg) {
info.linkName = parts[2] info.linkName = parts[2]
} }
} }
@@ -911,3 +797,13 @@ func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
} }
return methods return methods
} }
// Return true if this package imports "unsafe", false otherwise.
func hasUnsafeImport(pkg *types.Package) bool {
for _, imp := range pkg.Imports() {
if imp == types.Unsafe {
return true
}
}
return false
}
+27 -32
View File
@@ -26,25 +26,24 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{b.uintptrType} argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like: // Constraints will look something like:
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}" // "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
var constraints strings.Builder constraints := "={rax},0"
constraints.WriteString("={rax},0")
for i, arg := range call.Args[1:] { for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{ constraints += "," + [...]string{
"{rdi}", "{rdi}",
"{rsi}", "{rsi}",
"{rdx}", "{rdx}",
"{r10}", "{r10}",
"{r8}", "{r8}",
"{r9}", "{r9}",
}[i]) }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
// rcx and r11 are clobbered by the syscall, so make sure they are not used // rcx and r11 are clobbered by the syscall, so make sure they are not used
constraints.WriteString(",~{rcx},~{r11}") constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false) target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux": case b.GOARCH == "386" && b.GOOS == "linux":
@@ -56,23 +55,22 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{b.uintptrType} argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like: // Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}" // "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
var constraints strings.Builder constraints := "={eax},0"
constraints.WriteString("={eax},0")
for i, arg := range call.Args[1:] { for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{ constraints += "," + [...]string{
"{ebx}", "{ebx}",
"{ecx}", "{ecx}",
"{edx}", "{edx}",
"{esi}", "{esi}",
"{edi}", "{edi}",
"{ebp}", "{ebp}",
}[i]) }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false) target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux": case b.GOARCH == "arm" && b.GOOS == "linux":
@@ -90,10 +88,9 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
// Constraints will look something like: // Constraints will look something like:
// ={r0},0,{r1},{r2},{r7},~{r3} // ={r0},0,{r1},{r2},{r7},~{r3}
var constraints strings.Builder constraints := "={r0}"
constraints.WriteString("={r0}")
for i, arg := range call.Args[1:] { for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{ constraints += "," + [...]string{
"0", // tie to output "0", // tie to output
"{r1}", "{r1}",
"{r2}", "{r2}",
@@ -101,20 +98,20 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"{r4}", "{r4}",
"{r5}", "{r5}",
"{r6}", "{r6}",
}[i]) }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
args = append(args, num) args = append(args, num)
argTypes = append(argTypes, b.uintptrType) argTypes = append(argTypes, b.uintptrType)
constraints.WriteString(",{r7}") // syscall number constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ { for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns // r0-r3 get clobbered after the syscall returns
constraints.WriteString(",~{r" + strconv.Itoa(i) + "}") constraints += ",~{r" + strconv.Itoa(i) + "}"
} }
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux": case b.GOARCH == "arm64" && b.GOOS == "linux":
@@ -123,32 +120,31 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{} argTypes := []llvm.Type{}
// Constraints will look something like: // Constraints will look something like:
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17} // ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
var constraints strings.Builder constraints := "={x0}"
constraints.WriteString("={x0}")
for i, arg := range call.Args[1:] { for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{ constraints += "," + [...]string{
"0", // tie to output "0", // tie to output
"{x1}", "{x1}",
"{x2}", "{x2}",
"{x3}", "{x3}",
"{x4}", "{x4}",
"{x5}", "{x5}",
}[i]) }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
} }
args = append(args, num) args = append(args, num)
argTypes = append(argTypes, b.uintptrType) argTypes = append(argTypes, b.uintptrType)
constraints.WriteString(",{x8}") // syscall number constraints += ",{x8}" // syscall number
for i := len(call.Args) - 1; i < 8; i++ { for i := len(call.Args) - 1; i < 8; i++ {
// x0-x7 may get clobbered during the syscall following the aarch64 // x0-x7 may get clobbered during the syscall following the aarch64
// calling convention. // calling convention.
constraints.WriteString(",~{x" + strconv.Itoa(i) + "}") constraints += ",~{x" + strconv.Itoa(i) + "}"
} }
constraints.WriteString(",~{x16},~{x17}") // scratch registers constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false) fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false) target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil return b.CreateCall(fnType, target, args, ""), nil
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux": case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
@@ -167,8 +163,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// faster and smaller code. // faster and smaller code.
args := []llvm.Value{num} args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType} argTypes := []llvm.Type{b.uintptrType}
var constraints strings.Builder constraints := "={$2},={$7},0"
constraints.WriteString("={$2},={$7},0")
syscallParams := call.Args[1:] syscallParams := call.Args[1:]
if len(syscallParams) > 7 { if len(syscallParams) > 7 {
// There is one syscall that uses 7 parameters: sync_file_range. // There is one syscall that uses 7 parameters: sync_file_range.
@@ -177,7 +172,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallParams = syscallParams[:7] syscallParams = syscallParams[:7]
} }
for i, arg := range syscallParams { for i, arg := range syscallParams {
constraints.WriteString("," + [...]string{ constraints += "," + [...]string{
"{$4}", // arg1 "{$4}", // arg1
"{$5}", // arg2 "{$5}", // arg2
"{$6}", // arg3 "{$6}", // arg3
@@ -185,7 +180,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"r", // arg5 on the stack "r", // arg5 on the stack
"r", // arg6 on the stack "r", // arg6 on the stack
"r", // arg7 on the stack "r", // arg7 on the stack
}[i]) }[i]
llvmValue := b.getValue(arg, getPos(call)) llvmValue := b.getValue(arg, getPos(call))
args = append(args, llvmValue) args = append(args, llvmValue)
argTypes = append(argTypes, llvmValue.Type()) argTypes = append(argTypes, llvmValue.Type())
@@ -226,10 +221,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"addu $$sp, $$sp, 32\n" + "addu $$sp, $$sp, 32\n" +
".set at\n" ".set at\n"
} }
constraints.WriteString(",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}") constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false) returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
fnType := llvm.FunctionType(returnType, argTypes, false) fnType := llvm.FunctionType(returnType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, true, 0, false) target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
call := b.CreateCall(fnType, target, args, "") call := b.CreateCall(fnType, target, args, "")
resultCode := b.CreateExtractValue(call, 0, "") // r2 resultCode := b.CreateExtractValue(call, 0, "") // r2
errorFlag := b.CreateExtractValue(call, 1, "") // r7 errorFlag := b.CreateExtractValue(call, 1, "") // r7
+17 -14
View File
@@ -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 datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv7m-unknown-unknown-eabi" target triple = "thumbv7m-unknown-unknown-eabi"
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr } %runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
%runtime._interface = type { ptr, ptr } %runtime._interface = type { ptr, ptr }
; Function Attrs: nounwind ; Function Attrs: nounwind
@@ -18,14 +18,14 @@ declare void @main.external(ptr) #1
define hidden void @main.deferSimple(ptr %context) unnamed_addr #0 { define hidden void @main.deferSimple(ptr %context) unnamed_addr #0 {
entry: entry:
%defer.alloca = alloca { i32, ptr }, align 4 %defer.alloca = alloca { i32, ptr }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0() %0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
%defer.next = load ptr, ptr %deferPtr, align 4
store i32 0, ptr %defer.alloca, align 4 store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 %defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr %defer.next, ptr %defer.alloca.repack15, align 4 store ptr null, ptr %defer.alloca.repack15, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
%setjmp.result = icmp eq i32 %setjmp, 0 %setjmp.result = icmp eq i32 %setjmp, 0
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
; Function Attrs: nocallback nofree nosync nounwind willreturn ; Function Attrs: nocallback nofree nosync nounwind willreturn
declare ptr @llvm.stacksave.p0() #2 declare ptr @llvm.stacksave.p0() #2
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1 declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #1
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1 declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #1
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 { define internal void @"main.deferSimple$1"(ptr %context) unnamed_addr #0 {
@@ -135,18 +135,18 @@ define hidden void @main.deferMultiple(ptr %context) unnamed_addr #0 {
entry: entry:
%defer.alloca2 = alloca { i32, ptr }, align 4 %defer.alloca2 = alloca { i32, ptr }, align 4
%defer.alloca = alloca { i32, ptr }, align 4 %defer.alloca = alloca { i32, ptr }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0() %0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
%defer.next = load ptr, ptr %deferPtr, align 4
store i32 0, ptr %defer.alloca, align 4 store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4 %defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr %defer.next, ptr %defer.alloca.repack22, align 4 store ptr null, ptr %defer.alloca.repack22, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4 store ptr %defer.alloca, ptr %deferPtr, align 4
store i32 1, ptr %defer.alloca2, align 4 store i32 1, ptr %defer.alloca2, align 4
%defer.alloca2.repack24 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4 %defer.alloca2.repack23 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
store ptr %defer.alloca, ptr %defer.alloca2.repack24, align 4 store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4
store ptr %defer.alloca2, ptr %deferPtr, align 4 store ptr %defer.alloca2, ptr %deferPtr, align 4
%setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5 %setjmp = call i32 asm "\0Amovs r0, #0\0Amov r2, pc\0Astr r2, [r1, #4]", "={r0},{r1},~{r1},~{r2},~{r3},~{r4},~{r5},~{r6},~{r7},~{r8},~{r9},~{r10},~{r11},~{r12},~{lr},~{q0},~{q1},~{q2},~{q3},~{q4},~{q5},~{q6},~{q7},~{q8},~{q9},~{q10},~{q11},~{q12},~{q13},~{q14},~{q15},~{cpsr},~{memory}"(ptr nonnull %deferframe.buf) #5
%setjmp.result = icmp eq i32 %setjmp, 0 %setjmp.result = icmp eq i32 %setjmp, 0
@@ -270,8 +270,9 @@ entry:
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #0 { define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #0 {
entry: entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0() %0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.body br label %for.body
@@ -317,8 +318,9 @@ declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.deferLoop(ptr %context) unnamed_addr #0 { define hidden void @main.deferLoop(ptr %context) unnamed_addr #0 {
entry: entry:
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0() %0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop br label %for.loop
@@ -406,8 +408,9 @@ rundefers.end1: ; preds = %rundefers.loophead4
define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #0 { define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #0 {
entry: entry:
%defer.alloca = alloca { i32, ptr, i32 }, align 4 %defer.alloca = alloca { i32, ptr, i32 }, align 4
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%deferframe.buf = alloca %runtime.deferFrame, align 4 %deferframe.buf = alloca %runtime.deferFrame, align 4
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
%0 = call ptr @llvm.stacksave.p0() %0 = call ptr @llvm.stacksave.p0()
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4 call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
br label %for.loop br label %for.loop
+1 -4
View File
@@ -75,7 +75,7 @@ entry:
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 { define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
entry: entry:
%stackalloc = alloca i8, align 1 %stackalloc = alloca i8, align 1
%new = call align 1 ptr @runtime.alloc_zero(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
store ptr %new, ptr @main.struct1, align 4 store ptr %new, ptr @main.struct1, align 4
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -93,9 +93,6 @@ entry:
ret void ret void
} }
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 { define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
entry: entry:
+60 -30
View File
@@ -29,13 +29,13 @@ entry:
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
store float %a.X, ptr %a, align 4 store float %a.X, ptr %a, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4 %a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
store float %a.Y, ptr %a.repack5, align 4 store float %a.Y, ptr %a.repack9, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
store float %b.X, ptr %b, align 4 store float %b.X, ptr %b, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4 %b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
store float %b.Y, ptr %b.repack7, align 4 store float %b.Y, ptr %b.repack11, align 4
call void @main.checkSize(i32 4, ptr undef) #3 call void @main.checkSize(i32 4, ptr undef) #3
call void @main.checkSize(i32 8, ptr undef) #3 call void @main.checkSize(i32 8, ptr undef) #3
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -43,29 +43,29 @@ entry:
br i1 false, label %deref.throw, label %deref.next br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry deref.next: ; preds = %entry
br i1 false, label %deref.throw, label %deref.next1 br i1 false, label %deref.throw1, label %deref.next2
deref.next1: ; preds = %deref.next deref.next2: ; preds = %deref.next
%0 = load float, ptr %a, align 4 %0 = load float, ptr %a, align 4
%1 = load float, ptr %b, align 4 %1 = load float, ptr %b, align 4
%2 = fadd float %0, %1 %2 = fadd float %0, %1
br i1 false, label %deref.throw, label %deref.next2 br i1 false, label %deref.throw3, label %deref.next4
deref.next2: ; preds = %deref.next1 deref.next4: ; preds = %deref.next2
br i1 false, label %deref.throw, label %deref.next3 br i1 false, label %deref.throw5, label %deref.next6
deref.next3: ; preds = %deref.next2 deref.next6: ; preds = %deref.next4
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4 %3 = getelementptr inbounds nuw i8, ptr %b, i32 4
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4 %4 = getelementptr inbounds nuw i8, ptr %a, i32 4
%5 = load float, ptr %4, align 4 %5 = load float, ptr %4, align 4
%6 = load float, ptr %3, align 4 %6 = load float, ptr %3, align 4
br i1 false, label %deref.throw, label %store.next br i1 false, label %store.throw, label %store.next
store.next: ; preds = %deref.next3 store.next: ; preds = %deref.next6
store float %2, ptr %complit, align 4 store float %2, ptr %complit, align 4
br i1 false, label %deref.throw, label %store.next4 br i1 false, label %store.throw7, label %store.next8
store.next4: ; preds = %store.next store.next8: ; preds = %store.next
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 %7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
%8 = fadd float %5, %6 %8 = fadd float %5, %6
store float %8, ptr %7, align 4 store float %8, ptr %7, align 4
@@ -74,7 +74,22 @@ store.next4: ; preds = %store.next
%10 = insertvalue %"main.Point[float32]" %9, float %8, 1 %10 = insertvalue %"main.Point[float32]" %9, float %8, 1
ret %"main.Point[float32]" %10 ret %"main.Point[float32]" %10
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry deref.throw: ; preds = %entry
unreachable
deref.throw1: ; preds = %deref.next
unreachable
deref.throw3: ; preds = %deref.next2
unreachable
deref.throw5: ; preds = %deref.next4
unreachable
store.throw: ; preds = %deref.next6
unreachable
store.throw7: ; preds = %store.next
unreachable unreachable
} }
@@ -92,13 +107,13 @@ entry:
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
store i32 %a.X, ptr %a, align 4 store i32 %a.X, ptr %a, align 4
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4 %a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
store i32 %a.Y, ptr %a.repack5, align 4 store i32 %a.Y, ptr %a.repack9, align 4
%b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %b = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3 call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
store i32 %b.X, ptr %b, align 4 store i32 %b.X, ptr %b, align 4
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4 %b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
store i32 %b.Y, ptr %b.repack7, align 4 store i32 %b.Y, ptr %b.repack11, align 4
call void @main.checkSize(i32 4, ptr undef) #3 call void @main.checkSize(i32 4, ptr undef) #3
call void @main.checkSize(i32 8, ptr undef) #3 call void @main.checkSize(i32 8, ptr undef) #3
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3 %complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
@@ -106,29 +121,29 @@ entry:
br i1 false, label %deref.throw, label %deref.next br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry deref.next: ; preds = %entry
br i1 false, label %deref.throw, label %deref.next1 br i1 false, label %deref.throw1, label %deref.next2
deref.next1: ; preds = %deref.next deref.next2: ; preds = %deref.next
%0 = load i32, ptr %a, align 4 %0 = load i32, ptr %a, align 4
%1 = load i32, ptr %b, align 4 %1 = load i32, ptr %b, align 4
%2 = add i32 %0, %1 %2 = add i32 %0, %1
br i1 false, label %deref.throw, label %deref.next2 br i1 false, label %deref.throw3, label %deref.next4
deref.next2: ; preds = %deref.next1 deref.next4: ; preds = %deref.next2
br i1 false, label %deref.throw, label %deref.next3 br i1 false, label %deref.throw5, label %deref.next6
deref.next3: ; preds = %deref.next2 deref.next6: ; preds = %deref.next4
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4 %3 = getelementptr inbounds nuw i8, ptr %b, i32 4
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4 %4 = getelementptr inbounds nuw i8, ptr %a, i32 4
%5 = load i32, ptr %4, align 4 %5 = load i32, ptr %4, align 4
%6 = load i32, ptr %3, align 4 %6 = load i32, ptr %3, align 4
br i1 false, label %deref.throw, label %store.next br i1 false, label %store.throw, label %store.next
store.next: ; preds = %deref.next3 store.next: ; preds = %deref.next6
store i32 %2, ptr %complit, align 4 store i32 %2, ptr %complit, align 4
br i1 false, label %deref.throw, label %store.next4 br i1 false, label %store.throw7, label %store.next8
store.next4: ; preds = %store.next store.next8: ; preds = %store.next
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4 %7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
%8 = add i32 %5, %6 %8 = add i32 %5, %6
store i32 %8, ptr %7, align 4 store i32 %8, ptr %7, align 4
@@ -137,7 +152,22 @@ store.next4: ; preds = %store.next
%10 = insertvalue %"main.Point[int]" %9, i32 %8, 1 %10 = insertvalue %"main.Point[int]" %9, i32 %8, 1
ret %"main.Point[int]" %10 ret %"main.Point[int]" %10
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry deref.throw: ; preds = %entry
unreachable
deref.throw1: ; preds = %deref.next
unreachable
deref.throw3: ; preds = %deref.next2
unreachable
deref.throw5: ; preds = %deref.next4
unreachable
store.throw: ; preds = %deref.next6
unreachable
store.throw7: ; preds = %store.next
unreachable unreachable
} }
-28
View File
@@ -120,31 +120,3 @@ func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
func doesHeapAlloc() *int { func doesHeapAlloc() *int {
return new(int) return new(int)
} }
// Define a function in a different package using a file-level go:linkname.
// (Same as withLinkageName1, but with the //go:linkname directive detached
// from the function declaration — see https://github.com/tinygo-org/tinygo/issues/4395)
func withFileLevelLinkageName1() {
}
// Import a function from a different package using a file-level go:linkname.
// (Same as withLinkageName2, but with the //go:linkname directive detached
// from the function declaration.)
func withFileLevelLinkageName2()
//go:linkname withFileLevelLinkageName1 somepkg.someFileLevelFunction1
//go:linkname withFileLevelLinkageName2 somepkg.someFileLevelFunction2
// File-level linkname directives can also appear between two function
// declarations, in which case Go's AST attaches them as the doc comment
// of the following function — even when the directive's localname refers
// to a different function. Exercise that case: the directive below names
// withAdjacentLinkageName, but Go will attach it to
// sentinelAfterAdjacentLinkname's Doc. The file-level scan must find it
// by walking comment groups regardless of which decl they're attached to.
func withAdjacentLinkageName() {
}
//go:linkname withAdjacentLinkageName somepkg.someAdjacentFunction
func sentinelAfterAdjacentLinkname() {
}
-20
View File
@@ -102,26 +102,6 @@ entry:
; Function Attrs: allockind("alloc,zeroed") allocsize(0) ; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9 declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
; Function Attrs: nounwind
define hidden void @somepkg.someFileLevelFunction1(ptr %context) unnamed_addr #1 {
entry:
ret void
}
declare void @somepkg.someFileLevelFunction2(ptr) #0
; Function Attrs: nounwind
define hidden void @somepkg.someAdjacentFunction(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @main.sentinelAfterAdjacentLinkname(ptr %context) unnamed_addr #1 {
entry:
ret void
}
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" } attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" } attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
+2
View File
@@ -59,6 +59,7 @@ func TestCorpus(t *testing.T) {
} }
for _, repo := range repos { for _, repo := range repos {
repo := repo
name := repo.Repo name := repo.Repo
if repo.Tags != "" { if repo.Tags != "" {
name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")" name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")"
@@ -131,6 +132,7 @@ func TestCorpus(t *testing.T) {
} }
for _, dir := range repo.Subdirs { for _, dir := range repo.Subdirs {
dir := dir
t.Run(dir.Pkg, func(t *testing.T) { t.Run(dir.Pkg, func(t *testing.T) {
t.Parallel() t.Parallel()
+5 -2
View File
@@ -116,7 +116,10 @@ func Diff(oldName string, old []byte, newName string, new []byte) []byte {
// End chunk with common lines for context. // End chunk with common lines for context.
if len(ctext) > 0 { if len(ctext) > 0 {
n := min(end.x-start.x, C) n := end.x - start.x
if n > C {
n = C
}
for _, s := range x[start.x : start.x+n] { for _, s := range x[start.x : start.x+n] {
ctext = append(ctext, " "+s) ctext = append(ctext, " "+s)
count.x++ count.x++
@@ -231,7 +234,7 @@ func tgs(x, y []string) []pair {
for i := range T { for i := range T {
T[i] = n + 1 T[i] = n + 1
} }
for i := range n { for i := 0; i < n; i++ {
k := sort.Search(n, func(k int) bool { k := sort.Search(n, func(k int) bool {
return T[k] >= J[i] return T[k] >= J[i]
}) })
+1 -1
View File
@@ -136,7 +136,7 @@ func readErrorMessages(t *testing.T, file string) string {
} }
var errors []string var errors []string
for line := range strings.SplitSeq(string(data), "\n") { for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "// ERROR: ") { if strings.HasPrefix(line, "// ERROR: ") {
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n")) errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
} }
+7 -7
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo module github.com/tinygo-org/tinygo
go 1.24.0 go 1.23.0
require ( require (
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139 github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
@@ -18,11 +18,11 @@ require (
go.bug.st/serial v1.6.4 go.bug.st/serial v1.6.4
go.bytecodealliance.org v0.6.2 go.bytecodealliance.org v0.6.2
go.bytecodealliance.org/cm v0.2.2 go.bytecodealliance.org/cm v0.2.2
golang.org/x/net v0.50.0 golang.org/x/net v0.35.0
golang.org/x/sys v0.41.0 golang.org/x/sys v0.30.0
golang.org/x/tools v0.42.0 golang.org/x/tools v0.30.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
tinygo.org/x/espflasher v0.6.1 tinygo.org/x/espflasher v0.6.0
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
) )
@@ -48,6 +48,6 @@ require (
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect github.com/ulikunitz/xz v0.5.12 // indirect
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
golang.org/x/mod v0.33.0 // indirect golang.org/x/mod v0.23.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.22.0 // indirect
) )
+14 -16
View File
@@ -23,8 +23,6 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs= github.com/golangci/misspell v0.6.0 h1:JCle2HUTNWirNlDIAUO44hUsKhOFqGPoC4LZxlaSXDs=
github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo= github.com/golangci/misspell v0.6.0/go.mod h1:keMNyY6R9isGaSAu+4Q8NMBwMPkh15Gtc8UCVoDtAWo=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
@@ -95,24 +93,24 @@ go.bytecodealliance.org v0.6.2 h1:Jy4u5DVmSkXgsnwojBhJ+AD/YsJsR3VzVnxF0xRCqTQ=
go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA= go.bytecodealliance.org v0.6.2/go.mod h1:gqjTJm0y9NSksG4py/lSjIQ/SNuIlOQ+hCIEPQwtJgA=
go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA= go.bytecodealliance.org/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI= go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.11.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-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-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
@@ -120,7 +118,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs= tinygo.org/x/espflasher v0.6.0 h1:CHbGMHAIWq1tB8FKd/QwBpNFw1jvHHxpmvZiF+QOYUo=
tinygo.org/x/espflasher v0.6.1/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U= tinygo.org/x/espflasher v0.6.0/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU= tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6 h1:QSnqFgNV2Ij0T4hM2qKv53fcDAFElxClPjVUZXzYkWU=
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0= tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
-2
View File
@@ -151,8 +151,6 @@ func Get(name string) string {
panic("could not find cache dir: " + err.Error()) panic("could not find cache dir: " + err.Error())
} }
return filepath.Join(dir, "tinygo") return filepath.Join(dir, "tinygo")
case "CGO_CFLAGS":
return os.Getenv("CGO_CFLAGS")
case "CGO_ENABLED": case "CGO_ENABLED":
// Always enable CGo. It is required by a number of targets, including // Always enable CGo. It is required by a number of targets, including
// macOS and the rp2040. // macOS and the rp2040.
+1 -1
View File
@@ -151,7 +151,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
case llvm.PHI: case llvm.PHI:
inst.name = llvmInst.Name() inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount() incomingCount := inst.llvmInst.IncomingCount()
for i := range incomingCount { for i := 0; i < incomingCount; i++ {
incomingBB := inst.llvmInst.IncomingBlock(i) incomingBB := inst.llvmInst.IncomingBlock(i)
incomingValue := inst.llvmInst.IncomingValue(i) incomingValue := inst.llvmInst.IncomingValue(i)
inst.operands = append(inst.operands, inst.operands = append(inst.operands,
+1 -2
View File
@@ -18,10 +18,9 @@ func TestInterp(t *testing.T) {
"copy", "copy",
"interface", "interface",
"revert", "revert",
"store",
"alloc", "alloc",
"slicedata",
} { } {
name := name // make local to this closure
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
t.Parallel() t.Parallel()
runTest(t, "testdata/"+name) runTest(t, "testdata/"+name)
+5 -6
View File
@@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -300,7 +299,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// means that monotonic time in the time package is counted from // means that monotonic time in the time package is counted from
// time.Time{}.Sub(1), which should be fine. // time.Time{}.Sub(1), which should be fine.
locals[inst.localIndex] = literalValue{uint64(0)} locals[inst.localIndex] = literalValue{uint64(0)}
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap" || callFn.name == "runtime.alloc_zero": case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
// Allocate heap memory. At compile time, this is instead done // Allocate heap memory. At compile time, this is instead done
// by creating a global variable. // by creating a global variable.
@@ -471,7 +470,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
// should be returned. // should be returned.
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue()) numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
var method llvm.Value var method llvm.Value
for i := range numMethods { for i := 0; i < numMethods; i++ {
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "") methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "") methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
if methodSignature == signature { if methodSignature == signature {
@@ -908,7 +907,7 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error { func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
numOperands := inst.llvmInst.OperandsCount() numOperands := inst.llvmInst.OperandsCount()
operands := make([]llvm.Value, numOperands) operands := make([]llvm.Value, numOperands)
for i := range numOperands { for i := 0; i < numOperands; i++ {
operand := inst.llvmInst.Operand(i) operand := inst.llvmInst.Operand(i)
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() { if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
var err error var err error
@@ -986,9 +985,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
mem.instructions = append(mem.instructions, agg) mem.instructions = append(mem.instructions, agg)
} }
result = operands[1] result = operands[1]
for i, indice := range slices.Backward(indices) { for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i] agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indice), inst.name+".insertvalue"+strconv.Itoa(i)) result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
if i != 0 { // don't add last result to mem.instructions as it will be done at the end already if i != 0 { // don't add last result to mem.instructions as it will be done at the end already
mem.instructions = append(mem.instructions, result) mem.instructions = append(mem.instructions, result)
} }
+25 -46
View File
@@ -18,10 +18,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"math/big" "math/big"
"slices"
"strconv" "strconv"
"strings" "strings"
@@ -82,7 +80,9 @@ func (mv *memoryView) extend(sub memoryView) {
if mv.objects == nil && len(sub.objects) != 0 { if mv.objects == nil && len(sub.objects) != 0 {
mv.objects = make(map[uint32]object) mv.objects = make(map[uint32]object)
} }
maps.Copy(mv.objects, sub.objects) for key, value := range sub.objects {
mv.objects[key] = value
}
mv.instructions = append(mv.instructions, sub.instructions...) mv.instructions = append(mv.instructions, sub.instructions...)
} }
@@ -90,8 +90,8 @@ func (mv *memoryView) extend(sub memoryView) {
// created in this memoryView. Do not reuse this memoryView. // created in this memoryView. Do not reuse this memoryView.
func (mv *memoryView) revert() { func (mv *memoryView) revert() {
// Erase instructions in reverse order. // Erase instructions in reverse order.
for _, llvmInst := range slices.Backward(mv.instructions) { for i := len(mv.instructions) - 1; i >= 0; i-- {
llvmInst := mv.instructions[i]
if llvmInst.IsAInstruction().IsNil() { if llvmInst.IsAInstruction().IsNil() {
// The IR builder will try to create constant versions of // The IR builder will try to create constant versions of
// instructions whenever possible. If it does this, it's not an // instructions whenever possible. If it does this, it's not an
@@ -172,7 +172,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
continue continue
} }
numOperands := inst.OperandsCount() numOperands := inst.OperandsCount()
for i := range numOperands { for i := 0; i < numOperands; i++ {
// Using mark '2' (which means read/write access) // Using mark '2' (which means read/write access)
// because this might be a store instruction. // because this might be a store instruction.
err := mv.markExternal(inst.Operand(i), 2) err := mv.markExternal(inst.Operand(i), 2)
@@ -215,7 +215,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
// need any marking. // need any marking.
case llvm.StructTypeKind: case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount() numElements := llvmType.StructElementTypesCount()
for i := range numElements { for i := 0; i < numElements; i++ {
element := mv.r.builder.CreateExtractValue(llvmValue, i, "") element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
err := mv.markExternal(element, mark) err := mv.markExternal(element, mark)
if err != nil { if err != nil {
@@ -224,7 +224,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
} }
case llvm.ArrayTypeKind: case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength() numElements := llvmType.ArrayLength()
for i := range numElements { for i := 0; i < numElements; i++ {
element := mv.r.builder.CreateExtractValue(llvmValue, i, "") element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
err := mv.markExternal(element, mark) err := mv.markExternal(element, mark)
if err != nil { if err != nil {
@@ -299,8 +299,7 @@ func (mv *memoryView) put(index uint32, obj object) {
} }
// Load the value behind the given pointer. Returns nil if the pointer points to // Load the value behind the given pointer. Returns nil if the pointer points to
// an external global or if the load is out of bounds of the object (in which // an external global.
// case the caller defers the load to runtime).
func (mv *memoryView) load(p pointerValue, size uint32) value { func (mv *memoryView) load(p pointerValue, size uint32) value {
if checks && mv.hasExternalStore(p) { if checks && mv.hasExternalStore(p) {
panic("interp: load from object with external store") panic("interp: load from object with external store")
@@ -313,23 +312,14 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
if p.offset() == 0 && size == obj.size { if p.offset() == 0 && size == obj.size {
return obj.buffer.clone() return obj.buffer.clone()
} }
if p.offset()+size > obj.size { if checks && p.offset()+size > obj.size {
// The load is out of bounds of the object. This can happen for valid panic("interp: load out of bounds")
// Go programs, for example when dereferencing the pointer returned by
// unsafe.SliceData on a zero-capacity slice (which points to a
// zero-sized object). Return nil so the caller defers this load to
// runtime instead of crashing the compiler.
return nil
} }
v := obj.buffer.asRawValue(mv.r) v := obj.buffer.asRawValue(mv.r)
loadedBuf := v.buf[p.offset() : p.offset()+size] loadedValue := rawValue{
if _, writable := mv.objects[p.index()]; writable { buf: v.buf[p.offset() : p.offset()+size],
// This object's buffer is owned by this view, which means a later
// store may mutate it in place (see store below). Copy the loaded
// slice so the returned value is not aliased with the live buffer.
loadedBuf = append([]uint64(nil), loadedBuf...)
} }
return rawValue{buf: loadedBuf} return loadedValue
} }
// Store to the value behind the given pointer. This overwrites the value in the // Store to the value behind the given pointer. This overwrites the value in the
@@ -340,37 +330,26 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
if checks && mv.hasExternalLoadOrStore(p) { if checks && mv.hasExternalLoadOrStore(p) {
panic("interp: store to object with external load/store") panic("interp: store to object with external load/store")
} }
index := p.index() obj := mv.get(p.index())
var obj object
writable := false
if mv.objects != nil {
obj, writable = mv.objects[index]
}
if !writable {
obj = mv.get(index)
}
if obj.buffer == nil { if obj.buffer == nil {
// External global, return false (for a failure). // External global, return false (for a failure).
return false return false
} }
valueLen := v.len(mv.r) if checks && p.offset()+v.len(mv.r) > obj.size {
if checks && p.offset()+valueLen > obj.size {
panic("interp: store out of bounds") panic("interp: store out of bounds")
} }
if p.offset() == 0 && valueLen == obj.buffer.len(mv.r) { if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) {
obj.buffer = v.clone() obj.buffer = v
} else { } else {
if !writable {
obj = obj.clone() obj = obj.clone()
}
buffer := obj.buffer.asRawValue(mv.r) buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer obj.buffer = buffer
v := v.asRawValue(mv.r) v := v.asRawValue(mv.r)
for i := range valueLen { for i := uint32(0); i < v.len(mv.r); i++ {
buffer.buf[p.offset()+i] = v.buf[i] buffer.buf[p.offset()+i] = v.buf[i]
} }
} }
mv.put(index, obj) mv.put(p.index(), obj)
return true // success return true // success
} }
@@ -392,7 +371,7 @@ type value interface {
// literalValue contains simple integer values that don't need to be stored in a // literalValue contains simple integer values that don't need to be stored in a
// buffer. // buffer.
type literalValue struct { type literalValue struct {
value any value interface{}
} }
// Make a literalValue given the number of bits. // Make a literalValue given the number of bits.
@@ -1015,7 +994,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
for i := range ptrSize { for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptr.pointer v.buf[i] = ptr.pointer
} }
} else if !llvmValue.IsAConstantExpr().IsNil() { } else if !llvmValue.IsAConstantExpr().IsNil() {
@@ -1056,7 +1035,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
panic(err) panic(err)
} }
ptrValue.pointer += totalOffset ptrValue.pointer += totalOffset
for i := range ptrSize { for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptrValue.pointer v.buf[i] = ptrValue.pointer
} }
case llvm.ICmp: case llvm.ICmp:
@@ -1113,7 +1092,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
} }
case llvm.StructTypeKind: case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount() numElements := llvmType.StructElementTypesCount()
for i := range numElements { for i := 0; i < numElements; i++ {
offset := r.targetData.ElementOffset(llvmType, i) offset := r.targetData.ElementOffset(llvmType, i)
field := rawValue{ field := rawValue{
buf: v.buf[offset:], buf: v.buf[offset:],
@@ -1124,7 +1103,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
numElements := llvmType.ArrayLength() numElements := llvmType.ArrayLength()
childType := llvmType.ElementType() childType := llvmType.ElementType()
childTypeSize := r.targetData.TypeAllocSize(childType) childTypeSize := r.targetData.TypeAllocSize(childType)
for i := range numElements { for i := 0; i < numElements; i++ {
offset := i * int(childTypeSize) offset := i * int(childTypeSize)
field := rawValue{ field := rawValue{
buf: v.buf[offset:], buf: v.buf[offset:],
-23
View File
@@ -1,23 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
; Reproduction of https://github.com/tinygo-org/tinygo/issues/4214.
; Dereferencing the pointer returned by unsafe.SliceData on a zero-capacity
; slice produces a load that is out of bounds of a zero-sized object. The
; interp must defer this load to runtime instead of crashing the compiler.
@main.zeroSized = global {} zeroinitializer
@main.v = global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
call void @main.init(ptr undef)
ret void
}
define internal void @main.init(ptr %context) unnamed_addr {
entry:
%val = load i64, ptr @main.zeroSized
store i64 %val, ptr @main.v
ret void
}
-12
View File
@@ -1,12 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@main.zeroSized = local_unnamed_addr global {} zeroinitializer
@main.v = local_unnamed_addr global i64 0
define void @runtime.initAll() unnamed_addr {
entry:
%val = load i64, ptr @main.zeroSized, align 8
store i64 %val, ptr @main.v, align 8
ret void
}
-49
View File
@@ -1,49 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@overlap.buf = global [4 x i8] c"\01\02\03\04"
@alias.src = global [4 x i8] c"\05\06\07\08"
@alias.dst = global [2 x i8] zeroinitializer
@reload.buf = global [4 x i8] c"\01\02\03\04"
@reload.out = global [2 x i8] zeroinitializer
define void @runtime.initAll() unnamed_addr {
entry:
call void @overlap.init(ptr undef)
call void @alias.init(ptr undef)
call void @reload.init(ptr undef)
ret void
}
define internal void @overlap.init(ptr %context) unnamed_addr {
entry:
%tail = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 3
store i8 9, ptr %tail
%val = load i16, ptr @overlap.buf
%dst = getelementptr [4 x i8], ptr @overlap.buf, i32 0, i32 1
store i16 %val, ptr %dst
ret void
}
define internal void @alias.init(ptr %context) unnamed_addr {
entry:
%src = getelementptr [4 x i8], ptr @alias.src, i32 0, i32 1
%val = load i16, ptr %src
store i16 %val, ptr @alias.dst
store i8 9, ptr @alias.dst
ret void
}
define internal void @reload.init(ptr %context) unnamed_addr {
entry:
; First store makes reload.buf writable in the current memory view.
%tail = getelementptr [4 x i8], ptr @reload.buf, i32 0, i32 3
store i8 9, ptr %tail
; Partial load whose result may share the underlying buffer.
%val = load i16, ptr @reload.buf
; Subsequent in-place partial store; this must not corrupt %val.
store i8 99, ptr @reload.buf
; Write the originally-loaded value to a separate global.
store i16 %val, ptr @reload.out
ret void
}
-13
View File
@@ -1,13 +0,0 @@
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64--linux"
@overlap.buf = local_unnamed_addr global [4 x i8] c"\01\01\02\09"
@alias.src = local_unnamed_addr global [4 x i8] c"\05\06\07\08"
@alias.dst = local_unnamed_addr global [2 x i8] c"\09\07"
@reload.buf = local_unnamed_addr global [4 x i8] c"c\02\03\09"
@reload.out = local_unnamed_addr global [2 x i8] c"\01\02"
define void @runtime.initAll() unnamed_addr {
entry:
ret void
}
-1
View File
@@ -246,7 +246,6 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
"internal/futex/": false, "internal/futex/": false,
"internal/fuzz/": false, "internal/fuzz/": false,
"internal/itoa/": false, "internal/itoa/": false,
"internal/poll/": false,
"internal/reflectlite/": false, "internal/reflectlite/": false,
"internal/gclayout": false, "internal/gclayout": false,
"internal/task/": false, "internal/task/": false,
+4 -8
View File
@@ -22,12 +22,13 @@ import (
"strings" "strings"
"unicode" "unicode"
"github.com/google/shlex"
"github.com/tinygo-org/tinygo/cgo" "github.com/tinygo-org/tinygo/cgo"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
"github.com/tinygo-org/tinygo/goenv" "github.com/tinygo-org/tinygo/goenv"
) )
var initFileVersions = func(info *types.Info) {}
// Program holds all packages and some metadata about the program as a whole. // Program holds all packages and some metadata about the program as a whole.
type Program struct { type Program struct {
config *compileopts.Config config *compileopts.Config
@@ -434,7 +435,7 @@ func (p *Package) Check() error {
} }
checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor) checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
} }
p.info.FileVersions = make(map[*ast.File]string) initFileVersions(&p.info)
// Do typechecking of the package. // Do typechecking of the package.
packageName := p.ImportPath packageName := p.ImportPath
@@ -484,7 +485,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
if !filepath.IsAbs(file) { if !filepath.IsAbs(file) {
file = filepath.Join(p.Dir, file) file = filepath.Join(p.Dir, file)
} }
f, err := p.parseFile(file, parser.ParseComments|parser.SkipObjectResolution) f, err := p.parseFile(file, parser.ParseComments)
if err != nil { if err != nil {
fileErrs = append(fileErrs, err) fileErrs = append(fileErrs, err)
return return
@@ -507,11 +508,6 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
var initialCFlags []string var initialCFlags []string
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...) initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
initialCFlags = append(initialCFlags, "-I"+p.Dir) initialCFlags = append(initialCFlags, "-I"+p.Dir)
cgoCFlags, err := shlex.Split(goenv.Get("CGO_CFLAGS"))
if err != nil {
return nil, fmt.Errorf("failed to split CGO_CFLAGS: %w", err)
}
initialCFlags = append(initialCFlags, cgoCFlags...)
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.program.config.GOOS())
p.CFlags = append(initialCFlags, cflags...) p.CFlags = append(initialCFlags, cflags...)
p.CGoHeaders = headerCode p.CGoHeaders = headerCode
+17
View File
@@ -0,0 +1,17 @@
//go:build go1.22
// types.Info.FileVersions was added in Go 1.22, so we can only initialize it
// when built with Go 1.22.
package loader
import (
"go/ast"
"go/types"
)
func init() {
initFileVersions = func(info *types.Info) {
info.FileVersions = make(map[*ast.File]string)
}
}
+14 -48
View File
@@ -209,8 +209,6 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
// Test runs the tests in the given package. Returns whether the test passed and // Test runs the tests in the given package. Returns whether the test passed and
// possibly an error if the test failed to run. // possibly an error if the test failed to run.
func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) { func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) {
optionsCopy := *options
options = &optionsCopy
options.TestConfig.CompileTestBinary = true options.TestConfig.CompileTestBinary = true
config, err := builder.NewConfig(options) config, err := builder.NewConfig(options)
if err != nil { if err != nil {
@@ -397,7 +395,7 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
fileExt = filepath.Ext(config.Target.FlashFilename) fileExt = filepath.Ext(config.Target.FlashFilename)
case "openocd": case "openocd":
fileExt = ".hex" fileExt = ".hex"
case "bmp", "probe-rs": case "bmp":
fileExt = ".elf" fileExt = ".elf"
case "adb": case "adb":
fileExt = ".hex" fileExt = ".hex"
@@ -537,16 +535,6 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
if err != nil { if err != nil {
return &commandError{"failed to flash", result.Binary, err} return &commandError{"failed to flash", result.Binary, err}
} }
case "probe-rs":
// TODO: this halts the target after flashing.
// See: https://github.com/probe-rs/probe-rs/discussions/4005
cmd := executeCommand(config.Options, "probe-rs", "download", "--chip="+config.Target.ProbeRSChip, "--verify", result.Binary)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return &commandError{"failed to flash", result.Binary, err}
}
case "adb": case "adb":
// Run pre-flash adb shell commands. // Run pre-flash adb shell commands.
for _, preCmd := range config.Target.ADBPreCommands { for _, preCmd := range config.Target.ADBPreCommands {
@@ -709,21 +697,6 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
daemon.Stdout = w daemon.Stdout = w
daemon.Stderr = w daemon.Stderr = w
} }
case "probe-rs":
port = ":1337"
gdbCommands = append(gdbCommands, "monitor halt", "load", "monitor reset halt")
daemon = executeCommand(config.Options, "probe-rs", "gdb", "--chip="+config.Target.ProbeRSChip)
if ocdOutput {
// Make it clear which output is from the daemon.
w := &ColorWriter{
Out: colorable.NewColorableStderr(),
Prefix: "probe-rs: ",
Color: TermColorYellow,
}
daemon.Stdout = w
daemon.Stderr = w
}
case "jlink": case "jlink":
port = ":2331" port = ":2331"
gdbCommands = append(gdbCommands, "load", "monitor reset halt") gdbCommands = append(gdbCommands, "load", "monitor reset halt")
@@ -786,15 +759,15 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
} }
defer func() { defer func() {
daemon.Process.Signal(os.Interrupt) daemon.Process.Signal(os.Interrupt)
var stopped atomic.Uint32 var stopped uint32
go func() { go func() {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
if stopped.Load() == 0 { if atomic.LoadUint32(&stopped) == 0 {
daemon.Process.Kill() daemon.Process.Kill()
} }
}() }()
daemon.Wait() daemon.Wait()
stopped.Store(1) atomic.StoreUint32(&stopped, 1)
}() }()
} }
@@ -1046,7 +1019,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
func touchSerialPortAt1200bps(port string) (err error) { func touchSerialPortAt1200bps(port string) (err error) {
retryCount := 3 retryCount := 3
for range retryCount { for i := 0; i < retryCount; i++ {
// Open port // Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200}) p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil { if e != nil {
@@ -1244,7 +1217,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("could not list mount points: %w", err) return nil, fmt.Errorf("could not list mount points: %w", err)
} }
for line := range strings.SplitSeq(string(tab), "\n") { for _, line := range strings.Split(string(tab), "\n") {
fields := strings.Fields(line) fields := strings.Fields(line)
if len(fields) <= 2 { if len(fields) <= 2 {
continue continue
@@ -1273,7 +1246,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
} }
// Extract data to convert to a []mountPoint slice. // Extract data to convert to a []mountPoint slice.
for line := range strings.SplitSeq(out.String(), "\n") { for _, line := range strings.Split(out.String(), "\n") {
words := strings.Fields(line) words := strings.Fields(line)
if len(words) < 3 { if len(words) < 3 {
continue continue
@@ -1685,18 +1658,18 @@ func (m globalValuesFlag) String() string {
} }
func (m globalValuesFlag) Set(value string) error { func (m globalValuesFlag) Set(value string) error {
before, after, ok := strings.Cut(value, "=") equalsIndex := strings.IndexByte(value, '=')
if !ok { if equalsIndex < 0 {
return errors.New("expected format pkgpath.Var=value") return errors.New("expected format pkgpath.Var=value")
} }
pathAndName := before pathAndName := value[:equalsIndex]
pointIndex := strings.LastIndexByte(pathAndName, '.') pointIndex := strings.LastIndexByte(pathAndName, '.')
if pointIndex < 0 { if pointIndex < 0 {
return errors.New("expected format pkgpath.Var=value") return errors.New("expected format pkgpath.Var=value")
} }
path := pathAndName[:pointIndex] path := pathAndName[:pointIndex]
name := pathAndName[pointIndex+1:] name := pathAndName[pointIndex+1:]
stringValue := after stringValue := value[equalsIndex+1:]
if m[path] == nil { if m[path] == nil {
m[path] = make(map[string]string) m[path] = make(map[string]string)
} }
@@ -1780,7 +1753,6 @@ func main() {
printSize := flag.String("size", "", "print sizes (none, short, full, html)") printSize := flag.String("size", "", "print sizes (none, short, full, html)")
printStacks := flag.Bool("print-stacks", false, "print stack sizes of goroutines") 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") printAllocsString := flag.String("print-allocs", "", "regular expression of functions for which heap allocations should be printed")
printAllocsCoverString := flag.String("print-allocs-cover", "", "like -print-allocs, but in go coverage tool format")
printCommands := flag.Bool("x", false, "Print commands") printCommands := flag.Bool("x", false, "Print commands")
flagJSON := flag.Bool("json", false, "print output in JSON format") flagJSON := flag.Bool("json", false, "print output in JSON format")
parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel") parallelism := flag.Int("p", runtime.GOMAXPROCS(0), "the number of build jobs that can run in parallel")
@@ -1861,14 +1833,8 @@ func main() {
} }
var printAllocs *regexp.Regexp var printAllocs *regexp.Regexp
printAllocsCover := false if *printAllocsString != "" {
printAllocsPattern := *printAllocsString printAllocs, err = regexp.Compile(*printAllocsString)
if *printAllocsCoverString != "" {
printAllocsPattern = *printAllocsCoverString
printAllocsCover = true
}
if printAllocsPattern != "" {
printAllocs, err = regexp.Compile(printAllocsPattern)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
@@ -1916,7 +1882,6 @@ func main() {
PrintSizes: *printSize, PrintSizes: *printSize,
PrintStacks: *printStacks, PrintStacks: *printStacks,
PrintAllocs: printAllocs, PrintAllocs: printAllocs,
PrintAllocsCover: printAllocsCover,
Tags: []string(tags), Tags: []string(tags),
TestConfig: testConfig, TestConfig: testConfig,
GlobalValues: globalVarValues, GlobalValues: globalVarValues,
@@ -2054,6 +2019,7 @@ func main() {
// This uses an additional semaphore to reduce the memory usage. // This uses an additional semaphore to reduce the memory usage.
testSema := make(chan struct{}, cap(options.Semaphore)) testSema := make(chan struct{}, cap(options.Semaphore))
for i, pkgName := range explicitPkgNames { for i, pkgName := range explicitPkgNames {
pkgName := pkgName
buf := &bufs[i] buf := &bufs[i]
testSema <- struct{}{} testSema <- struct{}{}
wg.Add(1) wg.Add(1)
+21 -96
View File
@@ -46,6 +46,15 @@ var supportedLinuxArches = map[string]string{
"WASIp1": "wasip1/wasm", "WASIp1": "wasip1/wasm",
} }
func init() {
major, _, _ := goenv.GetGorootVersion()
if major < 21 {
// Go 1.20 backwards compatibility.
// Should be removed once we drop support for Go 1.20.
delete(supportedLinuxArches, "WASIp1")
}
}
var sema = make(chan struct{}, runtime.NumCPU()) var sema = make(chan struct{}, runtime.NumCPU())
func TestBuild(t *testing.T) { func TestBuild(t *testing.T) {
@@ -67,8 +76,6 @@ func TestBuild(t *testing.T) {
"init_multi.go", "init_multi.go",
"interface.go", "interface.go",
"json.go", "json.go",
"localtypes/",
"localtypes.go",
"map.go", "map.go",
"map_bigkey.go", "map_bigkey.go",
"math.go", "math.go",
@@ -245,21 +252,6 @@ func TestBuild(t *testing.T) {
} }
} }
// TestTimerStopResetRace checks that stopping or resetting a timer while its
// callback is running behaves correctly. It reaches into the runtime timer
// hooks via //go:linkname and relies on the threads scheduler, which is only
// used on these hosts.
func TestTimerStopResetRace(t *testing.T) {
t.Parallel()
switch runtime.GOOS {
case "darwin", "linux":
default:
t.Skipf("host GOOS %s does not use the threads scheduler", runtime.GOOS)
}
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options) emuCheck(t, options)
@@ -398,7 +390,7 @@ func emuCheck(t *testing.T, options compileopts.Options) {
t.Fatal("failed to load target spec:", err) t.Fatal("failed to load target spec:", err)
} }
if spec.Emulator != "" { if spec.Emulator != "" {
emulatorCommand, _, _ := strings.Cut(spec.Emulator, " ") emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
_, err := exec.LookPath(emulatorCommand) _, err := exec.LookPath(emulatorCommand)
if err != nil { if err != nil {
if errors.Is(err, exec.ErrNotFound) { if errors.Is(err, exec.ErrNotFound) {
@@ -473,12 +465,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
options.Directory = path options.Directory = path
pkgName = "." pkgName = "."
} }
isWebAssembly := strings.HasPrefix(options.Target, "wasi") ||
strings.HasPrefix(options.Target, "wasm") ||
strings.HasPrefix(options.GOARCH, "wasm")
if name == "testing.go" && isWebAssembly {
expectedOutputPath = TESTDATA + "/testing-wasm.txt"
}
config, err := builder.NewConfig(&options) config, err := builder.NewConfig(&options)
if err != nil { if err != nil {
@@ -488,18 +474,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
// Build the test binary. // Build the test binary.
stdout := &bytes.Buffer{} stdout := &bytes.Buffer{}
_, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, 2*time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { _, err = buildAndRun(pkgName, config, stdout, cmdArgs, environmentVars, 2*time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
if config.EmulatorName() == "simavr" {
// simavr before v1.8 wrote firmware output to stderr and loader logs
// to stdout, but PR #490 swapped these streams:
// https://github.com/buserror/simavr/pull/490
cmd.Stdout = stdout
}
return cmd.Run() return cmd.Run()
}) })
if err != nil { if err != nil {
w := &bytes.Buffer{} w := &bytes.Buffer{}
diagnostics.CreateDiagnostics(err).WriteTo(w, "") diagnostics.CreateDiagnostics(err).WriteTo(w, "")
for line := range strings.SplitSeq(strings.TrimRight(w.String(), "\n"), "\n") { for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
t.Log(line) t.Log(line)
} }
if stdout.Len() != 0 { if stdout.Len() != 0 {
@@ -511,7 +491,11 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
actual := stdout.Bytes() actual := stdout.Bytes()
if config.EmulatorName() == "simavr" { if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual) // Strip simavr log formatting.
actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1)
actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1)
} }
if name == "testing.go" { if name == "testing.go" {
// Strip actual time. // Strip actual time.
@@ -538,28 +522,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
} }
} }
func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{'.', '.', '\n'}, []byte{'\n'})
output = bytes.ReplaceAll(output, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'})
var cleaned []byte
for _, line := range bytes.SplitAfter(output, []byte{'\n'}) {
trimmedLine := bytes.TrimRight(line, "\r\n")
if simavrLoadTextLogPattern.Match(trimmedLine) || simavrLoadBytesLogPattern.Match(trimmedLine) {
continue
}
cleaned = append(cleaned, line...)
}
return cleaned
}
var (
simavrLoadTextLogPattern = regexp.MustCompile(`^Loaded [0-9]+ \.[A-Za-z0-9_]+( at address 0x[0-9a-fA-F]+)?$`)
simavrLoadBytesLogPattern = regexp.MustCompile(`^Loaded [0-9]+ bytes of [A-Za-z]+ data at (0x)?[0-9a-fA-F]+$`)
)
// Test WebAssembly files for certain properties. // Test WebAssembly files for certain properties.
func TestWebAssembly(t *testing.T) { func TestWebAssembly(t *testing.T) {
t.Parallel() t.Parallel()
@@ -575,6 +537,7 @@ func TestWebAssembly(t *testing.T) {
{name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}}, {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-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}},
} { } {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
tmpdir := t.TempDir() tmpdir := t.TempDir()
@@ -696,6 +659,7 @@ func TestWasmExport(t *testing.T) {
} }
for _, tc := range tests { for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
@@ -865,6 +829,7 @@ func TestWasmExportJS(t *testing.T) {
{name: "c-shared", buildMode: "c-shared"}, {name: "c-shared", buildMode: "c-shared"},
} }
for _, tc := range tests { for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
// Build the wasm binary. // Build the wasm binary.
@@ -912,6 +877,7 @@ func TestWasmExit(t *testing.T) {
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"}, {name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
} }
for _, tc := range tests { for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
t.Parallel() t.Parallel()
options := optionsFromTarget("wasm", sema) options := optionsFromTarget("wasm", sema)
@@ -954,48 +920,6 @@ func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
} }
} }
func TestGoexitCrash(t *testing.T) {
t.Parallel()
options := optionsFromTarget("", sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
want string
}{
{"main", "all goroutines are asleep - deadlock!"},
{"deadlock", "all goroutines are asleep - deadlock!"},
{"exit", "all goroutines are asleep - deadlock!"},
{"main-other", "all goroutines are asleep - deadlock!"},
{"in-panic", "all goroutines are asleep - deadlock!"},
{"panic", "panic: panic after Goexit"},
{"recovered-panic", "all goroutines are asleep - deadlock!"},
{"recover-before-panic", "all goroutines are asleep - deadlock!"},
{"recover-before-panic-loop", "all goroutines are asleep - deadlock!"},
} {
t.Run(tc.name, func(t *testing.T) {
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/goexit.go", config, output, []string{tc.name}, nil, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error {
cmd.Stdout = nil
cmd.Stderr = nil
data, err := cmd.CombinedOutput()
output.Write(data)
return err
})
if err == nil {
t.Fatal("program unexpectedly exited successfully")
}
if !strings.Contains(output.String(), tc.want) {
t.Fatalf("output does not contain %q:\n%s", tc.want, output.String())
}
})
}
}
func TestTest(t *testing.T) { func TestTest(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1028,6 +952,7 @@ func TestTest(t *testing.T) {
) )
} }
for _, targ := range targs { for _, targ := range targs {
targ := targ
t.Run(targ.name, func(t *testing.T) { t.Run(targ.name, func(t *testing.T) {
t.Parallel() t.Parallel()
+1 -1
View File
@@ -278,7 +278,7 @@ func ListSerialPorts() ([]SerialPortInfo, error) {
return serialPortInfo, nil return serialPortInfo, nil
} }
var addressMatch = regexp.MustCompile(`panic: runtime error at 0x([0-9a-f]+): `) var addressMatch = regexp.MustCompile(`^panic: runtime error at 0x([0-9a-f]+): `)
// Extract the address from the "panic: runtime error at" message. // Extract the address from the "panic: runtime error at" message.
func extractPanicAddress(line []byte) uint64 { func extractPanicAddress(line []byte) uint64 {
+1 -1
View File
@@ -20,7 +20,7 @@ type reader struct {
func (r *reader) Read(b []byte) (n int, err error) { func (r *reader) Read(b []byte) (n int, err error) {
if len(b) != 0 { if len(b) != 0 {
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(b)), uint(len(b))) libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
} }
return len(b), nil return len(b), nil
} }
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt) //go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
// If you update the above build constraint, you'll probably also need to update // If you update the above build constraint, you'll probably also need to update
// src/runtime/rand_hwrng.go. // src/runtime/rand_hwrng.go.
+1 -1
View File
@@ -25,7 +25,7 @@ func (r *reader) Read(b []byte) (n int, err error) {
// See for example: https://github.com/golang/go/issues/33542 // See for example: https://github.com/golang/go/issues/33542
// For Windows 7 and newer, we might switch to ProcessPrng in the future // For Windows 7 and newer, we might switch to ProcessPrng in the future
// (which is a documented function and might be a tiny bit faster). // (which is a documented function and might be a tiny bit faster).
ok := libc_RtlGenRandom(unsafe.Pointer(unsafe.SliceData(b)), len(b)) ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b))
if !ok { if !ok {
return 0, errRandom return 0, errRandom
} }
-107
View File
@@ -1,107 +0,0 @@
// TINYGO: cipher suite IDs and the exported CipherSuites/InsecureCipherSuites
// descriptors, copied verbatim from the Go official implementation. TinyGo has
// no software handshake, so only these declarations (no selection machinery)
// are provided to satisfy callers such as google.golang.org/grpc/credentials
// and github.com/pion/dtls that reference the cipher-suite API surface.
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
// A list of cipher suite IDs that are, or have been, implemented by this
// package.
//
// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
const (
// TLS 1.0 - 1.2 cipher suites.
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca8
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
// TLS 1.3 cipher suites.
TLS_AES_128_GCM_SHA256 uint16 = 0x1301
TLS_AES_256_GCM_SHA384 uint16 = 0x1302
TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
// that the client is doing version fallback. See RFC 7507.
TLS_FALLBACK_SCSV uint16 = 0x5600
// Legacy names for the corresponding cipher suites with the correct _SHA256
// suffix, retained for backward compatibility.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
)
var (
supportedUpToTLS12 = []uint16{VersionTLS10, VersionTLS11, VersionTLS12}
supportedOnlyTLS12 = []uint16{VersionTLS12}
supportedOnlyTLS13 = []uint16{VersionTLS13}
)
// CipherSuites returns a list of cipher suites currently implemented by this
// package, excluding those with security issues, which are returned by
// InsecureCipherSuites.
func CipherSuites() []*CipherSuite {
return []*CipherSuite{
{TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
{TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
{TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
}
}
// InsecureCipherSuites returns a list of cipher suites currently implemented by
// this package and which have security issues.
//
// Most applications should not use the cipher suites in this list, and should
// only use those returned by CipherSuites.
func InsecureCipherSuites() []*CipherSuite {
// This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher
// suites. See cipherSuitesPreferenceOrder for details.
return []*CipherSuite{
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, true},
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
}
}
-119
View File
@@ -55,16 +55,6 @@ func VersionName(version uint16) string {
// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7. // only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7.
type CurveID uint16 type CurveID uint16
const (
CurveP256 CurveID = 23
CurveP384 CurveID = 24
CurveP521 CurveID = 25
X25519 CurveID = 29
X25519MLKEM768 CurveID = 4588
SecP256r1MLKEM768 CurveID = 4587
SecP384r1MLKEM1024 CurveID = 4589
)
// CipherSuiteName returns the standard name for the passed cipher suite ID // CipherSuiteName returns the standard name for the passed cipher suite ID
// //
// Not Implemented. // Not Implemented.
@@ -78,42 +68,14 @@ type ConnectionState struct {
// //
// Minimum (empty) fields for fortio.org/log http logging and others // Minimum (empty) fields for fortio.org/log http logging and others
// to compile and run. // to compile and run.
Version uint16
ServerName string
PeerCertificates []*x509.Certificate PeerCertificates []*x509.Certificate
CipherSuite uint16 CipherSuite uint16
NegotiatedProtocol string
NegotiatedProtocolIsMutual bool // Deprecated: this value is always true.
} }
// ClientAuthType declares the policy the server will follow for // ClientAuthType declares the policy the server will follow for
// TLS Client Authentication. // TLS Client Authentication.
type ClientAuthType int type ClientAuthType int
const (
// NoClientCert indicates that no client certificate should be requested
// during the handshake, and if any certificates are sent they will not
// be verified.
NoClientCert ClientAuthType = iota
// RequestClientCert indicates that a client certificate should be requested
// during the handshake, but does not require that the client send any
// certificates.
RequestClientCert
// RequireAnyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one certificate is required to be
// sent by the client, but that certificate is not required to be valid.
RequireAnyClientCert
// VerifyClientCertIfGiven indicates that a client certificate should be requested
// during the handshake, but does not require that the client sends a
// certificate. If the client does send a certificate it is required to be
// valid.
VerifyClientCertIfGiven
// RequireAndVerifyClientCert indicates that a client certificate should be requested
// during the handshake, and that at least one valid certificate is required
// to be sent by the client.
RequireAndVerifyClientCert
)
// ClientSessionCache is a cache of ClientSessionState objects that can be used // ClientSessionCache is a cache of ClientSessionState objects that can be used
// by a client to resume a TLS session with a given server. ClientSessionCache // by a client to resume a TLS session with a given server. ClientSessionCache
// implementations should expect to be called concurrently from different // implementations should expect to be called concurrently from different
@@ -138,45 +100,6 @@ type ClientSessionCache interface {
// RFC 8446, Section 4.2.3. // RFC 8446, Section 4.2.3.
type SignatureScheme uint16 type SignatureScheme uint16
const (
// RSASSA-PKCS1-v1_5 algorithms.
PKCS1WithSHA256 SignatureScheme = 0x0401
PKCS1WithSHA384 SignatureScheme = 0x0501
PKCS1WithSHA512 SignatureScheme = 0x0601
// RSASSA-PSS algorithms with public key OID rsaEncryption.
PSSWithSHA256 SignatureScheme = 0x0804
PSSWithSHA384 SignatureScheme = 0x0805
PSSWithSHA512 SignatureScheme = 0x0806
// ECDSA algorithms. Only constrained to a specific curve in TLS 1.3.
ECDSAWithP256AndSHA256 SignatureScheme = 0x0403
ECDSAWithP384AndSHA384 SignatureScheme = 0x0503
ECDSAWithP521AndSHA512 SignatureScheme = 0x0603
// EdDSA algorithms.
Ed25519 SignatureScheme = 0x0807
// Legacy signature and hash algorithms for TLS 1.2.
PKCS1WithSHA1 SignatureScheme = 0x0201
ECDSAWithSHA1 SignatureScheme = 0x0203
)
// CipherSuite is a TLS cipher suite. Note that most functions in this package
// accept and expose cipher suite IDs instead of this type.
type CipherSuite struct {
ID uint16
Name string
// Supported versions is the list of TLS protocol versions that can
// negotiate this cipher suite.
SupportedVersions []uint16
// Insecure is true if the cipher suite has known security issues
// due to its primitives, design, or implementation.
Insecure bool
}
// ClientHelloInfo contains information from a ClientHello message in order to // ClientHelloInfo contains information from a ClientHello message in order to
// guide application logic in the GetCertificate and GetConfigForClient callbacks. // guide application logic in the GetCertificate and GetConfigForClient callbacks.
type ClientHelloInfo struct { type ClientHelloInfo struct {
@@ -536,48 +459,6 @@ type Config struct {
autoSessionTicketKeys []ticketKey autoSessionTicketKeys []ticketKey
} }
// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a
// Config that is being used concurrently by a TLS client or server.
func (c *Config) Clone() *Config {
if c == nil {
return nil
}
c.mutex.RLock()
defer c.mutex.RUnlock()
return &Config{
Rand: c.Rand,
Time: c.Time,
Certificates: c.Certificates,
NameToCertificate: c.NameToCertificate,
GetCertificate: c.GetCertificate,
GetClientCertificate: c.GetClientCertificate,
GetConfigForClient: c.GetConfigForClient,
VerifyPeerCertificate: c.VerifyPeerCertificate,
VerifyConnection: c.VerifyConnection,
RootCAs: c.RootCAs,
NextProtos: c.NextProtos,
ServerName: c.ServerName,
ClientAuth: c.ClientAuth,
ClientCAs: c.ClientCAs,
InsecureSkipVerify: c.InsecureSkipVerify,
CipherSuites: c.CipherSuites,
PreferServerCipherSuites: c.PreferServerCipherSuites,
SessionTicketsDisabled: c.SessionTicketsDisabled,
SessionTicketKey: c.SessionTicketKey,
ClientSessionCache: c.ClientSessionCache,
UnwrapSession: c.UnwrapSession,
WrapSession: c.WrapSession,
MinVersion: c.MinVersion,
MaxVersion: c.MaxVersion,
CurvePreferences: c.CurvePreferences,
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
Renegotiation: c.Renegotiation,
KeyLogWriter: c.KeyLogWriter,
sessionTicketKeys: c.sessionTicketKeys,
autoSessionTicketKeys: c.autoSessionTicketKeys,
}
}
// ticketKey is the internal representation of a session ticket key. // ticketKey is the internal representation of a session ticket key.
type ticketKey struct { type ticketKey struct {
aesKey [16]byte aesKey [16]byte
+3 -42
View File
@@ -20,52 +20,13 @@ import (
"net" "net"
) )
// Conn represents a secured connection. TINYGO: the actual TLS handshake and
// record layer are offloaded to the network device (see net.TLSConn), so Conn
// is a thin wrapper over an underlying net.Conn that exists to satisfy callers
// (e.g. google.golang.org/grpc/credentials) which expect the *tls.Conn API
// shape — ConnectionState/Handshake — that this package does not implement in
// software.
type Conn struct {
net.Conn
}
// ConnectionState returns basic TLS details about the connection. TINYGO:
// empty; TLS is offloaded to the network device.
func (c *Conn) ConnectionState() ConnectionState {
return ConnectionState{}
}
// Handshake runs the client or server handshake protocol if it has not yet been
// run. TINYGO: no-op; the handshake is performed by the network device.
func (c *Conn) Handshake() error {
return c.HandshakeContext(context.Background())
}
// HandshakeContext is the context-aware variant of Handshake. TINYGO: no-op.
func (c *Conn) HandshakeContext(ctx context.Context) error {
return nil
}
// NetConn returns the underlying connection that is wrapped by c.
func (c *Conn) NetConn() net.Conn {
return c.Conn
}
// Client returns a new TLS client side connection // Client returns a new TLS client side connection
// using conn as the underlying transport. // using conn as the underlying transport.
// The config cannot be nil: users must set either ServerName or // The config cannot be nil: users must set either ServerName or
// InsecureSkipVerify in the config. // InsecureSkipVerify in the config.
func Client(conn net.Conn, config *Config) *Conn { func Client(conn net.Conn, config *Config) *net.TLSConn {
return &Conn{Conn: conn} panic("tls.Client() not implemented")
} return nil
// Server returns a new TLS server side connection
// using conn as the underlying transport.
// The configuration config must be non-nil and must include
// at least one certificate or else set GetCertificate.
func Server(conn net.Conn, config *Config) *Conn {
return &Conn{Conn: conn}
} }
// A listener implements a network listener (net.Listener) for TLS connections. // A listener implements a network listener (net.Listener) for TLS connections.
-74
View File
@@ -1,74 +0,0 @@
//go:build amd64
package amd64
const (
CPUIDTimeStampCounter = 0x15
CPUIDProcessorFrequency = 0x16
)
type CPUExtendedFamily uint16
const (
CPUFamilyIntelCore CPUExtendedFamily = 6
)
//export asmPause
func AsmPause()
//export asmReadRdtsc
func AsmReadRdtsc() uint64
//export asmCpuid
func AsmCpuid(index uint32, registerEax *uint32, registerEbx *uint32, registerEcx *uint32) int
var maxCpuidIndex uint32
var stdVendorName0 uint32
var stdCpuid1Eax uint32
func init() {
AsmCpuid(0, &maxCpuidIndex, &stdVendorName0, nil)
AsmCpuid(1, &stdCpuid1Eax, nil, nil)
}
func getExtendedCPUFamily() CPUExtendedFamily {
family := CPUExtendedFamily((stdCpuid1Eax >> 8) & 0x0f)
family += CPUExtendedFamily((stdCpuid1Eax >> 20) & 0xff)
return family
}
func isIntel() bool {
return stdVendorName0 == 0x756e6547
}
func isIntelFamilyCore() bool {
return isIntel() && getExtendedCPUFamily() == CPUFamilyIntelCore
}
func InternalGetPerformanceCounterFrequency() uint64 {
if maxCpuidIndex >= CPUIDTimeStampCounter {
return cpuidCoreClockCalculateTSCFrequency()
}
return 0
}
func cpuidCoreClockCalculateTSCFrequency() uint64 {
var eax uint32
var ebx uint32
var ecx uint32
AsmCpuid(CPUIDTimeStampCounter, &eax, &ebx, &ecx)
if eax == 0 || ebx == 0 {
return 0
}
coreCrystalFrequency := uint64(ecx)
if coreCrystalFrequency == 0 {
if !isIntelFamilyCore() {
return 0
}
coreCrystalFrequency = 24000000
}
return ((coreCrystalFrequency * uint64(ebx)) + (uint64(eax) / 2)) / uint64(eax)
}
-39
View File
@@ -1,39 +0,0 @@
.section .text
.global asmPause
asmPause:
pause
ret
.global asmReadRdtsc
asmReadRdtsc:
rdtsc
shlq $0x20, %rdx
orq %rdx, %rax
ret
.global asmCpuid
asmCpuid:
pushq %rbx
mov %ecx, %eax
pushq %rax
pushq %rdx
cpuid
test %r9, %r9
jz .SkipEcx
mov %ecx, (%r9)
.SkipEcx:
popq %rcx
jrcxz .SkipEax
mov %eax, (%rcx)
.SkipEax:
mov %r8, %rcx
jrcxz .SkipEbx
mov %ebx, (%rcx)
.SkipEbx:
popq %rax
popq %rbx
ret
-66
View File
@@ -1,66 +0,0 @@
// This is a very minimal bootloader for the ESP32-C6. It only initializes the
// flash and then continues with the generic RISC-V initialization code, which
// in turn will call runtime.main.
// It is written in assembly (and not in a higher level language) to make sure
// it is entirely loaded into IRAM and doesn't accidentally call functions
// stored in IROM.
//
// The ESP32-C6 has a unified IRAM/DRAM address space at 0x40800000, and
// separate DROM (0x42800000) / IROM (0x42000000) flash-mapped regions.
.section .init
.global call_start_cpu0
.type call_start_cpu0,@function
call_start_cpu0:
// At this point:
// - The ROM bootloader is finished and has jumped to here.
// - We're running from IRAM: both IRAM and DRAM segments have been loaded
// by the ROM bootloader.
// - We have a usable stack (but not the one we would like to use).
// - No flash mappings (MMU) are set up yet.
// Reset MMU, see bootloader_reset_mmu in the ESP-IDF.
call Cache_Suspend_ICache
mv s0, a0 // autoload value
call Cache_Invalidate_ICache_All
call Cache_MMU_Init
// Set up flash mapping (both IROM and DROM).
// On ESP32-C6, Cache_Dbus_MMU_Set is replaced by Cache_MSPI_MMU_Set
// which has an extra "sensitive" parameter.
// C equivalent:
// Cache_MSPI_MMU_Set(0, 0, 0x42000000, 0, 64, 256, 0)
// Maps 16MB starting at 0x42000000, covering both IROM and DROM.
li a0, 0 // sensitive: no flash encryption
li a1, 0 // ext_ram: MMU_ACCESS_FLASH
li a2, 0x42000000 // vaddr: start of flash-mapped region
li a3, 0 // paddr: physical address in the flash chip
li a4, 64 // psize: always 64 (kilobytes)
li a5, 256 // num: pages (16MB / 64K = 256, covers IROM+DROM)
li a6, 0 // fixed
call Cache_MSPI_MMU_Set
// Enable the flash cache.
mv a0, s0 // restore autoload value from Cache_Suspend_ICache call
call Cache_Resume_ICache
// Jump to generic RISC-V initialization, which initializes the stack
// pointer and globals register. It should not return.
j _start
.section .text.exception_vectors
.global _vector_table
.type _vector_table,@function
_vector_table:
.option push
.option norvc
.rept 32
j handleInterruptASM /* interrupt handler */
.endr
.option pop
.size _vector_table, .-_vector_table
-60
View File
@@ -85,9 +85,6 @@ var (
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134))) SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200))) INTERRUPT = (*INTERRUPT_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0200)))
// BIOS interrupt flags
BIOS_IF = (*volatile.Register16)(unsafe.Pointer(uintptr(0x03007FF8)))
) )
// Main memory sections // Main memory sections
@@ -828,60 +825,3 @@ const (
OAMOBJ_ATT2_PB_Pos = 0xC OAMOBJ_ATT2_PB_Pos = 0xC
OAMOBJ_ATT2_PB_Msk = 0xF OAMOBJ_ATT2_PB_Msk = 0xF
) )
// Constants for SOUND: sound control
const (
SSW_INC = 0x0 // Increasing sweep rate
SSW_DEC = 0x0008 // Decreasing sweep rate
SSW_OFF = 0x0008 // Disable sweep altogether
SSQR_DUTY1_8 = 0x0 // 12.5% duty cycle (#-------)
SSQR_DUTY1_4 = 0x0040 // 25% duty cycle (##------)
SSQR_DUTY1_2 = 0x0080 // 50% duty cycle (####----)
SSQR_DUTY3_4 = 0x00C0 // 75% duty cycle (######--) Equivalent to 25%
SSQR_INC = 0x0 // Increasing volume
SSQR_DEC = 0x0800 // Decreasing volume
SFREQ_HOLD = 0x0 // Continuous play
SFREQ_TIMED = 0x4000 // Timed play
SFREQ_RESET = 0x8000 // Reset sound
SDMG_SQR1 = 0x01
SDMG_SQR2 = 0x02
SDMG_WAVE = 0x04
SDMG_NOISE = 0x08
SDMG_LSQR1 = 0x0100 // Enable channel 1 on left
SDMG_LSQR2 = 0x0200 // Enable channel 2 on left
SDMG_LWAVE = 0x0400 // Enable channel 3 on left
SDMG_LNOISE = 0x0800 // Enable channel 4 on left
SDMG_RSQR1 = 0x1000 // Enable channel 1 on right
SDMG_RSQR2 = 0x2000 // Enable channel 2 on right
SDMG_RWAVE = 0x4000 // Enable channel 3 on right
SDMG_RNOISE = 0x8000 // Enable channel 4 on right
SDS_DMG25 = 0x0 // Tone generators at 25% volume
SDS_DMG50 = 0x0001 // Tone generators at 50% volume
SDS_DMG100 = 0x0002 // Tone generators at 100% volume
SDS_A50 = 0x0 // Direct Sound A at 50% volume
SDS_A100 = 0x0004 // Direct Sound A at 100% volume
SDS_B50 = 0x0 // Direct Sound B at 50% volume
SDS_B100 = 0x0008 // Direct Sound B at 100% volume
SDS_AR = 0x0100 // Enable Direct Sound A on right
SDS_AL = 0x0200 // Enable Direct Sound A on left
SDS_ATMR0 = 0x0 // Direct Sound A to use timer 0
SDS_ATMR1 = 0x0400 // Direct Sound A to use timer 1
SDS_ARESET = 0x0800 // Reset FIFO of Direct Sound A
SDS_BR = 0x1000 // Enable Direct Sound B on right
SDS_BL = 0x2000 // Enable Direct Sound B on left
SDS_BTMR0 = 0x0 // Direct Sound B to use timer 0
SDS_BTMR1 = 0x4000 // Direct Sound B to use timer 1
SDS_BRESET = 0x8000 // Reset FIFO of Direct Sound B
SSTAT_SQR1 = 0x0001 // (R) Channel 1 status
SSTAT_SQR2 = 0x0002 // (R) Channel 2 status
SSTAT_WAVE = 0x0004 // (R) Channel 3 status
SSTAT_NOISE = 0x0008 // (R) Channel 4 status
SSTAT_DISABLE = 0 // Disable sound
SSTAT_ENABLE = 0x0080 // Enable sound. NOTE: enable before using any other sound regs
)
-17
View File
@@ -1,17 +0,0 @@
//go:build i386 || amd64
package uefi
import "device/amd64"
func Ticks() uint64 {
return amd64.AsmReadRdtsc()
}
func CpuPause() {
amd64.AsmPause()
}
func getTSCFrequency() uint64 {
return amd64.InternalGetPerformanceCounterFrequency()
}
-201
View File
@@ -1,201 +0,0 @@
.section .text
.global uefiCall0
uefiCall0:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
callq *%rcx
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall1
uefiCall1:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall2
uefiCall2:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall3
uefiCall3:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall4
uefiCall4:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall5
uefiCall5:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall6
uefiCall6:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall7
uefiCall7:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall8
uefiCall8:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall9
uefiCall9:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall10
uefiCall10:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
movq 0x60(%rbp), %r10
movq %r10, 0x48(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global ___chkstk_ms
___chkstk_ms:
ret
-45
View File
@@ -1,45 +0,0 @@
package uefi
//go:nosplit
//export uefiCall0
func UefiCall0(fn uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall1
func UefiCall1(fn uintptr, a uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall2
func UefiCall2(fn uintptr, a uintptr, b uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall3
func UefiCall3(fn uintptr, a uintptr, b uintptr, c uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall4
func UefiCall4(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall5
func UefiCall5(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall6
func UefiCall6(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall7
func UefiCall7(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall8
func UefiCall8(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall9
func UefiCall9(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall10
func UefiCall10(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr, j uintptr) EFI_STATUS
-77
View File
@@ -1,77 +0,0 @@
package uefi
import (
"unicode/utf16"
"unsafe"
)
// StringToCHAR16 converts a Go string to a UTF-16 code unit slice.
func StringToCHAR16(s string) []CHAR16 {
if s == "" {
return nil
}
encoded := utf16.Encode([]rune(s))
out := make([]CHAR16, len(encoded))
for i, r := range encoded {
out[i] = CHAR16(r)
}
return out
}
// StringToCHAR16Z converts a Go string to a NUL-terminated UTF-16 code unit slice.
func StringToCHAR16Z(s string) []CHAR16 {
out := StringToCHAR16(s)
return append(out, 0)
}
// BytesToCHAR16 converts UTF-8 text bytes to a UTF-16 code unit slice.
func BytesToCHAR16(b []byte) []CHAR16 {
return StringToCHAR16(string(b))
}
// BytesToCHAR16Z converts UTF-8 text bytes to a NUL-terminated UTF-16 code unit slice.
func BytesToCHAR16Z(b []byte) []CHAR16 {
return StringToCHAR16Z(string(b))
}
// CHAR16ToString converts a UTF-16 code unit slice to a Go string.
func CHAR16ToString(input []CHAR16) string {
if len(input) == 0 {
return ""
}
units := make([]uint16, len(input))
for i, c := range input {
units[i] = uint16(c)
}
return string(utf16.Decode(units))
}
// CHAR16ToBytes converts a UTF-16 code unit slice to UTF-8 text bytes.
func CHAR16ToBytes(input []CHAR16) []byte {
return []byte(CHAR16ToString(input))
}
// CHAR16PtrToString converts a NUL-terminated UTF-16 string pointer to a Go string.
func CHAR16PtrToString(input *CHAR16) string {
if input == nil {
return ""
}
ptr := uintptr(unsafe.Pointer(input))
length := 0
for *(*CHAR16)(unsafe.Pointer(ptr)) != 0 {
length++
ptr += 2
}
return CHAR16PtrLenToString(input, length)
}
// CHAR16PtrLenToString converts a UTF-16 string pointer with a known code unit count to a Go string.
func CHAR16PtrLenToString(input *CHAR16, length int) string {
if input == nil || length <= 0 {
return ""
}
return CHAR16ToString(unsafe.Slice(input, length))
}
-39
View File
@@ -1,39 +0,0 @@
//go:build uefi
package uefi
import "sync"
var calibrateMutex sync.Mutex
var calculatedFrequency uint64
func TicksFrequency() uint64 {
frequency := getTSCFrequency()
if frequency > 0 {
return frequency
}
calibrateMutex.Lock()
defer calibrateMutex.Unlock()
if calculatedFrequency > 0 {
return calculatedFrequency
}
var event EFI_EVENT
var index UINTN
if BS().CreateEvent(EVT_TIMER, TPL_CALLBACK, nil, nil, &event) != EFI_SUCCESS {
return 0
}
defer BS().CloseEvent(event)
start := Ticks()
if BS().SetTimer(event, TimerPeriodic, 250*10000) != EFI_SUCCESS {
return 0
}
if BS().WaitForEvent(1, &event, &index) != EFI_SUCCESS {
return 0
}
calculatedFrequency = (Ticks() - start) * 4
return calculatedFrequency
}
-66
View File
@@ -1,66 +0,0 @@
package uefi
type UINTN uintptr
type EFI_STATUS UINTN
type EFI_TPL UINTN
type EFI_HANDLE uintptr
type EFI_EVENT uintptr
type EFI_PHYSICAL_ADDRESS uint64
type CHAR16 uint16
type BOOLEAN bool
type VOID byte
type EFI_GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
type EFI_TABLE_HEADER struct {
Signature uint64
Revision uint32
HeaderSize uint32
CRC32 uint32
Reserved uint32
}
type EFI_ALLOCATE_TYPE int
const (
AllocateAnyPages EFI_ALLOCATE_TYPE = iota
AllocateMaxAddress
AllocateAddress
)
type EFI_MEMORY_TYPE int
const (
EfiReservedMemoryType EFI_MEMORY_TYPE = iota
EfiLoaderCode
EfiLoaderData
EfiBootServicesCode
EfiBootServicesData
EfiRuntimeServicesCode
EfiRuntimeServicesData
EfiConventionalMemory
)
type EVENT_TYPE uint32
const (
EVT_TIMER EVENT_TYPE = 0x80000000
)
const (
TPL_CALLBACK EFI_TPL = 8
)
type EFI_TIMER_DELAY int
const (
TimerCancel EFI_TIMER_DELAY = iota
TimerPeriodic
TimerRelative
)
-111
View File
@@ -1,111 +0,0 @@
package uefi
const (
uintnSize = 32 << (^uintptr(0) >> 63)
errorMask = 1 << uintptr(uintnSize-1)
)
const (
EFI_SUCCESS EFI_STATUS = 0
EFI_LOAD_ERROR EFI_STATUS = errorMask | 1
EFI_INVALID_PARAMETER EFI_STATUS = errorMask | 2
EFI_UNSUPPORTED EFI_STATUS = errorMask | 3
EFI_BAD_BUFFER_SIZE EFI_STATUS = errorMask | 4
EFI_BUFFER_TOO_SMALL EFI_STATUS = errorMask | 5
EFI_NOT_READY EFI_STATUS = errorMask | 6
EFI_DEVICE_ERROR EFI_STATUS = errorMask | 7
EFI_WRITE_PROTECTED EFI_STATUS = errorMask | 8
EFI_OUT_OF_RESOURCES EFI_STATUS = errorMask | 9
EFI_VOLUME_CORRUPTED EFI_STATUS = errorMask | 10
EFI_VOLUME_FULL EFI_STATUS = errorMask | 11
EFI_NO_MEDIA EFI_STATUS = errorMask | 12
EFI_MEDIA_CHANGED EFI_STATUS = errorMask | 13
EFI_NOT_FOUND EFI_STATUS = errorMask | 14
EFI_ACCESS_DENIED EFI_STATUS = errorMask | 15
EFI_NO_RESPONSE EFI_STATUS = errorMask | 16
EFI_NO_MAPPING EFI_STATUS = errorMask | 17
EFI_TIMEOUT EFI_STATUS = errorMask | 18
EFI_NOT_STARTED EFI_STATUS = errorMask | 19
EFI_ALREADY_STARTED EFI_STATUS = errorMask | 20
EFI_ABORTED EFI_STATUS = errorMask | 21
EFI_ICMP_ERROR EFI_STATUS = errorMask | 22
EFI_TFTP_ERROR EFI_STATUS = errorMask | 23
EFI_PROTOCOL_ERROR EFI_STATUS = errorMask | 24
EFI_INCOMPATIBLE_VERSION EFI_STATUS = errorMask | 25
EFI_SECURITY_VIOLATION EFI_STATUS = errorMask | 26
EFI_CRC_ERROR EFI_STATUS = errorMask | 27
EFI_END_OF_MEDIA EFI_STATUS = errorMask | 28
EFI_END_OF_FILE EFI_STATUS = errorMask | 31
EFI_INVALID_LANGUAGE EFI_STATUS = errorMask | 32
EFI_COMPROMISED_DATA EFI_STATUS = errorMask | 33
EFI_IP_ADDRESS_CONFLICT EFI_STATUS = errorMask | 34
EFI_HTTP_ERROR EFI_STATUS = errorMask | 35
)
var errMap = map[EFI_STATUS]*Error{}
var (
ErrLoadError = newError(EFI_LOAD_ERROR, "image failed to load")
ErrInvalidParameter = newError(EFI_INVALID_PARAMETER, "a parameter was incorrect")
ErrUnsupported = newError(EFI_UNSUPPORTED, "operation not supported")
ErrBadBufferSize = newError(EFI_BAD_BUFFER_SIZE, "buffer size incorrect for request")
ErrBufferTooSmall = newError(EFI_BUFFER_TOO_SMALL, "buffer too small; size returned in parameter")
ErrNotReady = newError(EFI_NOT_READY, "no data pending")
ErrDeviceError = newError(EFI_DEVICE_ERROR, "physical device reported an error")
ErrWriteProtected = newError(EFI_WRITE_PROTECTED, "device is write-protected")
ErrOutOfResources = newError(EFI_OUT_OF_RESOURCES, "out of resources")
ErrVolumeCorrupted = newError(EFI_VOLUME_CORRUPTED, "filesystem inconsistency detected")
ErrVolumeFull = newError(EFI_VOLUME_FULL, "no more space on filesystem")
ErrNoMedia = newError(EFI_NO_MEDIA, "device contains no medium")
ErrMediaChanged = newError(EFI_MEDIA_CHANGED, "medium changed since last access")
ErrNotFound = newError(EFI_NOT_FOUND, "item not found")
ErrAccessDenied = newError(EFI_ACCESS_DENIED, "access denied")
ErrNoResponse = newError(EFI_NO_RESPONSE, "server not found or no response")
ErrNoMapping = newError(EFI_NO_MAPPING, "no device mapping exists")
ErrTimeout = newError(EFI_TIMEOUT, "timeout expired")
ErrNotStarted = newError(EFI_NOT_STARTED, "protocol not started")
ErrAlreadyStarted = newError(EFI_ALREADY_STARTED, "protocol already started")
ErrAborted = newError(EFI_ABORTED, "operation aborted")
ErrICMPError = newError(EFI_ICMP_ERROR, "ICMP error during network operation")
ErrTFTPError = newError(EFI_TFTP_ERROR, "TFTP error during network operation")
ErrProtocolError = newError(EFI_PROTOCOL_ERROR, "protocol error during network operation")
ErrIncompatibleVersion = newError(EFI_INCOMPATIBLE_VERSION, "requested version incompatible")
ErrSecurityViolation = newError(EFI_SECURITY_VIOLATION, "security violation")
ErrCRCError = newError(EFI_CRC_ERROR, "CRC error detected")
ErrEndOfMedia = newError(EFI_END_OF_MEDIA, "beginning or end of media reached")
ErrEndOfFile = newError(EFI_END_OF_FILE, "end of file reached")
ErrInvalidLanguage = newError(EFI_INVALID_LANGUAGE, "invalid language specified")
ErrCompromisedData = newError(EFI_COMPROMISED_DATA, "data security status unknown or compromised")
ErrIPAddressConflict = newError(EFI_IP_ADDRESS_CONFLICT, "IP address conflict detected")
ErrHTTPError = newError(EFI_HTTP_ERROR, "HTTP error during network operation")
)
type Error struct {
code EFI_STATUS
msg string
}
func newError(code EFI_STATUS, msg string) *Error {
err := &Error{code: code, msg: msg}
errMap[code] = err
return err
}
func (e *Error) Error() string {
return e.msg
}
func (e *Error) Status() EFI_STATUS {
return e.code
}
func StatusError(status EFI_STATUS) *Error {
if status == EFI_SUCCESS {
return nil
}
err, ok := errMap[status]
if !ok {
return newError(status, "unknown EFI error")
}
return err
}
-74
View File
@@ -1,74 +0,0 @@
package uefi
import "unsafe"
func booleanArg(v BOOLEAN) uintptr {
if v {
return 1
}
return 0
}
type EFI_SIMPLE_TEXT_OUTPUT_MODE struct {
MaxMode int32
Mode int32
Attribute int32
CursorColumn int32
CursorRow int32
CursorVisible BOOLEAN
}
type EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL struct {
reset uintptr
outputString uintptr
testString uintptr
queryMode uintptr
setMode uintptr
setAttribute uintptr
clearScreen uintptr
setCursorPosition uintptr
enableCursor uintptr
Mode *EFI_SIMPLE_TEXT_OUTPUT_MODE
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) Reset(extendedVerification BOOLEAN) EFI_STATUS {
return UefiCall2(p.reset, uintptr(unsafe.Pointer(p)), booleanArg(extendedVerification))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) OutputString(s *CHAR16) EFI_STATUS {
return UefiCall2(p.outputString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) TestString(s *CHAR16) EFI_STATUS {
return UefiCall2(p.testString, uintptr(unsafe.Pointer(p)), uintptr(unsafe.Pointer(s)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) QueryMode(modeNumber UINTN, columns *UINTN, rows *UINTN) EFI_STATUS {
return UefiCall4(
p.queryMode,
uintptr(unsafe.Pointer(p)),
uintptr(modeNumber),
uintptr(unsafe.Pointer(columns)),
uintptr(unsafe.Pointer(rows)),
)
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetMode(modeNumber UINTN) EFI_STATUS {
return UefiCall2(p.setMode, uintptr(unsafe.Pointer(p)), uintptr(modeNumber))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetAttribute(attribute UINTN) EFI_STATUS {
return UefiCall2(p.setAttribute, uintptr(unsafe.Pointer(p)), uintptr(attribute))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) ClearScreen() EFI_STATUS {
return UefiCall1(p.clearScreen, uintptr(unsafe.Pointer(p)))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) SetCursorPosition(column UINTN, row UINTN) EFI_STATUS {
return UefiCall3(p.setCursorPosition, uintptr(unsafe.Pointer(p)), uintptr(column), uintptr(row))
}
func (p *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) EnableCursor(visible BOOLEAN) EFI_STATUS {
return UefiCall2(p.enableCursor, uintptr(unsafe.Pointer(p)), booleanArg(visible))
}
-123
View File
@@ -1,123 +0,0 @@
package uefi
import "unsafe"
type EFI_RUNTIME_SERVICES struct {
Hdr EFI_TABLE_HEADER
getTime uintptr
setTime uintptr
getWakeupTime uintptr
setWakeupTime uintptr
setVirtualAddressMap uintptr
convertPointer uintptr
getVariable uintptr
getNextVariableName uintptr
setVariable uintptr
getNextHighMonoCount uintptr
resetSystem uintptr
updateCapsule uintptr
queryCapsuleCaps uintptr
queryVariableInfo uintptr
}
type EFI_BOOT_SERVICES struct {
Hdr EFI_TABLE_HEADER
raiseTPL uintptr
restoreTPL uintptr
allocatePages uintptr
freePages uintptr
getMemoryMap uintptr
allocatePool uintptr
freePool uintptr
createEvent uintptr
setTimer uintptr
waitForEvent uintptr
signalEvent uintptr
closeEvent uintptr
checkEvent uintptr
installProtocolInterface uintptr
reinstallProtocolIFace uintptr
uninstallProtocolIFace uintptr
handleProtocol uintptr
reserved *VOID
registerProtocolNotify uintptr
locateHandle uintptr
locateDevicePath uintptr
installConfigurationTable uintptr
loadImage uintptr
startImage uintptr
exit uintptr
unloadImage uintptr
exitBootServices uintptr
getNextMonotonicCount uintptr
stall uintptr
setWatchdogTimer uintptr
connectController uintptr
disconnectController uintptr
openProtocol uintptr
closeProtocol uintptr
openProtocolInformation uintptr
protocolsPerHandle uintptr
locateHandleBuffer uintptr
locateProtocol uintptr
}
func (p *EFI_BOOT_SERVICES) AllocatePages(typ EFI_ALLOCATE_TYPE, memoryType EFI_MEMORY_TYPE, pages UINTN, memory *EFI_PHYSICAL_ADDRESS) EFI_STATUS {
return UefiCall4(p.allocatePages, uintptr(typ), uintptr(memoryType), uintptr(pages), uintptr(unsafe.Pointer(memory)))
}
func (p *EFI_BOOT_SERVICES) FreePages(memory EFI_PHYSICAL_ADDRESS, pages UINTN) EFI_STATUS {
return UefiCall2(p.freePages, uintptr(memory), uintptr(pages))
}
func (p *EFI_BOOT_SERVICES) CreateEvent(typ EVENT_TYPE, notifyTPL EFI_TPL, notifyFunction unsafe.Pointer, notifyContext unsafe.Pointer, event *EFI_EVENT) EFI_STATUS {
return UefiCall5(p.createEvent, uintptr(typ), uintptr(notifyTPL), uintptr(notifyFunction), uintptr(notifyContext), uintptr(unsafe.Pointer(event)))
}
func (p *EFI_BOOT_SERVICES) SetTimer(event EFI_EVENT, typ EFI_TIMER_DELAY, triggerTime uint64) EFI_STATUS {
return UefiCall3(p.setTimer, uintptr(event), uintptr(typ), uintptr(triggerTime))
}
func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT, index *UINTN) EFI_STATUS {
return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index)))
}
func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.closeEvent, uintptr(event))
}
func (p *EFI_BOOT_SERVICES) CheckEvent(event EFI_EVENT) EFI_STATUS {
return UefiCall1(p.checkEvent, uintptr(event))
}
func (p *EFI_BOOT_SERVICES) HandleProtocol(handle EFI_HANDLE, protocol *EFI_GUID, iface unsafe.Pointer) EFI_STATUS {
return UefiCall3(p.handleProtocol, uintptr(handle), uintptr(unsafe.Pointer(protocol)), uintptr(iface))
}
func (p *EFI_BOOT_SERVICES) LocateProtocol(protocol *EFI_GUID, registration *VOID, iface unsafe.Pointer) EFI_STATUS {
return UefiCall3(p.locateProtocol, uintptr(unsafe.Pointer(protocol)), uintptr(unsafe.Pointer(registration)), uintptr(iface))
}
func (p *EFI_BOOT_SERVICES) Exit(imageHandle EFI_HANDLE, exitStatus EFI_STATUS, exitDataSize UINTN, exitData *CHAR16) EFI_STATUS {
return UefiCall4(p.exit, uintptr(imageHandle), uintptr(exitStatus), uintptr(exitDataSize), uintptr(unsafe.Pointer(exitData)))
}
func (p *EFI_BOOT_SERVICES) SetWatchdogTimer(timeout UINTN, watchdogCode uint64, dataSize UINTN, watchdogData *CHAR16) EFI_STATUS {
return UefiCall4(p.setWatchdogTimer, uintptr(timeout), uintptr(watchdogCode), uintptr(dataSize), uintptr(unsafe.Pointer(watchdogData)))
}
type EFI_SYSTEM_TABLE struct {
Hdr EFI_TABLE_HEADER
FirmwareVendor *CHAR16
FirmwareRevision uint32
ConsoleInHandle EFI_HANDLE
ConIn *VOID
ConsoleOutHandle EFI_HANDLE
ConOut *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
StandardErrorHandle EFI_HANDLE
StdErr *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
RuntimeServices *EFI_RUNTIME_SERVICES
BootServices *EFI_BOOT_SERVICES
NumberOfTableEntries UINTN
ConfigurationTable *VOID
}
-41
View File
@@ -1,41 +0,0 @@
package uefi
import "errors"
var errNilTextOutputProtocol = errors.New("uefi: nil simple text output protocol")
type TextOutput struct {
proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
}
func NewTextOutput(proto *EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL) *TextOutput {
return &TextOutput{proto: proto}
}
func ConsoleOut() *TextOutput {
return NewTextOutput(ST().ConOut)
}
func StandardError() *TextOutput {
return NewTextOutput(ST().StdErr)
}
func (w *TextOutput) Write(p []byte) (int, error) {
if w == nil || w.proto == nil {
return 0, errNilTextOutputProtocol
}
if len(p) == 0 {
return 0, nil
}
buf := StringToCHAR16Z(string(p))
status := w.proto.OutputString(&buf[0])
if status != EFI_SUCCESS {
return 0, StatusError(status)
}
return len(p), nil
}
func (w *TextOutput) WriteString(s string) (int, error) {
return w.Write([]byte(s))
}
-29
View File
@@ -1,29 +0,0 @@
//go:build uefi
package uefi
import "unsafe"
var systemTable *EFI_SYSTEM_TABLE
var imageHandle uintptr
//go:nobounds
func Init(argImageHandle uintptr, argSystemTable uintptr) {
systemTable = (*EFI_SYSTEM_TABLE)(unsafe.Pointer(argSystemTable))
imageHandle = argImageHandle
}
func ST() *EFI_SYSTEM_TABLE {
return systemTable
}
func BS() *EFI_BOOT_SERVICES {
if systemTable == nil {
return nil
}
return systemTable.BootServices
}
func GetImageHandle() EFI_HANDLE {
return EFI_HANDLE(imageHandle)
}
-1
View File
@@ -22,7 +22,6 @@ func main() {
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup}) button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
kb := keyboard.Port() kb := keyboard.Port()
machine.USBDev.Configure(machine.UARTConfig{}) // no-op if already init'd by serial.usb
for { for {
if !button.Get() { if !button.Get() {
+1 -1
View File
@@ -25,7 +25,7 @@ func main() {
func escapesToHeap() { func escapesToHeap() {
n := rand.Intn(100) n := rand.Intn(100)
println("Doing ", n, " iterations") println("Doing ", n, " iterations")
for i := range n { for i := 0; i < n; i++ {
s := make([]byte, i) s := make([]byte, i)
_ = append(s, 42) _ = append(s, 42)
} }
+2 -2
View File
@@ -70,7 +70,7 @@ func main() {
printItf(Number(3)) printItf(Number(3))
s := Stringer(thing) s := Stringer(thing)
println("Stringer.String():", s.String()) println("Stringer.String():", s.String())
var itf any = s var itf interface{} = s
println("Stringer.(*Thing).String():", itf.(Stringer).String()) println("Stringer.(*Thing).String():", itf.(Stringer).String())
// unusual calls // unusual calls
@@ -124,7 +124,7 @@ func strlen(s string) int {
return len(s) return len(s)
} }
func printItf(val any) { func printItf(val interface{}) {
switch val := val.(type) { switch val := val.(type) {
case Doubler: case Doubler:
println("is Doubler:", val.Double()) println("is Doubler:", val.Double())
+1 -78
View File
@@ -6,85 +6,8 @@ import (
"time" "time"
) )
// Disk geometry.
const (
sectorSize = 512
diskSectors = 128 // 64 KB total
)
var diskData [diskSectors * sectorSize]byte
type ramDisk struct{}
func (r *ramDisk) ReadAt(p []byte, off int64) (int, error) {
return copy(p, diskData[off:]), nil
}
func (r *ramDisk) WriteAt(p []byte, off int64) (int, error) {
return copy(diskData[off:], p), nil
}
func (r *ramDisk) Size() int64 { return int64(diskSectors * sectorSize) }
func (r *ramDisk) WriteBlockSize() int64 { return sectorSize }
func (r *ramDisk) EraseBlockSize() int64 { return sectorSize }
func (r *ramDisk) EraseBlocks(start, len int64) error { return nil }
func init() {
formatFAT12(diskData[:])
}
// formatFAT12 writes a minimal FAT12 volume boot record and FAT tables so the
// host OS can mount the disk without reformatting.
func formatFAT12(d []byte) {
// --- Sector 0: Volume Boot Record ---
s := d[0:]
s[0] = 0xEB
s[1] = 0x3C
s[2] = 0x90 // short JMP + NOP
copy(s[3:11], "MSDOS5.0")
// BPB fields (little-endian)
s[11] = 0x00
s[12] = 0x02 // bytesPerSector = 512
s[13] = 0x01 // sectorsPerCluster = 1
s[14] = 0x01
s[15] = 0x00 // reservedSectors = 1
s[16] = 0x02 // numFATs = 2
s[17] = 0x20
s[18] = 0x00 // rootEntryCount = 32
s[19] = 0x80
s[20] = 0x00 // totalSectors16 = 128
s[21] = 0xF8 // mediaType = fixed disk
s[22] = 0x01
s[23] = 0x00 // sectorsPerFAT = 1
s[24] = 0x80
s[25] = 0x00 // sectorsPerTrack = 128
s[26] = 0x01
s[27] = 0x00 // numHeads = 1
// hiddenSectors[28:32] = 0
// totalSectors32[32:36] = 0
s[38] = 0x29 // extBootSig
s[39] = 0x47
s[40] = 0x4F
s[41] = 0x30
s[42] = 0x31 // volumeID "GO01"
copy(s[43:54], "TINYGO ") // volumeLabel (11 bytes)
copy(s[54:62], "FAT12 ") // fsType
s[510] = 0x55
s[511] = 0xAA // boot sector signature
// --- Sector 1: FAT1 ---
// Entry 0 = 0xFF8 (media byte), entry 1 = 0xFFF (EOC); all others = free.
d[512] = 0xF8
d[513] = 0xFF
d[514] = 0xFF
// --- Sector 2: FAT2 (identical copy) ---
copy(d[1024:1027], d[512:515])
}
func main() { func main() {
msc.Port(&ramDisk{}) msc.Port(machine.Flash)
machine.USBDev.Configure(machine.UARTConfig{})
for { for {
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
+8 -5
View File
@@ -45,8 +45,11 @@ func Compare(a, b []byte) int {
// This function was copied from the Go 1.23 source tree (with runtime_cmpstring // This function was copied from the Go 1.23 source tree (with runtime_cmpstring
// manually inlined). // manually inlined).
func CompareString(a, b string) int { func CompareString(a, b string) int {
l := min(len(b), len(a)) l := len(a)
for i := range l { if len(b) < l {
l = len(b)
}
for i := 0; i < l; i++ {
c1, c2 := a[i], b[i] c1, c2 := a[i], b[i]
if c1 < c2 { if c1 < c2 {
return -1 return -1
@@ -167,7 +170,7 @@ const PrimeRK = 16777619
// This function was removed in Go 1.22. // This function was removed in Go 1.22.
func HashStrBytes(sep []byte) (uint32, uint32) { func HashStrBytes(sep []byte) (uint32, uint32) {
hash := uint32(0) hash := uint32(0)
for i := range sep { for i := 0; i < len(sep); i++ {
hash = hash*PrimeRK + uint32(sep[i]) hash = hash*PrimeRK + uint32(sep[i])
} }
var pow, sq uint32 = 1, PrimeRK var pow, sq uint32 = 1, PrimeRK
@@ -246,7 +249,7 @@ func IndexRabinKarpBytes(s, sep []byte) int {
hashsep, pow := HashStrBytes(sep) hashsep, pow := HashStrBytes(sep)
n := len(sep) n := len(sep)
var h uint32 var h uint32
for i := range n { for i := 0; i < n; i++ {
h = h*PrimeRK + uint32(s[i]) h = h*PrimeRK + uint32(s[i])
} }
if h == hashsep && Equal(s[:n], sep) { if h == hashsep && Equal(s[:n], sep) {
@@ -273,7 +276,7 @@ func IndexRabinKarp[T string | []byte](s, sep T) int {
hashss, pow := HashStr(sep) hashss, pow := HashStr(sep)
n := len(sep) n := len(sep)
var h uint32 var h uint32
for i := range n { for i := 0; i < n; i++ {
h = h*PrimeRK + uint32(s[i]) h = h*PrimeRK + uint32(s[i])
} }
if h == hashss && string(s[:n]) == string(sep) { if h == hashss && string(s[:n]) == string(sep) {
+2 -2
View File
@@ -12,8 +12,8 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te
return &emptyTextError{} return &emptyTextError{}
} }
s := string(text) s := string(text)
for i, c := range cases { for i := 0; i < len(cases); i++ {
if c == s { if cases[i] == s {
*v = T(i) *v = T(i)
return nil return nil
} }
+11
View File
@@ -0,0 +1,11 @@
//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,3 +1,5 @@
//go:build go1.23
package cm package cm
import "structs" import "structs"

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