mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-27 07:08:42 +00:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6645f412ae | |||
| 35a61ac8c5 | |||
| c1a4ed1489 | |||
| 140c82e012 | |||
| dc23ce73d6 | |||
| e039ce131c | |||
| d59c706e60 | |||
| 26d3645317 | |||
| 432ebe13ed | |||
| 16eefc59eb | |||
| fb1bf2e6bf | |||
| 0922e3e956 | |||
| bd61913973 | |||
| 2dbfdbae10 | |||
| 05029c4fb5 | |||
| a7b30639d8 | |||
| 4adc661197 | |||
| 311c071ae6 | |||
| f1fdfe2313 | |||
| 7c3dc05117 | |||
| e569dcfe38 | |||
| 20038817ae | |||
| 9ce73cfafa | |||
| 0033c23848 | |||
| 5c15a68d18 | |||
| 02384c9414 | |||
| e79edb58b2 | |||
| bf80e9b446 | |||
| 30355cec9f | |||
| 9111927f85 | |||
| 10224a4bf8 | |||
| 33ddee76db | |||
| 7fa50a55cf | |||
| 1c0c26b8b7 | |||
| 3abe58448b | |||
| 364ac2dcb2 | |||
| e111b489ec | |||
| f31b39775f | |||
| 6c30583062 | |||
| d29e8e5f22 | |||
| a93640662e | |||
| 3fb0a90854 | |||
| df09dbdf0f | |||
| 134de98ba5 | |||
| 221ea6a54c | |||
| 7ffbcbc666 | |||
| 5f66260c3a | |||
| ba95eb3e1b | |||
| f16697624d | |||
| cb311ed25e | |||
| 6f686d12af | |||
| 6a0acfc735 | |||
| 27f8ec17fc | |||
| a683881eb7 | |||
| 335b085fd2 | |||
| 11c2b76e29 | |||
| 6605b6e43f | |||
| ec18e89365 | |||
| 39cde9f9a3 | |||
| 2747027ef2 | |||
| 4465360f3d | |||
| c33113ab5f | |||
| eab43851ac | |||
| c3f1832d9a | |||
| 2328d1e799 | |||
| 5fabc203db | |||
| d9d19e812e | |||
| 45dd431f19 | |||
| 088a660c00 | |||
| 1ff348fb1f | |||
| cd364a9315 | |||
| ca584de84a | |||
| 594be6db63 | |||
| aef70a5839 | |||
| e6e7250a47 | |||
| 7b3dc19445 | |||
| 8e36d2758b | |||
| 7cbbfd6fc5 | |||
| e8bb38f7c9 | |||
| 1b9bb143bf | |||
| 41f666c670 | |||
| 469e2434fd | |||
| 278aa09819 | |||
| dded832238 | |||
| 8ffabbea64 | |||
| 29b4c6723f | |||
| 039f48f3d2 | |||
| eed4afda63 | |||
| ce888e1042 | |||
| f5d0ec93ef | |||
| 3a0d72457b | |||
| c980e48ad4 | |||
| f5bd750a4d | |||
| 0a5875ab2e | |||
| 54ecf74261 | |||
| 9b83acffe6 | |||
| 67aa15e92e | |||
| 0d6efc6429 | |||
| 1bd3ade825 | |||
| fb8ed8fde8 | |||
| c698eeacba | |||
| 906a20d3bf | |||
| 1be377c47d | |||
| 4dbb5dd4da | |||
| 997974376d | |||
| 7d51044892 | |||
| 37c080083b | |||
| 70a0dee8c8 | |||
| de83d67364 | |||
| 7a5a50d949 | |||
| d348d8b4cf | |||
| 443de9fe1c | |||
| c72f1d5e62 | |||
| ca8fcc9479 | |||
| d01a932201 |
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
@@ -131,7 +131,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Build TinyGo (LLVM ${{ matrix.version }})
|
||||
run: go install -tags=llvm${{ matrix.version }}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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
|
||||
@@ -12,6 +12,24 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
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 binaries, ready for release.
|
||||
# This runs inside an Alpine Linux container so we can more easily create a
|
||||
@@ -142,7 +160,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
@@ -186,12 +204,12 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '22'
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
with:
|
||||
@@ -266,6 +284,8 @@ jobs:
|
||||
- run: make smoketest
|
||||
- run: make wasmtest
|
||||
- run: make tinygo-test-baremetal
|
||||
- name: Check Go code formatting
|
||||
run: make fmt-check lint
|
||||
build-linux-cross:
|
||||
# Build ARM Linux binaries, ready for release.
|
||||
# This intentionally uses an older Linux image, so that we compile against
|
||||
@@ -303,7 +323,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
|
||||
@@ -17,12 +17,6 @@ jobs:
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -40,13 +34,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Restore cached LLVM source
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-windows-v1
|
||||
key: llvm-source-20-windows-v3
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
@@ -71,7 +65,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-20-windows-v2
|
||||
key: llvm-build-20-windows-v5
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
@@ -93,7 +87,7 @@ jobs:
|
||||
- name: Cache Go cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
key: go-cache-windows-v2-${{ hashFiles('go.mod') }}
|
||||
key: go-cache-windows-v3-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
C:/Users/runneradmin/AppData/Local/go-build
|
||||
C:/Users/runneradmin/go/pkg/mod
|
||||
@@ -124,12 +118,6 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -141,7 +129,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -157,18 +145,12 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
@@ -183,12 +165,6 @@ jobs:
|
||||
runs-on: windows-2022
|
||||
needs: build-windows
|
||||
steps:
|
||||
- name: Configure pagefile
|
||||
uses: al-cheb/configure-pagefile-action@v1.4
|
||||
with:
|
||||
minimum-size: 8GB
|
||||
maximum-size: 24GB
|
||||
disk-root: "C:"
|
||||
- uses: MinoruSekine/setup-scoop@v4
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
@@ -200,7 +176,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.2'
|
||||
go-version: '1.26.4'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
+46
-13
@@ -142,6 +142,9 @@ ifeq ($(OS),Windows_NT)
|
||||
|
||||
# PIC needs to be disabled for libclang to work.
|
||||
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_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
@@ -194,6 +197,12 @@ NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm lld $(addprefix lib/lib,
|
||||
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_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)
|
||||
endif
|
||||
|
||||
@@ -298,7 +307,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
|
||||
|
||||
# Check for Node.js used during WASM tests.
|
||||
MIN_NODEJS_VERSION=18
|
||||
MIN_NODEJS_VERSION=22
|
||||
|
||||
.PHONY: check-nodejs-version
|
||||
check-nodejs-version:
|
||||
@@ -310,7 +319,7 @@ check-nodejs-version:
|
||||
|
||||
tinygo: ## Build the TinyGo compiler
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " $(MAKE) llvm-source"; echo " $(MAKE) $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CFLAGS="$(CGO_CFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm osusergo" .
|
||||
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)
|
||||
|
||||
@@ -379,22 +388,22 @@ TEST_PACKAGES_FAST = \
|
||||
# archive/zip requires os.ReadAt, which is not yet supported on windows
|
||||
# bytes requires mmap
|
||||
# compress/flate appears to hang on wasi
|
||||
# crypto/aes fails on wasi, needs panic()/recover()
|
||||
# crypto/aes needs reflect.Type.Method(), not yet implemented
|
||||
# crypto/des fails on wasi, needs panic()/recover()
|
||||
# crypto/hmac fails on wasi, it exits with a "slice out of range" panic
|
||||
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
|
||||
# encoding/xml takes a minute on linux and gives a stack overflow on wasi
|
||||
# image requires recover(), which is not yet supported on wasi
|
||||
# image fails on wasi, needs panic()/recover()
|
||||
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
|
||||
# mime: fail on wasi; neds panic()/recover()
|
||||
# mime: fails on wasi, needs panic()/recover()
|
||||
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# mime/quotedprintable requires syscall.Faccessat
|
||||
# net/mail: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# net/ntextproto: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||
# regexp/syntax: fails on wasip1; needs panic()/recover()
|
||||
# strconv requires recover() which is not yet supported on wasi
|
||||
# text/tabwriter requires recover(), which is not yet supported on wasi
|
||||
# text/template/parse requires recover(), which is not yet supported on wasi
|
||||
# regexp/syntax: fails on wasip1, needs panic()/recover()
|
||||
# strconv: fails on wasi, needs panic()/recover()
|
||||
# text/tabwriter: fails on wasi, needs panic()/recover()
|
||||
# text/template/parse: fails on wasi, needs panic()/recover()
|
||||
# testing/fstest requires os.ReadDir, which is not yet supported on windows or wasi
|
||||
|
||||
# Additional standard library packages that pass tests on individual platforms
|
||||
@@ -404,6 +413,7 @@ TEST_PACKAGES_LINUX := \
|
||||
context \
|
||||
crypto/aes \
|
||||
crypto/des \
|
||||
crypto/ecdh \
|
||||
crypto/hmac \
|
||||
debug/dwarf \
|
||||
debug/plan9obj \
|
||||
@@ -419,6 +429,7 @@ TEST_PACKAGES_LINUX := \
|
||||
os/user \
|
||||
regexp/syntax \
|
||||
strconv \
|
||||
testing/fstest \
|
||||
text/tabwriter \
|
||||
text/template/parse
|
||||
|
||||
@@ -429,7 +440,11 @@ TEST_PACKAGES_WINDOWS := \
|
||||
compress/flate \
|
||||
crypto/des \
|
||||
crypto/hmac \
|
||||
image \
|
||||
mime \
|
||||
regexp/syntax \
|
||||
strconv \
|
||||
text/tabwriter \
|
||||
text/template/parse \
|
||||
$(nil)
|
||||
|
||||
@@ -478,10 +493,12 @@ report-stdlib-tests-pass:
|
||||
ifeq ($(uname),Darwin)
|
||||
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_DARWIN)
|
||||
TEST_IOFS := true
|
||||
TEST_ENCODING_XML := true
|
||||
endif
|
||||
ifeq ($(uname),Linux)
|
||||
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_LINUX)
|
||||
TEST_IOFS := true
|
||||
TEST_ENCODING_XML := true
|
||||
endif
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
|
||||
@@ -496,8 +513,11 @@ TEST_ADDITIONAL_FLAGS ?=
|
||||
.PHONY: tinygo-test
|
||||
tinygo-test:
|
||||
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
|
||||
@# TestParseAndBytesRoundTrip/P256/Generic: relies on t.Skip() which is not implemented
|
||||
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(TEST_PACKAGES_HOST) $(TEST_PACKAGES_SLOW)
|
||||
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm.
|
||||
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(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
|
||||
@# requires a large stack-size. Hence, io/fs is only run conditionally.
|
||||
@# For more details, see the comments on issue #3143.
|
||||
@@ -742,6 +762,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=xiao-ble-plus examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=rak4631 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
|
||||
@@ -778,6 +800,8 @@ endif
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
|
||||
@$(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
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||
@@ -887,6 +911,8 @@ ifneq ($(STM32), 0)
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32l0x1 examples/serial
|
||||
@$(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
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-uno-q examples/serial
|
||||
@@ -947,6 +973,11 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target mch2022 examples/machinetest
|
||||
@$(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
|
||||
$(TINYGO) build -size short -o test.bin -target=xiao-esp32s3 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
@@ -967,6 +998,8 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32s3-supermini examples/adc
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32s3-box-3 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
# esp32c3-supermini
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32c3-supermini examples/blinky1
|
||||
@@ -1163,8 +1196,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/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 llvm-project/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp llvm-project/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt-builtins
|
||||
@cp -rp ${LLVM_PROJECTDIR}/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 src build/release/tinygo/src
|
||||
@cp -rp targets build/release/tinygo/targets
|
||||
|
||||
|
||||
+25
-11
@@ -14,6 +14,7 @@ import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"hash/crc32"
|
||||
"maps"
|
||||
"math/bits"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -137,9 +138,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if _, ok := globalValues[pkgPath]; !ok {
|
||||
globalValues[pkgPath] = map[string]string{}
|
||||
}
|
||||
for k, v := range vals {
|
||||
globalValues[pkgPath][k] = v
|
||||
}
|
||||
maps.Copy(globalValues[pkgPath], vals)
|
||||
}
|
||||
|
||||
// Check for a libc dependency.
|
||||
@@ -278,7 +277,6 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
|
||||
var embedFileObjects []*compileJob
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg // necessary to avoid a race condition
|
||||
|
||||
var undefinedGlobals []string
|
||||
for name := range globalValues[pkg.Pkg.Path()] {
|
||||
@@ -775,7 +773,6 @@ 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
|
||||
// bitcode files together.
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := pkg
|
||||
for _, filename := range pkg.CFiles {
|
||||
abspath := filepath.Join(pkg.OriginalDir(), filename)
|
||||
job := &compileJob{
|
||||
@@ -842,22 +839,25 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
|
||||
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
|
||||
if config.GOOS() == "windows" {
|
||||
// Options for the MinGW wrapper for the lld COFF linker.
|
||||
switch config.LinkerFlavor() {
|
||||
case "coff":
|
||||
// Options for driving ld.lld in PE/COFF mode.
|
||||
ldflags = append(ldflags,
|
||||
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
|
||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
|
||||
} else if config.GOOS() == "darwin" {
|
||||
case "darwin":
|
||||
// Options for the ld64-compatible lld linker.
|
||||
ldflags = append(ldflags,
|
||||
"--lto-O"+strconv.Itoa(speedLevel),
|
||||
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
|
||||
} else {
|
||||
case "gnu":
|
||||
// Options for the ELF linker.
|
||||
ldflags = append(ldflags,
|
||||
"--lto-O"+strconv.Itoa(speedLevel),
|
||||
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"),
|
||||
)
|
||||
default:
|
||||
return fmt.Errorf("unknown linker flavor: %s", config.LinkerFlavor())
|
||||
}
|
||||
if config.CodeModel() != "default" {
|
||||
ldflags = append(ldflags,
|
||||
@@ -914,7 +914,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
}
|
||||
|
||||
// Run wasm-opt for wasm binaries
|
||||
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
|
||||
if arch, _, _ := strings.Cut(config.Triple(), "-"); arch == "wasm32" {
|
||||
optLevel, _, _ := config.OptLevel()
|
||||
opt := "-" + optLevel
|
||||
|
||||
@@ -1076,7 +1076,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp8266":
|
||||
case "esp32", "esp32-img", "esp32c3", "esp32s3", "esp32c6", "esp8266":
|
||||
// Special format for the ESP family of chips (parsed by the ROM
|
||||
// bootloader).
|
||||
result.Binary = filepath.Join(tmpdir, "main"+outext)
|
||||
@@ -1352,6 +1352,14 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
|
||||
}
|
||||
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)
|
||||
|
||||
// Add the reset handler function, for convenience. The reset handler runs
|
||||
@@ -1400,6 +1408,12 @@ func determineStackSizes(mod llvm.Module, executable string) ([]string, map[stri
|
||||
// overflow will occur even before the goroutine is started.
|
||||
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{
|
||||
stackSize: stackSize,
|
||||
stackSizeType: stackSizeType,
|
||||
|
||||
@@ -28,6 +28,7 @@ func TestClangAttributes(t *testing.T) {
|
||||
"cortex-m4",
|
||||
"cortex-m7",
|
||||
"esp32c3",
|
||||
"esp32c6",
|
||||
"esp32s3",
|
||||
"fe310",
|
||||
"gameboy-advance",
|
||||
@@ -46,7 +47,6 @@ func TestClangAttributes(t *testing.T) {
|
||||
targetNames = append(targetNames, "esp32", "esp8266")
|
||||
}
|
||||
for _, targetName := range targetNames {
|
||||
targetName := targetName
|
||||
t.Run(targetName, func(t *testing.T) {
|
||||
testClangAttributes(t, &compileopts.Options{Target: targetName})
|
||||
})
|
||||
|
||||
+1
-1
@@ -281,7 +281,7 @@ func parseDepFile(s string) ([]string, error) {
|
||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
||||
|
||||
// Only use the first line, which is expected to begin with "deps:".
|
||||
line := strings.SplitN(s, "\n", 2)[0]
|
||||
line, _, _ := strings.Cut(s, "\n")
|
||||
if !strings.HasPrefix(line, "deps:") {
|
||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
||||
}
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ import (
|
||||
var commands = map[string][]string{}
|
||||
|
||||
func init() {
|
||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||
llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
|
||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
}
|
||||
|
||||
// Version range supported by TinyGo.
|
||||
const minorMin = 19
|
||||
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 26
|
||||
|
||||
// Check that we support this Go toolchain version.
|
||||
|
||||
@@ -15,7 +15,7 @@ func makeDarwinLibSystemJob(config *compileopts.Config, tmpdir string) *compileJ
|
||||
return &compileJob{
|
||||
description: "compile Darwin libSystem.dylib",
|
||||
run: func(job *compileJob) (err error) {
|
||||
arch := strings.Split(config.Triple(), "-")[0]
|
||||
arch, _, _ := strings.Cut(config.Triple(), "-")
|
||||
job.result = filepath.Join(tmpdir, "libSystem.dylib")
|
||||
objpath := filepath.Join(tmpdir, "libSystem.o")
|
||||
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
|
||||
|
||||
+15
-3
@@ -100,12 +100,24 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
chip_id := map[string]uint16{
|
||||
"esp32": 0x0000,
|
||||
"esp32c3": 0x0005,
|
||||
"esp32c6": 0x000d,
|
||||
"esp32s3": 0x0009,
|
||||
}[chip]
|
||||
|
||||
// SPI flash speed/size byte (byte 3 of header):
|
||||
// Upper nibble = flash size, lower nibble = flash frequency.
|
||||
// The espflasher auto-detects and patches the flash size (upper nibble),
|
||||
// but the frequency (lower nibble) must be correct per chip.
|
||||
spiSpeedSize := map[string]uint8{
|
||||
"esp32": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
"esp32c6": 0x10, // 80MHz=0x00, 2MB=0x10 (C6 uses different freq encoding)
|
||||
"esp32s3": 0x1f, // 80MHz=0x0F, 2MB=0x10
|
||||
}[chip]
|
||||
|
||||
// Image header.
|
||||
switch chip {
|
||||
case "esp32", "esp32c3", "esp32s3":
|
||||
case "esp32", "esp32c3", "esp32s3", "esp32c6":
|
||||
// Header format:
|
||||
// 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
|
||||
@@ -126,8 +138,8 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: spiSpeedSize,
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
chip_id: chip_id,
|
||||
|
||||
+2
-2
@@ -195,11 +195,11 @@ type intHeap struct {
|
||||
sort.IntSlice
|
||||
}
|
||||
|
||||
func (h *intHeap) Push(x interface{}) {
|
||||
func (h *intHeap) Push(x any) {
|
||||
h.IntSlice = append(h.IntSlice, x.(int))
|
||||
}
|
||||
|
||||
func (h *intHeap) Pop() interface{} {
|
||||
func (h *intHeap) Pop() any {
|
||||
x := h.IntSlice[len(h.IntSlice)-1]
|
||||
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
|
||||
return x
|
||||
|
||||
+1
-2
@@ -218,7 +218,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
|
||||
return err
|
||||
}
|
||||
// Store this archive in the cache.
|
||||
return os.Rename(f.Name(), archiveFilePath)
|
||||
return robustRename(f.Name(), archiveFilePath)
|
||||
},
|
||||
}
|
||||
|
||||
@@ -232,7 +232,6 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
|
||||
}
|
||||
for _, path := range paths {
|
||||
// Strip leading "../" parts off the path.
|
||||
path := path
|
||||
cleanpath := path
|
||||
for strings.HasPrefix(cleanpath, "../") {
|
||||
cleanpath = cleanpath[3:]
|
||||
|
||||
+2
-2
@@ -28,8 +28,8 @@ func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
lines := strings.SplitSeq(string(data), "\n")
|
||||
for line := range lines {
|
||||
if strings.HasPrefix(line, "TYPEDEF ") {
|
||||
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
|
||||
value := matches[1]
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//go:build !windows
|
||||
|
||||
package builder
|
||||
|
||||
import "os"
|
||||
|
||||
func robustRename(oldpath, newpath string) error {
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
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
|
||||
}
|
||||
+8
-2
@@ -501,8 +501,9 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
Align: section.Addralign,
|
||||
Type: memoryStack,
|
||||
})
|
||||
} else {
|
||||
// Regular .bss section.
|
||||
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||
// Regular .bss section. Zero-initialized RAM is always
|
||||
// writable, so require SHF_WRITE here.
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
@@ -510,6 +511,11 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz
|
||||
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 {
|
||||
// .text
|
||||
sections = append(sections, memorySection{
|
||||
|
||||
@@ -42,16 +42,15 @@ func TestBinarySize(t *testing.T) {
|
||||
// This is a small number of very diverse targets that we want to test.
|
||||
tests := []sizeTest{
|
||||
// microcontrollers
|
||||
{"hifive1b", "examples/echo", 3680, 280, 0, 2252},
|
||||
{"microbit", "examples/serial", 2694, 342, 8, 2248},
|
||||
{"wioterminal", "examples/pininterrupt", 7074, 1510, 120, 7248},
|
||||
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
|
||||
{"microbit", "examples/serial", 2832, 368, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
// output varies by binaryen version.
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.target+"/"+tc.path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -85,7 +84,6 @@ func TestSizeFull(t *testing.T) {
|
||||
pkgMatch := regexp.MustCompile(`^[a-z/]+$`) // example: "internal/task"
|
||||
|
||||
for _, target := range tests {
|
||||
target := target
|
||||
t.Run(target, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -99,7 +97,7 @@ func TestSizeFull(t *testing.T) {
|
||||
t.Fatal("could not read program size:", err)
|
||||
}
|
||||
for _, pkg := range sizes.sortedPackageNames() {
|
||||
if pkg == "(padding)" || pkg == "(unknown)" {
|
||||
if pkg == "(padding)" || pkg == "(unknown)" || pkg == "Go types" {
|
||||
// TODO: correctly attribute all unknown binary size.
|
||||
continue
|
||||
}
|
||||
|
||||
+5
-3
@@ -116,14 +116,16 @@ func parseLLDErrors(text string) error {
|
||||
// This can happen in some cases like with CGo and //go:linkname tricker.
|
||||
if matches := regexp.MustCompile(`^ld.lld(-[0-9]+)?: error: undefined symbol: (.*)\n`).FindStringSubmatch(message); matches != nil {
|
||||
symbolName := matches[2]
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
for line := range strings.SplitSeq(message, "\n") {
|
||||
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
|
||||
if matches != nil {
|
||||
parsedError = true
|
||||
line, _ := strconv.Atoi(matches[3])
|
||||
// TODO: detect common mistakes like -gc=none?
|
||||
msg := "linker could not find symbol " + symbolName
|
||||
if symbolName == "runtime.alloc_noheap" {
|
||||
switch symbolName {
|
||||
case "runtime.alloc":
|
||||
msg = "object allocated on the heap with -gc=none"
|
||||
case "runtime.alloc_noheap":
|
||||
msg = "object allocated on the heap in //go:noheap function"
|
||||
}
|
||||
linkErrors = append(linkErrors, scanner.Error{
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
|
||||
}
|
||||
bl.SetNumBlocks(len(blocks))
|
||||
|
||||
for i := 0; i < len(blocks); i++ {
|
||||
for i := range blocks {
|
||||
bl.SetBlockNo(i)
|
||||
bl.SetData(blocks[i])
|
||||
|
||||
|
||||
+10
-30
@@ -26,10 +26,6 @@ import (
|
||||
"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.
|
||||
type cgoPackage struct {
|
||||
generated *ast.File
|
||||
@@ -44,7 +40,7 @@ type cgoPackage struct {
|
||||
tokenFiles map[string]*token.File
|
||||
definedGlobally map[string]ast.Node
|
||||
noescapingFuncs map[string]*noescapingFunc // #cgo noescape lines
|
||||
anonDecls map[interface{}]string
|
||||
anonDecls map[any]string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
visitedFiles map[string][]byte
|
||||
@@ -263,7 +259,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
tokenFiles: map[string]*token.File{},
|
||||
definedGlobally: map[string]ast.Node{},
|
||||
noescapingFuncs: map[string]*noescapingFunc{},
|
||||
anonDecls: map[interface{}]string{},
|
||||
anonDecls: map[any]string{},
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
|
||||
@@ -306,7 +302,7 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
// Find `import "C"` C fragments in the file.
|
||||
p.cgoHeaders = make([]string, len(files)) // combined CGo header fragment for each file
|
||||
for i, f := range files {
|
||||
var cgoHeader string
|
||||
var cgoHeader strings.Builder
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
@@ -341,7 +337,8 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
// Iterate through all parts of the CGo header. Note that every //
|
||||
// line is a new comment.
|
||||
position := fset.Position(genDecl.Doc.Pos())
|
||||
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
|
||||
var fragment strings.Builder
|
||||
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
|
||||
for _, comment := range genDecl.Doc.List {
|
||||
// Find all #cgo lines, extract and use their contents, and
|
||||
// replace the lines with spaces (to preserve locations).
|
||||
@@ -358,12 +355,13 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
|
||||
} else { // comment
|
||||
c = " " + c[2:len(c)-2]
|
||||
}
|
||||
fragment += c + "\n"
|
||||
fragment.WriteString(c)
|
||||
fragment.WriteByte('\n')
|
||||
}
|
||||
cgoHeader += fragment
|
||||
cgoHeader.WriteString(fragment.String())
|
||||
}
|
||||
|
||||
p.cgoHeaders[i] = cgoHeader
|
||||
p.cgoHeaders[i] = cgoHeader.String()
|
||||
}
|
||||
|
||||
// Define CFlags that will be used while parsing the package.
|
||||
@@ -654,7 +652,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "union",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: pos,
|
||||
@@ -708,7 +705,6 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
X: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -764,7 +760,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
@@ -811,11 +806,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
@@ -823,7 +813,6 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -881,7 +870,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
@@ -964,11 +952,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "s",
|
||||
Decl: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.StarExpr{
|
||||
@@ -976,7 +959,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: typeName,
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -997,7 +979,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "value",
|
||||
Obj: nil,
|
||||
},
|
||||
},
|
||||
Type: bitfield.field.Type,
|
||||
@@ -1015,7 +996,6 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
X: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: nil,
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
NamePos: bitfield.pos,
|
||||
@@ -1239,7 +1219,7 @@ func getPos(node ast.Node) token.Pos {
|
||||
// getUnnamedDeclName creates a name (with the given prefix) for the given C
|
||||
// declaration. This is used for structs, unions, and enums that are often
|
||||
// defined without a name and used in a typedef.
|
||||
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
|
||||
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf any) string {
|
||||
if name, ok := p.anonDecls[itf]; ok {
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//go:build go1.22
|
||||
|
||||
package cgo
|
||||
|
||||
// Code specifically for Go 1.22.
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
func init() {
|
||||
setASTFileFields = func(f *ast.File, start, end token.Pos) {
|
||||
f.FileStart = start
|
||||
f.FileEnd = end
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,6 @@ func TestCGo(t *testing.T) {
|
||||
"flags",
|
||||
"const",
|
||||
} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
|
||||
+5
-63
@@ -160,7 +160,7 @@ func (f *cgoFile) readNames(fragment string, cflags []string, filename string, c
|
||||
pos := f.getClangLocationPosition(location, unit)
|
||||
f.addError(pos, severity+": "+spelling)
|
||||
}
|
||||
for i := 0; i < numDiagnostics; i++ {
|
||||
for i := range numDiagnostics {
|
||||
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
|
||||
addDiagnostic(diagnostic)
|
||||
|
||||
@@ -217,10 +217,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
case C.CXCursor_FunctionDecl:
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Fun,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
exportName := name
|
||||
localName := name
|
||||
var stringSignature string
|
||||
@@ -258,7 +254,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + localName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.FuncType{
|
||||
Func: pos,
|
||||
@@ -283,7 +278,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Text: strings.Join(doc, "\n"),
|
||||
})
|
||||
}
|
||||
for i := 0; i < numArgs; i++ {
|
||||
for i := range numArgs {
|
||||
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
|
||||
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
|
||||
argType := C.clang_getArgType(cursorType, C.uint(i))
|
||||
@@ -295,11 +290,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: argName,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: argName,
|
||||
Decl: decl,
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: f.makeDecayingASTType(argType, pos),
|
||||
@@ -315,7 +305,6 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
},
|
||||
}
|
||||
}
|
||||
obj.Decl = decl
|
||||
return decl, stringSignature
|
||||
case C.CXCursor_StructDecl, C.CXCursor_UnionDecl:
|
||||
typ := f.makeASTRecordType(c, pos)
|
||||
@@ -325,39 +314,27 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
// Convert to a single-field struct type.
|
||||
typeExpr = f.makeUnionField(typ)
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: typ.pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: typeExpr,
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, typ
|
||||
case C.CXCursor_TypedefDecl:
|
||||
typeName := "_Cgo_" + name
|
||||
underlyingType := C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: typeName,
|
||||
}
|
||||
typeSpec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
}
|
||||
if underlyingType.kind != C.CXType_Enum {
|
||||
typeSpec.Assign = pos
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_VarDecl:
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
@@ -376,19 +353,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
},
|
||||
},
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Type: typeExpr,
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
case C.CXCursor_MacroDefinition:
|
||||
@@ -405,26 +376,16 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
case C.CXCursor_EnumDecl:
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
underlying := C.tinygo_clang_getEnumDeclIntegerType(c)
|
||||
// TODO: gc's CGo implementation uses types such as `uint32` for enums
|
||||
// instead of types such as C.int, which are used here.
|
||||
@@ -432,12 +393,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
},
|
||||
Assign: pos,
|
||||
Type: f.makeASTType(underlying, pos),
|
||||
}
|
||||
obj.Decl = typeSpec
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_EnumConstantDecl:
|
||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
||||
@@ -452,19 +411,13 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
}
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Con,
|
||||
Name: "_Cgo_" + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: pos,
|
||||
Name: "_Cgo_" + name,
|
||||
Obj: obj,
|
||||
}},
|
||||
Values: []ast.Expr{expr},
|
||||
}
|
||||
obj.Decl = valueSpec
|
||||
gen.Specs = append(gen.Specs, valueSpec)
|
||||
return gen, nil
|
||||
default:
|
||||
@@ -581,7 +534,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
|
||||
// source locations.
|
||||
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
|
||||
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) any {
|
||||
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
|
||||
var file C.CXFile
|
||||
var line C.unsigned
|
||||
@@ -639,7 +592,8 @@ func (p *cgoPackage) getClangLocationPosition(location C.CXSourceLocation, tu C.
|
||||
Package: f.Pos(0),
|
||||
Name: ast.NewIdent(p.packageName),
|
||||
}
|
||||
setASTFileFields(astFile, f.Pos(0), f.Pos(int(size)))
|
||||
astFile.FileStart = f.Pos(0)
|
||||
astFile.FileEnd = f.Pos(int(size))
|
||||
p.cgoFiles = append(p.cgoFiles, astFile)
|
||||
}
|
||||
positionFile := p.tokenFiles[filename]
|
||||
@@ -956,22 +910,16 @@ func (p *cgoPackage) getIntegerType(name string, cursor clangCursor) *ast.TypeSp
|
||||
}
|
||||
|
||||
// Construct an *ast.TypeSpec for this type.
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Typ,
|
||||
Name: name,
|
||||
}
|
||||
spec := &ast.TypeSpec{
|
||||
Name: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: obj,
|
||||
},
|
||||
Type: &ast.Ident{
|
||||
NamePos: pos,
|
||||
Name: goName,
|
||||
},
|
||||
}
|
||||
obj.Decl = spec
|
||||
return spec
|
||||
}
|
||||
|
||||
@@ -1104,7 +1052,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
pos: prevField.Names[0].NamePos,
|
||||
})
|
||||
prevField.Names[0].Name = bitfieldName
|
||||
prevField.Names[0].Obj.Name = bitfieldName
|
||||
}
|
||||
prevBitfield := &(*bitfieldList)[len(*bitfieldList)-1]
|
||||
prevBitfield.endBit = bitfieldOffset
|
||||
@@ -1121,11 +1068,6 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: name,
|
||||
Decl: field,
|
||||
},
|
||||
},
|
||||
}
|
||||
fieldList.List = append(fieldList.List, field)
|
||||
|
||||
+4
-4
@@ -12,17 +12,17 @@ import "C"
|
||||
// C. It is useful if an API uses function pointers and you cannot pass a Go
|
||||
// pointer but only a C pointer.
|
||||
type refMap struct {
|
||||
refs map[unsafe.Pointer]interface{}
|
||||
refs map[unsafe.Pointer]any
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Put stores a value in the map. It can later be retrieved using Get. It must
|
||||
// be removed using Remove to avoid memory leaks.
|
||||
func (m *refMap) Put(v interface{}) unsafe.Pointer {
|
||||
func (m *refMap) Put(v any) unsafe.Pointer {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
if m.refs == nil {
|
||||
m.refs = make(map[unsafe.Pointer]interface{}, 1)
|
||||
m.refs = make(map[unsafe.Pointer]any, 1)
|
||||
}
|
||||
ref := C.malloc(1)
|
||||
m.refs[ref] = v
|
||||
@@ -31,7 +31,7 @@ func (m *refMap) Put(v interface{}) unsafe.Pointer {
|
||||
|
||||
// Get returns a stored value previously inserted with Put. Use the same
|
||||
// reference as you got from Put.
|
||||
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
|
||||
func (m *refMap) Get(ref unsafe.Pointer) any {
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
return m.refs[ref]
|
||||
|
||||
+20
-9
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -140,13 +141,7 @@ func (c *Config) GC() string {
|
||||
func (c *Config) NeedsStackObjects() bool {
|
||||
switch c.GC() {
|
||||
case "conservative", "custom", "precise", "boehm":
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "tinygo.wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return slices.Contains(c.BuildTags(), "tinygo.wasm")
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -245,7 +240,7 @@ func (c *Config) RP2040BootPatch() bool {
|
||||
// Return a canonicalized architecture name, so we don't have to deal with arm*
|
||||
// vs thumb* vs arm64.
|
||||
func CanonicalArchName(triple string) string {
|
||||
arch := strings.Split(triple, "-")[0]
|
||||
arch, _, _ := strings.Cut(triple, "-")
|
||||
if arch == "arm64" {
|
||||
return "aarch64"
|
||||
}
|
||||
@@ -466,6 +461,22 @@ func (c *Config) LDFlags() []string {
|
||||
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
|
||||
// executable. This can include extra C and assembly files.
|
||||
func (c *Config) ExtraFiles() []string {
|
||||
@@ -539,7 +550,7 @@ func (c *Config) Programmer() (method, openocdInterface string) {
|
||||
case "openocd", "msd", "command", "adb":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||
case "bmp":
|
||||
case "bmp", "probe-rs":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, ""
|
||||
default:
|
||||
|
||||
+9
-16
@@ -3,6 +3,7 @@ package compileopts
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -47,6 +48,7 @@ type Options struct {
|
||||
Nobounds bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintAllocsCover bool // emit allocs in go coverage tool format
|
||||
PrintStacks bool
|
||||
Tags []string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
@@ -66,7 +68,7 @@ type Options struct {
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
func (o *Options) Verify() error {
|
||||
if o.BuildMode != "" {
|
||||
valid := isInArray(validBuildModeOptions, o.BuildMode)
|
||||
valid := slices.Contains(validBuildModeOptions, o.BuildMode)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
|
||||
o.BuildMode,
|
||||
@@ -74,7 +76,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
if o.GC != "" {
|
||||
valid := isInArray(validGCOptions, o.GC)
|
||||
valid := slices.Contains(validGCOptions, o.GC)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
|
||||
o.GC,
|
||||
@@ -83,7 +85,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Scheduler != "" {
|
||||
valid := isInArray(validSchedulerOptions, o.Scheduler)
|
||||
valid := slices.Contains(validSchedulerOptions, o.Scheduler)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
|
||||
o.Scheduler,
|
||||
@@ -92,7 +94,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Serial != "" {
|
||||
valid := isInArray(validSerialOptions, o.Serial)
|
||||
valid := slices.Contains(validSerialOptions, o.Serial)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
||||
o.Serial,
|
||||
@@ -101,7 +103,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.PrintSizes != "" {
|
||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
|
||||
o.PrintSizes,
|
||||
@@ -110,7 +112,7 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.PanicStrategy != "" {
|
||||
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
|
||||
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
|
||||
o.PanicStrategy,
|
||||
@@ -119,19 +121,10 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
|
||||
if o.Opt != "" {
|
||||
if !isInArray(validOptOptions, o.Opt) {
|
||||
if !slices.Contains(validOptOptions, o.Opt) {
|
||||
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isInArray(arr []string, item string) bool {
|
||||
for _, i := range arr {
|
||||
if i == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@ type TargetSpec struct {
|
||||
Scheduler string `json:"scheduler,omitempty"`
|
||||
Serial string `json:"serial,omitempty"` // which serial output to use (uart, usb, none)
|
||||
Linker string `json:"linker,omitempty"`
|
||||
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
|
||||
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)
|
||||
Libc string `json:"libc,omitempty"`
|
||||
AutoStackSize *bool `json:"automatic-stack-size,omitempty"` // Determine stack size automatically at compile time.
|
||||
DefaultStackSize uint64 `json:"default-stack-size,omitempty"` // Default stack size if the size couldn't be determined at compile time.
|
||||
@@ -67,6 +68,7 @@ type TargetSpec struct {
|
||||
ADBPreCommands []string `json:"adb-pre-commands,omitempty"`
|
||||
ADBPushRemote string `json:"adb-push-remote,omitempty"`
|
||||
ADBPostCommands []string `json:"adb-post-commands,omitempty"`
|
||||
ProbeRSChip string `json:"probe-rs-chip,omitempty"`
|
||||
CodeModel string `json:"code-model,omitempty"`
|
||||
RelocationModel string `json:"relocation-model,omitempty"`
|
||||
WITPackage string `json:"wit-package,omitempty"`
|
||||
@@ -484,6 +486,8 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
|
||||
"--no-insert-timestamp",
|
||||
"--no-dynamicbase",
|
||||
)
|
||||
spec.ExtraFiles = append(spec.ExtraFiles,
|
||||
"src/runtime/runtime_windows.c")
|
||||
case "wasm", "wasip1", "wasip2":
|
||||
return nil, fmt.Errorf("GOOS=%s but GOARCH is unset. Please set GOARCH to wasm", options.GOOS)
|
||||
default:
|
||||
|
||||
@@ -112,3 +112,52 @@ 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+21
-8
@@ -241,24 +241,37 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
||||
}
|
||||
}
|
||||
|
||||
// Put the fault block at the end of the function and the next block at the
|
||||
// current insert position.
|
||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||
faultBlock := b.getRuntimeAssertBlock(blockPrefix, assertFunc)
|
||||
nextBlock := b.insertBasicBlock(blockPrefix + ".next")
|
||||
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now branch to the out-of-bounds or the regular block.
|
||||
b.CreateCondBr(assert, faultBlock, nextBlock)
|
||||
|
||||
// Fail: the assert triggered so panic.
|
||||
b.SetInsertPointAtEnd(faultBlock)
|
||||
b.createRuntimeCall(assertFunc, nil, "")
|
||||
b.CreateUnreachable()
|
||||
|
||||
// Ok: assert didn't trigger so continue normally.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
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.CreateUnreachable()
|
||||
b.SetInsertPointAtEnd(savedBlock)
|
||||
return block
|
||||
}
|
||||
|
||||
// extendInteger extends the value to at least targetType using a zero or sign
|
||||
// extend. The resulting value is not truncated: it may still be bigger than
|
||||
// targetType.
|
||||
|
||||
+2
-2
@@ -48,7 +48,7 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
|
||||
// Do the send.
|
||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
|
||||
// End the lifetime of the allocas.
|
||||
// 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.
|
||||
func (b *builder) createChanClose(ch llvm.Value) {
|
||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
b.createRuntimeInvoke("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
// createSelect emits all IR necessary for a select statements. That's a
|
||||
|
||||
+26
-24
@@ -90,8 +90,10 @@ type compilerContext struct {
|
||||
astComments map[string]*ast.CommentGroup
|
||||
embedGlobals map[string][]*loader.EmbedFile
|
||||
pkg *types.Package
|
||||
packageDir string // directory for this package
|
||||
loaderPkg *loader.Package // current package being compiled (for AST access)
|
||||
packageDir string // directory for this 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
|
||||
@@ -167,7 +169,7 @@ type builder struct {
|
||||
dilocals map[*types.Var]llvm.Metadata
|
||||
initInlinedAt llvm.Metadata // fake inlinedAt position
|
||||
initPseudoFuncs map[string]llvm.Metadata // fake "inlined" functions for proper init debug locations
|
||||
allDeferFuncs []interface{}
|
||||
allDeferFuncs []any
|
||||
deferFuncs map[*ssa.Function]int
|
||||
deferInvokeFuncs map[string]int
|
||||
deferClosureFuncs map[*ssa.Function]int
|
||||
@@ -176,6 +178,9 @@ type builder struct {
|
||||
deferBuiltinFuncs map[ssa.Value]deferBuiltin
|
||||
runDefersBlock []llvm.BasicBlock
|
||||
afterDefersBlock []llvm.BasicBlock
|
||||
|
||||
runtimeAssertBlocks map[string]llvm.BasicBlock
|
||||
interfaceAssertBlock llvm.BasicBlock
|
||||
}
|
||||
|
||||
func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *builder {
|
||||
@@ -192,16 +197,6 @@ 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 {
|
||||
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
|
||||
entry llvm.BasicBlock
|
||||
@@ -304,12 +299,18 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
|
||||
c.packageDir = pkg.OriginalDir()
|
||||
c.embedGlobals = pkg.EmbedGlobals
|
||||
c.pkg = pkg.Pkg
|
||||
c.loaderPkg = pkg
|
||||
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
|
||||
c.program = ssaPkg.Prog
|
||||
|
||||
// Convert AST to SSA.
|
||||
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.
|
||||
if c.Debug {
|
||||
c.cu = c.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
||||
@@ -869,10 +870,9 @@ func (c *compilerContext) createPackage(irbuilder llvm.Builder, pkg *ssa.Package
|
||||
}
|
||||
// Create the function definition.
|
||||
b := newBuilder(c, irbuilder, member)
|
||||
if _, ok := mathToLLVMMapping[member.RelString(nil)]; ok {
|
||||
if ok := b.defineMathOp(); ok {
|
||||
// The body of this function (if there is one) is ignored and
|
||||
// replaced with a LLVM intrinsic call.
|
||||
b.defineMathOp()
|
||||
continue
|
||||
}
|
||||
if ok := b.defineMathBitsIntrinsic(); ok {
|
||||
@@ -1895,6 +1895,12 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||
// not of the current function.
|
||||
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
|
||||
case "ssa:wrapnilchk":
|
||||
// TODO: do an actual nil check?
|
||||
@@ -2150,13 +2156,10 @@ func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
|
||||
if elementSize == 0 {
|
||||
elementSize = 1
|
||||
}
|
||||
maxSize := maxPointerValue / elementSize
|
||||
|
||||
// len(slice) is an int. Make sure the length remains small enough to fit in
|
||||
// an int.
|
||||
if maxSize > maxIntegerValue {
|
||||
maxSize = maxIntegerValue
|
||||
}
|
||||
maxSize := min(
|
||||
// len(slice) is an int. Make sure the length remains small enough to fit in
|
||||
// an int.
|
||||
maxPointerValue/elementSize, maxIntegerValue)
|
||||
|
||||
return maxSize
|
||||
}
|
||||
@@ -2183,9 +2186,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
layoutValue := b.createObjectLayout(typ, expr.Pos())
|
||||
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
|
||||
align := b.targetData.ABITypeAlignment(typ)
|
||||
buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
buf := b.createAlloc(sizeValue, layoutValue, align, expr.Comment)
|
||||
return buf, nil
|
||||
} else {
|
||||
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
|
||||
@@ -2415,7 +2417,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
|
||||
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
|
||||
slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
|
||||
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
|
||||
|
||||
// Extend or truncate if necessary. This is safe as we've already done
|
||||
|
||||
@@ -188,9 +188,9 @@ func TestCompilerErrors(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
|
||||
for _, line := range strings.Split(errorsFileString, "\n") {
|
||||
if strings.HasPrefix(line, "// ERROR: ") {
|
||||
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
|
||||
for line := range strings.SplitSeq(errorsFileString, "\n") {
|
||||
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok {
|
||||
expectedErrors = append(expectedErrors, after)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+33
-12
@@ -60,10 +60,6 @@ func (b *builder) deferInitFunc() {
|
||||
b.deferExprFuncs = make(map[ssa.Value]int)
|
||||
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() {
|
||||
// Set up the defer frame with the current stack pointer.
|
||||
// This assumes that the stack pointer doesn't move outside of the
|
||||
@@ -73,12 +69,22 @@ func (b *builder) deferInitFunc() {
|
||||
// in the setjmp-like inline assembly.
|
||||
deferFrameType := b.getLLVMRuntimeType("deferFrame")
|
||||
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()
|
||||
b.createRuntimeCall("setupDeferFrame", []llvm.Value{b.deferFrame, stackPointer}, "")
|
||||
|
||||
// Create the landing pad block, which is where control transfers after
|
||||
// a panic.
|
||||
b.landingpad = b.ctx.AddBasicBlock(b.llvmFn, "lpad")
|
||||
} else {
|
||||
// Create defer list pointer.
|
||||
b.deferPtr = b.CreateAlloca(b.dataPtrType, "deferPtr")
|
||||
b.CreateStore(llvm.ConstPointerNull(b.dataPtrType), b.deferPtr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,8 +116,8 @@ func (b *builder) createLandingPad() {
|
||||
func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
||||
// Construct inline assembly equivalents of setjmp.
|
||||
// The assembly works as follows:
|
||||
// * All registers (both callee-saved and caller saved) are clobbered
|
||||
// after the inline assembly returns.
|
||||
// * Registers are either clobbered or, on 386, saved for longjmp to
|
||||
// restore if the ABI requires them to survive calls.
|
||||
// * The assembly stores the address just past the end of the assembly
|
||||
// into the jump buffer.
|
||||
// * The return value (eax, rax, r0, etc) is set to zero in the inline
|
||||
@@ -124,8 +130,12 @@ func (b *builder) createCheckpoint(ptr llvm.Value) llvm.Value {
|
||||
asmString = `
|
||||
xorl %eax, %eax
|
||||
movl $$1f, 4(%ebx)
|
||||
movl %ebx, 8(%ebx)
|
||||
movl %esi, 12(%ebx)
|
||||
movl %edi, 16(%ebx)
|
||||
movl %ebp, 20(%ebx)
|
||||
1:`
|
||||
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
||||
constraints = "={eax},{ebx},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
|
||||
// This doesn't include the floating point stack because TinyGo uses
|
||||
// newer floating point instructions.
|
||||
case "x86_64":
|
||||
@@ -237,6 +247,17 @@ func (b *builder) createInvokeCheckpoint() {
|
||||
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.
|
||||
// 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.
|
||||
@@ -488,7 +509,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
size := b.targetData.TypeAllocSize(deferredCallType)
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
nilPtr := llvm.ConstNull(b.dataPtrType)
|
||||
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
||||
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
|
||||
}
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
@@ -652,8 +673,8 @@ func (b *builder) createRunDefers() {
|
||||
fn := callback.Fn.(*ssa.Function)
|
||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
for v := range params.Variables() {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
|
||||
}
|
||||
valueTypes = append(valueTypes, b.dataPtrType) // closure
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
@@ -678,8 +699,8 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
//Get signature from call results
|
||||
params := callback.Type().Underlying().(*types.Signature).Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
for v := range params.Variables() {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
|
||||
}
|
||||
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
+2
-2
@@ -81,8 +81,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
for v := range typ.Params().Variables() {
|
||||
subType := c.getLLVMType(v.Type())
|
||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
|
||||
+30
-8
@@ -5,11 +5,38 @@ package compiler
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"slices"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"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
|
||||
// one of the expressions that need this.
|
||||
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
|
||||
@@ -62,7 +89,7 @@ func (b *builder) trackValue(value llvm.Value) {
|
||||
return
|
||||
}
|
||||
numElements := typ.StructElementTypesCount()
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
@@ -71,7 +98,7 @@ func (b *builder) trackValue(value llvm.Value) {
|
||||
return
|
||||
}
|
||||
numElements := typ.ArrayLength()
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
@@ -92,12 +119,7 @@ func typeHasPointers(t llvm.Type) bool {
|
||||
case llvm.PointerTypeKind:
|
||||
return true
|
||||
case llvm.StructTypeKind:
|
||||
for _, subType := range t.StructElementTypes() {
|
||||
if typeHasPointers(subType) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers)
|
||||
case llvm.ArrayTypeKind:
|
||||
if t.ArrayLength() == 0 {
|
||||
return false
|
||||
|
||||
@@ -97,7 +97,7 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
|
||||
params = append(params, context, funcPtr)
|
||||
hasContext = true
|
||||
prefix = b.fn.RelString(nil)
|
||||
prefix = b.getFunctionInfo(b.fn).linkName
|
||||
}
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
@@ -139,7 +139,7 @@ func (b *builder) createWasmExport() {
|
||||
// Declare the exported function.
|
||||
paramTypes := b.llvmFnType.ParamTypes()
|
||||
exportedFnType := llvm.FunctionType(b.llvmFnType.ReturnType(), paramTypes[:len(paramTypes)-1], false)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
|
||||
exportedFn := llvm.AddFunction(b.mod, b.getFunctionInfo(b.fn).linkName+suffix, exportedFnType)
|
||||
b.addStandardAttributes(exportedFn)
|
||||
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
|
||||
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
|
||||
// that's being wrapped.
|
||||
var callParams []llvm.Value
|
||||
for i := 0; i < numParams; i++ {
|
||||
for i := range numParams {
|
||||
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
|
||||
+12
-10
@@ -146,13 +146,14 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={r0}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={r0}")
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
constraints.WriteString(",0")
|
||||
} else {
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
constraints.WriteString(",{r" + strconv.Itoa(i) + "}")
|
||||
}
|
||||
llvmValue := b.getValue(arg, pos)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
@@ -161,9 +162,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
|
||||
// Implement the ARM calling convention by marking r1-r3 as
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
constraints.WriteString(",~{r1},~{r2},~{r3}")
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
@@ -184,13 +185,14 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={x0}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={x0}")
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
constraints.WriteString(",0")
|
||||
} else {
|
||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
||||
constraints.WriteString(",{x" + strconv.Itoa(i) + "}")
|
||||
}
|
||||
llvmValue := b.getValue(arg, pos)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
@@ -199,9 +201,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
|
||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
||||
// clobbered. x0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
||||
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}")
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
|
||||
+354
-72
@@ -10,6 +10,7 @@ import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -144,8 +145,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
// For a non-interface type, it returns the number of exported methods.
|
||||
// For an interface type, it returns the number of exported and unexported methods.
|
||||
var numMethods int
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
if isInterface || ms.At(i).Obj().Exported() {
|
||||
for method := range ms.Methods() {
|
||||
if isInterface || method.Obj().Exported() {
|
||||
numMethods++
|
||||
}
|
||||
}
|
||||
@@ -165,7 +166,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
typeCodeName, isLocal := getTypeCodeName(typ)
|
||||
typeCodeName, isLocal := c.getTypeCodeName(typ)
|
||||
globalName := "reflect/types.type:" + typeCodeName
|
||||
var global llvm.Value
|
||||
if isLocal {
|
||||
@@ -192,8 +193,8 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
}
|
||||
// Compute the method set value for types that support methods.
|
||||
var methods []*types.Func
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
methods = append(methods, ms.At(i).Obj().(*types.Func))
|
||||
for method := range ms.Methods() {
|
||||
methods = append(methods, method.Obj().(*types.Func))
|
||||
}
|
||||
methodSetType := types.NewStruct([]*types.Var{
|
||||
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
|
||||
@@ -489,10 +490,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
c.getTypeMethodSet(typ),
|
||||
}, typeFields...)
|
||||
}
|
||||
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
|
||||
if alignment < 4 {
|
||||
alignment = 4
|
||||
}
|
||||
alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4)
|
||||
globalValue := c.ctx.ConstStruct(typeFields, false)
|
||||
global.SetInitializer(globalValue)
|
||||
if isLocal {
|
||||
@@ -580,20 +578,50 @@ var basicTypeNames = [...]string{
|
||||
// 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
|
||||
// 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) {
|
||||
case *types.Named:
|
||||
if t.Obj().Parent() != t.Obj().Pkg().Scope() {
|
||||
return "named:" + t.String() + "$local", true
|
||||
tn := t.Obj()
|
||||
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
|
||||
// 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:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal
|
||||
case *types.Basic:
|
||||
return "basic:" + basicTypeNames[t.Kind()], false
|
||||
case *types.Chan:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
var dir string
|
||||
switch t.Dir() {
|
||||
case types.SendOnly:
|
||||
@@ -613,7 +641,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if !token.IsExported(name) {
|
||||
name = t.Method(i).Pkg().Path() + "." + name
|
||||
}
|
||||
s, local := getTypeCodeName(t.Method(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Method(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -621,17 +649,17 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
|
||||
case *types.Map:
|
||||
keyType, keyLocal := getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := getTypeCodeName(t.Elem())
|
||||
keyType, keyLocal := c.getTypeCodeName(t.Key())
|
||||
elemType, elemLocal := c.getTypeCodeName(t.Elem())
|
||||
return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
|
||||
case *types.Pointer:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "pointer:" + s, isLocal
|
||||
case *types.Signature:
|
||||
isLocal := false
|
||||
params := make([]string, t.Params().Len())
|
||||
for i := 0; i < t.Params().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Params().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Params().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -639,7 +667,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
results := make([]string, t.Results().Len())
|
||||
for i := 0; i < t.Results().Len(); i++ {
|
||||
s, local := getTypeCodeName(t.Results().At(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Results().At(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -647,7 +675,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal
|
||||
case *types.Slice:
|
||||
s, isLocal := getTypeCodeName(t.Elem())
|
||||
s, isLocal := c.getTypeCodeName(t.Elem())
|
||||
return "slice:" + s, isLocal
|
||||
case *types.Struct:
|
||||
elems := make([]string, t.NumFields())
|
||||
@@ -657,7 +685,7 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
if t.Field(i).Embedded() {
|
||||
embedded = "#"
|
||||
}
|
||||
s, local := getTypeCodeName(t.Field(i).Type())
|
||||
s, local := c.getTypeCodeName(t.Field(i).Type())
|
||||
if local {
|
||||
isLocal = true
|
||||
}
|
||||
@@ -672,6 +700,232 @@ func getTypeCodeName(t types.Type) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
@@ -682,8 +936,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
|
||||
// Create method set.
|
||||
var signatures, wrappers []llvm.Value
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
for method := range ms.Methods() {
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
signatures = append(signatures, signatureGlobal)
|
||||
fn := c.program.MethodValue(method)
|
||||
@@ -769,7 +1022,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum)
|
||||
}
|
||||
} else {
|
||||
name, _ := getTypeCodeName(expr.AssertedType)
|
||||
name, _ := b.getTypeCodeName(expr.AssertedType)
|
||||
globalName := "reflect/types.typeid:" + name
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
@@ -796,43 +1049,68 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
|
||||
prevBlock := b.GetInsertBlock()
|
||||
okBlock := b.insertBasicBlock("typeassert.ok")
|
||||
nextBlock := b.insertBasicBlock("typeassert.next")
|
||||
b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
// successful.
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
var valueOk llvm.Value
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type. Easy: just return the same
|
||||
// interface value.
|
||||
valueOk = itf
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valueOk = b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
nextBlock := b.insertBasicBlock("typeassert.next")
|
||||
b.currentBlockInfo.exit = nextBlock
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
// successful.
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
var valueOk llvm.Value
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type. Easy: just return the same
|
||||
// interface value.
|
||||
valueOk = itf
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valueOk = b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
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, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
return tuple
|
||||
} else {
|
||||
// This is kind of dirty as the branch above becomes mostly useless,
|
||||
// but hopefully this gets optimized away.
|
||||
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
return phi
|
||||
// Type assert without comma-ok. If it fails, panic.
|
||||
faultBlock := b.getInterfaceAssertBlock()
|
||||
b.currentBlockInfo.exit = okBlock
|
||||
b.CreateCondBr(commaOk, okBlock, faultBlock)
|
||||
|
||||
// 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
|
||||
// attribute for interface functions.
|
||||
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
||||
@@ -857,7 +1135,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
if !token.IsExported(name) {
|
||||
name = method.Pkg().Path() + "." + name
|
||||
}
|
||||
s, _ := getTypeCodeName(method.Type())
|
||||
s, _ := c.getTypeCodeName(method.Type())
|
||||
globalName := "reflect/types.signature:" + name + ":" + s
|
||||
value := c.mod.NamedGlobal(globalName)
|
||||
if value.IsNil() {
|
||||
@@ -901,14 +1179,14 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
fnName := s + "." + instr.Method.Name() + "$invoke"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
sig := instr.Method.Type().(*types.Signature)
|
||||
var paramTuple []*types.Var
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
paramTuple = append(paramTuple, sig.Params().At(i))
|
||||
for v := range sig.Params().Variables() {
|
||||
paramTuple = append(paramTuple, v)
|
||||
}
|
||||
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))
|
||||
@@ -926,7 +1204,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
// by the interface lowering pass as a type-ID comparison chain, avoiding the
|
||||
// need for runtime.typeImplementsMethodSet at compile time.
|
||||
func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value {
|
||||
s, _ := getTypeCodeName(intf)
|
||||
s, _ := b.getTypeCodeName(intf)
|
||||
fnName := s + ".$typeassert"
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
@@ -1025,34 +1303,38 @@ func methodSignature(method *types.Func) string {
|
||||
// () string
|
||||
// (string, int) (int, error)
|
||||
func signature(sig *types.Signature) string {
|
||||
s := ""
|
||||
var s strings.Builder
|
||||
if sig.Params().Len() == 0 {
|
||||
s += "()"
|
||||
s.WriteString("()")
|
||||
} else {
|
||||
s += "("
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
s.WriteString("(")
|
||||
i := 0
|
||||
for v := range sig.Params().Variables() {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
s.WriteString(", ")
|
||||
}
|
||||
s += typestring(sig.Params().At(i).Type())
|
||||
s.WriteString(typestring(v.Type()))
|
||||
i++
|
||||
}
|
||||
s += ")"
|
||||
s.WriteString(")")
|
||||
}
|
||||
if sig.Results().Len() == 0 {
|
||||
// keep as-is
|
||||
} else if sig.Results().Len() == 1 {
|
||||
s += " " + typestring(sig.Results().At(0).Type())
|
||||
s.WriteString(" " + typestring(sig.Results().At(0).Type()))
|
||||
} else {
|
||||
s += " ("
|
||||
for i := 0; i < sig.Results().Len(); i++ {
|
||||
s.WriteString(" (")
|
||||
i := 0
|
||||
for v := range sig.Results().Variables() {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
s.WriteString(", ")
|
||||
}
|
||||
s += typestring(sig.Results().At(i).Type())
|
||||
s.WriteString(typestring(v.Type()))
|
||||
i++
|
||||
}
|
||||
s += ")"
|
||||
s.WriteString(")")
|
||||
}
|
||||
return s
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// typestring returns a stable (human-readable) type string for the given type
|
||||
|
||||
+42
-8
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -166,12 +167,22 @@ func (b *builder) createMachineKeepAliveImpl() {
|
||||
}
|
||||
|
||||
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.Cos": "llvm.cos.f64",
|
||||
"math.Cosh": "llvm.cosh.f64",
|
||||
"math.Exp": "llvm.exp.f64",
|
||||
"math.Exp2": "llvm.exp2.f64",
|
||||
"math.Floor": "llvm.floor.f64",
|
||||
"math.Log": "llvm.log.f64",
|
||||
"math.Sin": "llvm.sin.f64",
|
||||
"math.Sinh": "llvm.sinh.f64",
|
||||
"math.Sqrt": "llvm.sqrt.f64",
|
||||
"math.Tan": "llvm.tan.f64",
|
||||
"math.Tanh": "llvm.tanh.f64",
|
||||
"math.Trunc": "llvm.trunc.f64",
|
||||
}
|
||||
|
||||
@@ -185,18 +196,40 @@ var mathToLLVMMapping = map[string]string{
|
||||
// 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)
|
||||
// more expensive than 32-bit ones.
|
||||
func (b *builder) defineMathOp() {
|
||||
b.createFunctionStart(true)
|
||||
llvmName := mathToLLVMMapping[b.fn.RelString(nil)]
|
||||
if llvmName == "" {
|
||||
panic("unreachable: unknown math operation") // sanity check
|
||||
func (b *builder) defineMathOp() bool {
|
||||
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)
|
||||
llvmFn := b.mod.NamedFunction(llvmName)
|
||||
if llvmFn.IsNil() {
|
||||
// The intrinsic doesn't exist yet, so declare it.
|
||||
// At the moment, all supported intrinsics have the form "double
|
||||
// foo(double %x)" so we can hardcode the signature here.
|
||||
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||
var llvmType llvm.Type
|
||||
switch b.fn.Name() {
|
||||
case "Atan2":
|
||||
// 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)
|
||||
}
|
||||
// Create a call to the intrinsic.
|
||||
@@ -206,6 +239,7 @@ func (b *builder) defineMathOp() {
|
||||
}
|
||||
result := b.CreateCall(llvmFn.GlobalValueType(), llvmFn, args, "")
|
||||
b.CreateRet(result)
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *builder) defineCryptoIntrinsic() bool {
|
||||
|
||||
+4
-8
@@ -129,11 +129,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
align := b.targetData.ABITypeAlignment(packedType)
|
||||
packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.ConstNull(b.dataPtrType),
|
||||
}, "")
|
||||
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(packedAlloc)
|
||||
}
|
||||
@@ -210,7 +206,7 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
|
||||
globalType := llvm.ArrayType(elementType, len(buf))
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
for i := 0; i < len(buf); i++ {
|
||||
for i := range buf {
|
||||
ch := uint64(buf[i])
|
||||
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
|
||||
}
|
||||
@@ -394,7 +390,7 @@ func (c *compilerContext) buildPointerBitmap(
|
||||
return
|
||||
}
|
||||
elementSize /= ptrAlign
|
||||
for i := 0; i < len; i++ {
|
||||
for i := range len {
|
||||
c.buildPointerBitmap(
|
||||
dst,
|
||||
ptrAlign,
|
||||
@@ -421,7 +417,7 @@ func (c *compilerContext) archFamily() string {
|
||||
// features string is not one for an ARM architecture.
|
||||
func (c *compilerContext) isThumb() bool {
|
||||
var isThumb, isNotThumb bool
|
||||
for _, feature := range strings.Split(c.Features, ",") {
|
||||
for feature := range strings.SplitSeq(c.Features, ",") {
|
||||
if feature == "+thumb-mode" {
|
||||
isThumb = true
|
||||
}
|
||||
|
||||
+8
-6
@@ -7,6 +7,7 @@ import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"strings"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
@@ -133,7 +134,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 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valueAlloca}
|
||||
b.createRuntimeCall("hashmapStringSet", params, "")
|
||||
b.createRuntimeInvoke("hashmapStringSet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type.
|
||||
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
@@ -143,7 +144,7 @@ func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value,
|
||||
fnName = "hashmapGenericSet"
|
||||
}
|
||||
params := []llvm.Value{m, keyAlloca, valueAlloca}
|
||||
b.createRuntimeCall(fnName, params, "")
|
||||
b.createRuntimeInvoke(fnName, params, "")
|
||||
b.emitLifetimeEnd(keyAlloca, keySize)
|
||||
}
|
||||
b.emitLifetimeEnd(valueAlloca, valueSize)
|
||||
@@ -293,14 +294,15 @@ func hashmapCanonicalTypeName(t types.Type) string {
|
||||
}
|
||||
return t.String()
|
||||
case *types.Struct:
|
||||
s := "struct{"
|
||||
var s strings.Builder
|
||||
s.WriteString("struct{")
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
if i > 0 {
|
||||
s += "; "
|
||||
s.WriteString("; ")
|
||||
}
|
||||
s += hashmapCanonicalTypeName(t.Field(i).Type())
|
||||
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type()))
|
||||
}
|
||||
return s + "}"
|
||||
return s.String() + "}"
|
||||
case *types.Array:
|
||||
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
|
||||
}
|
||||
|
||||
+1
-2
@@ -29,8 +29,7 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
|
||||
// is the largest of the values unsafe.Alignof(x.f) for each
|
||||
// field f of x, but at least 1."
|
||||
max := int64(1)
|
||||
for i := 0; i < t.NumFields(); i++ {
|
||||
f := t.Field(i)
|
||||
for f := range t.Fields() {
|
||||
if a := s.Alignof(f.Type()); a > max {
|
||||
max = a
|
||||
}
|
||||
|
||||
+130
-26
@@ -8,6 +8,8 @@ import (
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -85,8 +87,8 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
|
||||
} else {
|
||||
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
|
||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
||||
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
|
||||
for v := range fn.Signature.Results().Variables() {
|
||||
results = append(results, c.getLLVMType(v.Type()))
|
||||
}
|
||||
retType = c.ctx.StructType(results, false)
|
||||
}
|
||||
@@ -162,7 +164,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.alloc", "runtime.alloc_noheap":
|
||||
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
|
||||
// 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.
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
@@ -322,6 +324,12 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
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.
|
||||
if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
|
||||
info.linkName = "_initialize"
|
||||
@@ -346,6 +354,42 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
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
|
||||
// //export or //go:noinline.
|
||||
func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
@@ -372,6 +416,51 @@ 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.
|
||||
for _, comment := range pragmas {
|
||||
parts := strings.Fields(comment.Text)
|
||||
@@ -389,7 +478,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
info.wasmName = info.linkName
|
||||
info.exported = true
|
||||
case "//go:interrupt":
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
info.interrupt = true
|
||||
}
|
||||
case "//go:wasm-module":
|
||||
@@ -451,14 +540,14 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
// Only enable go:section when the package imports "unsafe".
|
||||
// go:section also implies go:noinline since inlining could
|
||||
// move the code to a different section than that requested.
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
if len(parts) == 2 && slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
info.section = parts[1]
|
||||
info.inline = inlineNone
|
||||
}
|
||||
@@ -467,7 +556,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:noescape":
|
||||
@@ -567,8 +656,8 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
|
||||
hasHostLayout = false // package structs added in go1.23
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.NumFields(); i++ {
|
||||
ftyp := typ.Field(i).Type()
|
||||
for field := range typ.Fields() {
|
||||
ftyp := field.Type()
|
||||
if types.Unalias(ftyp).String() == "structs.HostLayout" {
|
||||
hasHostLayout = true
|
||||
continue
|
||||
@@ -600,8 +689,8 @@ func getParams(sig *types.Signature) []*types.Var {
|
||||
if sig.Recv() != nil {
|
||||
params = append(params, sig.Recv())
|
||||
}
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
params = append(params, sig.Params().At(i))
|
||||
for v := range sig.Params().Variables() {
|
||||
params = append(params, v)
|
||||
}
|
||||
return params
|
||||
}
|
||||
@@ -666,6 +755,34 @@ type globalInfo struct {
|
||||
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
|
||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||
@@ -704,10 +821,7 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
|
||||
// Set alignment from the //go:align comment.
|
||||
alignment := c.targetData.ABITypeAlignment(llvmType)
|
||||
if info.align > alignment {
|
||||
alignment = info.align
|
||||
}
|
||||
alignment := max(info.align, c.targetData.ABITypeAlignment(llvmType))
|
||||
if alignment <= 0 || alignment&(alignment-1) != 0 {
|
||||
// Check for power-of-two (or 0).
|
||||
// See: https://stackoverflow.com/a/108360
|
||||
@@ -781,7 +895,7 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext,
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(g.Pkg.Pkg) {
|
||||
if slices.Contains(g.Pkg.Pkg.Imports(), types.Unsafe) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
}
|
||||
@@ -797,13 +911,3 @@ func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
+32
-27
@@ -26,24 +26,25 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
constraints := "={rax},0"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={rax},0")
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
constraints.WriteString("," + [...]string{
|
||||
"{rdi}",
|
||||
"{rsi}",
|
||||
"{rdx}",
|
||||
"{r10}",
|
||||
"{r8}",
|
||||
"{r9}",
|
||||
}[i]
|
||||
}[i])
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// rcx and r11 are clobbered by the syscall, so make sure they are not used
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
constraints.WriteString(",~{rcx},~{r11}")
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
||||
@@ -55,22 +56,23 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
||||
constraints := "={eax},0"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={eax},0")
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
constraints.WriteString("," + [...]string{
|
||||
"{ebx}",
|
||||
"{ecx}",
|
||||
"{edx}",
|
||||
"{esi}",
|
||||
"{edi}",
|
||||
"{ebp}",
|
||||
}[i]
|
||||
}[i])
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
||||
@@ -88,9 +90,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={r0},0,{r1},{r2},{r7},~{r3}
|
||||
constraints := "={r0}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={r0}")
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
constraints.WriteString("," + [...]string{
|
||||
"0", // tie to output
|
||||
"{r1}",
|
||||
"{r2}",
|
||||
@@ -98,20 +101,20 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"{r4}",
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
}[i])
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
constraints.WriteString(",{r7}") // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
constraints.WriteString(",~{r" + strconv.Itoa(i) + "}")
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
||||
@@ -120,31 +123,32 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes := []llvm.Type{}
|
||||
// Constraints will look something like:
|
||||
// ={x0},0,{x1},{x2},{x8},~{x3},~{x4},~{x5},~{x6},~{x7},~{x16},~{x17}
|
||||
constraints := "={x0}"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={x0}")
|
||||
for i, arg := range call.Args[1:] {
|
||||
constraints += "," + [...]string{
|
||||
constraints.WriteString("," + [...]string{
|
||||
"0", // tie to output
|
||||
"{x1}",
|
||||
"{x2}",
|
||||
"{x3}",
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
}[i])
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
constraints.WriteString(",{x8}") // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
// calling convention.
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
constraints.WriteString(",~{x" + strconv.Itoa(i) + "}")
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
constraints.WriteString(",~{x16},~{x17}") // scratch registers
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
|
||||
return b.CreateCall(fnType, target, args, ""), nil
|
||||
|
||||
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
|
||||
@@ -163,7 +167,8 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
// faster and smaller code.
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
constraints := "={$2},={$7},0"
|
||||
var constraints strings.Builder
|
||||
constraints.WriteString("={$2},={$7},0")
|
||||
syscallParams := call.Args[1:]
|
||||
if len(syscallParams) > 7 {
|
||||
// There is one syscall that uses 7 parameters: sync_file_range.
|
||||
@@ -172,7 +177,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallParams = syscallParams[:7]
|
||||
}
|
||||
for i, arg := range syscallParams {
|
||||
constraints += "," + [...]string{
|
||||
constraints.WriteString("," + [...]string{
|
||||
"{$4}", // arg1
|
||||
"{$5}", // arg2
|
||||
"{$6}", // arg3
|
||||
@@ -180,7 +185,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"r", // arg5 on the stack
|
||||
"r", // arg6 on the stack
|
||||
"r", // arg7 on the stack
|
||||
}[i]
|
||||
}[i])
|
||||
llvmValue := b.getValue(arg, getPos(call))
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
@@ -221,10 +226,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
"addu $$sp, $$sp, 32\n" +
|
||||
".set at\n"
|
||||
}
|
||||
constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
|
||||
constraints.WriteString(",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}")
|
||||
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
|
||||
fnType := llvm.FunctionType(returnType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, true, 0, false)
|
||||
call := b.CreateCall(fnType, target, args, "")
|
||||
resultCode := b.CreateExtractValue(call, 0, "") // r2
|
||||
errorFlag := b.CreateExtractValue(call, 1, "") // r7
|
||||
|
||||
+14
-17
@@ -3,7 +3,7 @@ source_filename = "defer.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "thumbv7m-unknown-unknown-eabi"
|
||||
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface }
|
||||
%runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i8, %runtime._interface, ptr }
|
||||
%runtime._interface = type { ptr, ptr }
|
||||
|
||||
; Function Attrs: nounwind
|
||||
@@ -18,14 +18,14 @@ declare void @main.external(ptr) #1
|
||||
define hidden void @main.deferSimple(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%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
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
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
|
||||
%defer.alloca.repack15 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
|
||||
store ptr null, ptr %defer.alloca.repack15, align 4
|
||||
store ptr %defer.next, ptr %defer.alloca.repack15, 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.result = icmp eq i32 %setjmp, 0
|
||||
@@ -111,9 +111,9 @@ rundefers.end3: ; preds = %rundefers.loophead6
|
||||
; Function Attrs: nocallback nofree nosync nounwind willreturn
|
||||
declare ptr @llvm.stacksave.p0() #2
|
||||
|
||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(24), ptr, ptr) #1
|
||||
declare void @runtime.setupDeferFrame(ptr dereferenceable_or_null(28), ptr, ptr) #1
|
||||
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(24), ptr) #1
|
||||
declare void @runtime.destroyDeferFrame(ptr dereferenceable_or_null(28), ptr) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
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:
|
||||
%defer.alloca2 = 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
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
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
|
||||
%defer.alloca.repack22 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
|
||||
store ptr null, ptr %defer.alloca.repack22, align 4
|
||||
store ptr %defer.next, ptr %defer.alloca.repack22, align 4
|
||||
store ptr %defer.alloca, ptr %deferPtr, align 4
|
||||
store i32 1, ptr %defer.alloca2, align 4
|
||||
%defer.alloca2.repack23 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
|
||||
store ptr %defer.alloca, ptr %defer.alloca2.repack23, align 4
|
||||
%defer.alloca2.repack24 = getelementptr inbounds nuw i8, ptr %defer.alloca2, i32 4
|
||||
store ptr %defer.alloca, ptr %defer.alloca2.repack24, 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.result = icmp eq i32 %setjmp, 0
|
||||
@@ -270,9 +270,8 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferInfiniteLoop(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, 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()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.body
|
||||
@@ -318,9 +317,8 @@ declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.deferLoop(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%deferPtr = alloca ptr, align 4
|
||||
store ptr null, ptr %deferPtr, 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()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.loop
|
||||
@@ -408,9 +406,8 @@ rundefers.end1: ; preds = %rundefers.loophead4
|
||||
define hidden void @main.deferBetweenLoops(ptr %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%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
|
||||
%deferPtr = getelementptr inbounds nuw i8, ptr %deferframe.buf, i32 24
|
||||
%0 = call ptr @llvm.stacksave.p0()
|
||||
call void @runtime.setupDeferFrame(ptr nonnull %deferframe.buf, ptr %0, ptr undef) #4
|
||||
br label %for.loop
|
||||
|
||||
Vendored
+4
-1
@@ -75,7 +75,7 @@ entry:
|
||||
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
%stackalloc = alloca i8, align 1
|
||||
%new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||
%new = call align 1 ptr @runtime.alloc_zero(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
|
||||
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
|
||||
@@ -93,6 +93,9 @@ entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
|
||||
Vendored
+30
-60
@@ -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
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
store float %a.X, ptr %a, align 4
|
||||
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store float %a.Y, ptr %a.repack9, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store float %a.Y, ptr %a.repack5, align 4
|
||||
%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
|
||||
store float %b.X, ptr %b, align 4
|
||||
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store float %b.Y, ptr %b.repack11, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store float %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, 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
|
||||
@@ -43,29 +43,29 @@ entry:
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
br i1 false, label %deref.throw1, label %deref.next2
|
||||
br i1 false, label %deref.throw, label %deref.next1
|
||||
|
||||
deref.next2: ; preds = %deref.next
|
||||
deref.next1: ; preds = %deref.next
|
||||
%0 = load float, ptr %a, align 4
|
||||
%1 = load float, ptr %b, align 4
|
||||
%2 = fadd float %0, %1
|
||||
br i1 false, label %deref.throw3, label %deref.next4
|
||||
br i1 false, label %deref.throw, label %deref.next2
|
||||
|
||||
deref.next4: ; preds = %deref.next2
|
||||
br i1 false, label %deref.throw5, label %deref.next6
|
||||
deref.next2: ; preds = %deref.next1
|
||||
br i1 false, label %deref.throw, label %deref.next3
|
||||
|
||||
deref.next6: ; preds = %deref.next4
|
||||
deref.next3: ; preds = %deref.next2
|
||||
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
%5 = load float, ptr %4, align 4
|
||||
%6 = load float, ptr %3, align 4
|
||||
br i1 false, label %store.throw, label %store.next
|
||||
br i1 false, label %deref.throw, label %store.next
|
||||
|
||||
store.next: ; preds = %deref.next6
|
||||
store.next: ; preds = %deref.next3
|
||||
store float %2, ptr %complit, align 4
|
||||
br i1 false, label %store.throw7, label %store.next8
|
||||
br i1 false, label %deref.throw, label %store.next4
|
||||
|
||||
store.next8: ; preds = %store.next
|
||||
store.next4: ; preds = %store.next
|
||||
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
|
||||
%8 = fadd float %5, %6
|
||||
store float %8, ptr %7, align 4
|
||||
@@ -74,22 +74,7 @@ store.next8: ; preds = %store.next
|
||||
%10 = insertvalue %"main.Point[float32]" %9, float %8, 1
|
||||
ret %"main.Point[float32]" %10
|
||||
|
||||
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
|
||||
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
|
||||
unreachable
|
||||
}
|
||||
|
||||
@@ -107,13 +92,13 @@ entry:
|
||||
%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
|
||||
store i32 %a.X, ptr %a, align 4
|
||||
%a.repack9 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store i32 %a.Y, ptr %a.repack9, align 4
|
||||
%a.repack5 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
store i32 %a.Y, ptr %a.repack5, align 4
|
||||
%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
|
||||
store i32 %b.X, ptr %b, align 4
|
||||
%b.repack11 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store i32 %b.Y, ptr %b.repack11, align 4
|
||||
%b.repack7 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
store i32 %b.Y, ptr %b.repack7, align 4
|
||||
call void @main.checkSize(i32 4, 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
|
||||
@@ -121,29 +106,29 @@ entry:
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
br i1 false, label %deref.throw1, label %deref.next2
|
||||
br i1 false, label %deref.throw, label %deref.next1
|
||||
|
||||
deref.next2: ; preds = %deref.next
|
||||
deref.next1: ; preds = %deref.next
|
||||
%0 = load i32, ptr %a, align 4
|
||||
%1 = load i32, ptr %b, align 4
|
||||
%2 = add i32 %0, %1
|
||||
br i1 false, label %deref.throw3, label %deref.next4
|
||||
br i1 false, label %deref.throw, label %deref.next2
|
||||
|
||||
deref.next4: ; preds = %deref.next2
|
||||
br i1 false, label %deref.throw5, label %deref.next6
|
||||
deref.next2: ; preds = %deref.next1
|
||||
br i1 false, label %deref.throw, label %deref.next3
|
||||
|
||||
deref.next6: ; preds = %deref.next4
|
||||
deref.next3: ; preds = %deref.next2
|
||||
%3 = getelementptr inbounds nuw i8, ptr %b, i32 4
|
||||
%4 = getelementptr inbounds nuw i8, ptr %a, i32 4
|
||||
%5 = load i32, ptr %4, align 4
|
||||
%6 = load i32, ptr %3, align 4
|
||||
br i1 false, label %store.throw, label %store.next
|
||||
br i1 false, label %deref.throw, label %store.next
|
||||
|
||||
store.next: ; preds = %deref.next6
|
||||
store.next: ; preds = %deref.next3
|
||||
store i32 %2, ptr %complit, align 4
|
||||
br i1 false, label %store.throw7, label %store.next8
|
||||
br i1 false, label %deref.throw, label %store.next4
|
||||
|
||||
store.next8: ; preds = %store.next
|
||||
store.next4: ; preds = %store.next
|
||||
%7 = getelementptr inbounds nuw i8, ptr %complit, i32 4
|
||||
%8 = add i32 %5, %6
|
||||
store i32 %8, ptr %7, align 4
|
||||
@@ -152,22 +137,7 @@ store.next8: ; preds = %store.next
|
||||
%10 = insertvalue %"main.Point[int]" %9, i32 %8, 1
|
||||
ret %"main.Point[int]" %10
|
||||
|
||||
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
|
||||
deref.throw: ; preds = %store.next, %deref.next3, %deref.next2, %deref.next1, %deref.next, %entry
|
||||
unreachable
|
||||
}
|
||||
|
||||
|
||||
Vendored
+28
@@ -120,3 +120,31 @@ func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
|
||||
func doesHeapAlloc() *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() {
|
||||
}
|
||||
|
||||
Vendored
+20
@@ -102,6 +102,26 @@ entry:
|
||||
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||
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 #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" }
|
||||
|
||||
@@ -59,7 +59,6 @@ func TestCorpus(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
repo := repo
|
||||
name := repo.Repo
|
||||
if repo.Tags != "" {
|
||||
name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")"
|
||||
@@ -132,7 +131,6 @@ func TestCorpus(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, dir := range repo.Subdirs {
|
||||
dir := dir
|
||||
t.Run(dir.Pkg, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -116,10 +116,7 @@ func Diff(oldName string, old []byte, newName string, new []byte) []byte {
|
||||
|
||||
// End chunk with common lines for context.
|
||||
if len(ctext) > 0 {
|
||||
n := end.x - start.x
|
||||
if n > C {
|
||||
n = C
|
||||
}
|
||||
n := min(end.x-start.x, C)
|
||||
for _, s := range x[start.x : start.x+n] {
|
||||
ctext = append(ctext, " "+s)
|
||||
count.x++
|
||||
@@ -234,7 +231,7 @@ func tgs(x, y []string) []pair {
|
||||
for i := range T {
|
||||
T[i] = n + 1
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
k := sort.Search(n, func(k int) bool {
|
||||
return T[k] >= J[i]
|
||||
})
|
||||
|
||||
+1
-1
@@ -136,7 +136,7 @@ func readErrorMessages(t *testing.T, file string) string {
|
||||
}
|
||||
|
||||
var errors []string
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
for line := range strings.SplitSeq(string(data), "\n") {
|
||||
if strings.HasPrefix(line, "// ERROR: ") {
|
||||
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.23.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
|
||||
@@ -18,11 +18,11 @@ require (
|
||||
go.bug.st/serial v1.6.4
|
||||
go.bytecodealliance.org v0.6.2
|
||||
go.bytecodealliance.org/cm v0.2.2
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
golang.org/x/tools v0.30.0
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/tools v0.42.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
tinygo.org/x/espflasher v0.6.0
|
||||
tinygo.org/x/espflasher v0.6.1
|
||||
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/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/urfave/cli/v3 v3.0.0-beta1 // indirect
|
||||
golang.org/x/mod v0.23.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/mod v0.33.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
|
||||
@@ -23,6 +23,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||
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/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/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||
@@ -93,24 +95,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/cm v0.2.2 h1:M9iHS6qs884mbQbIjtLX1OifgyPG9DuMs2iwz8G4WQA=
|
||||
go.bytecodealliance.org/cm v0.2.2/go.mod h1:JD5vtVNZv7sBoQQkvBvAAVKJPhR/bqBH7yYXTItMfZI=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY=
|
||||
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
|
||||
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
|
||||
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/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
@@ -118,7 +120,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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/espflasher v0.6.0 h1:CHbGMHAIWq1tB8FKd/QwBpNFw1jvHHxpmvZiF+QOYUo=
|
||||
tinygo.org/x/espflasher v0.6.0/go.mod h1:tr5u08HoE67WD5zxJesCiiVF/R1b6Akz3yXwh5zah8U=
|
||||
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs=
|
||||
tinygo.org/x/espflasher v0.6.1/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/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
|
||||
|
||||
@@ -151,6 +151,8 @@ func Get(name string) string {
|
||||
panic("could not find cache dir: " + err.Error())
|
||||
}
|
||||
return filepath.Join(dir, "tinygo")
|
||||
case "CGO_CFLAGS":
|
||||
return os.Getenv("CGO_CFLAGS")
|
||||
case "CGO_ENABLED":
|
||||
// Always enable CGo. It is required by a number of targets, including
|
||||
// macOS and the rp2040.
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
|
||||
case llvm.PHI:
|
||||
inst.name = llvmInst.Name()
|
||||
incomingCount := inst.llvmInst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
for i := range incomingCount {
|
||||
incomingBB := inst.llvmInst.IncomingBlock(i)
|
||||
incomingValue := inst.llvmInst.IncomingValue(i)
|
||||
inst.operands = append(inst.operands,
|
||||
|
||||
@@ -18,9 +18,10 @@ func TestInterp(t *testing.T) {
|
||||
"copy",
|
||||
"interface",
|
||||
"revert",
|
||||
"store",
|
||||
"alloc",
|
||||
"slicedata",
|
||||
} {
|
||||
name := name // make local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
runTest(t, "testdata/"+name)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -299,7 +300,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// means that monotonic time in the time package is counted from
|
||||
// time.Time{}.Sub(1), which should be fine.
|
||||
locals[inst.localIndex] = literalValue{uint64(0)}
|
||||
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
|
||||
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap" || callFn.name == "runtime.alloc_zero":
|
||||
// Allocate heap memory. At compile time, this is instead done
|
||||
// by creating a global variable.
|
||||
|
||||
@@ -470,7 +471,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
||||
// should be returned.
|
||||
numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue())
|
||||
var method llvm.Value
|
||||
for i := 0; i < numMethods; i++ {
|
||||
for i := range numMethods {
|
||||
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
|
||||
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
|
||||
if methodSignature == signature {
|
||||
@@ -907,7 +908,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 {
|
||||
numOperands := inst.llvmInst.OperandsCount()
|
||||
operands := make([]llvm.Value, numOperands)
|
||||
for i := 0; i < numOperands; i++ {
|
||||
for i := range numOperands {
|
||||
operand := inst.llvmInst.Operand(i)
|
||||
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
||||
var err error
|
||||
@@ -985,9 +986,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
|
||||
mem.instructions = append(mem.instructions, agg)
|
||||
}
|
||||
result = operands[1]
|
||||
for i := len(indices) - 1; i >= 0; i-- {
|
||||
for i, indice := range slices.Backward(indices) {
|
||||
agg := aggregates[i]
|
||||
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
|
||||
result = r.builder.CreateInsertValue(agg, result, int(indice), 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
|
||||
mem.instructions = append(mem.instructions, result)
|
||||
}
|
||||
|
||||
+47
-26
@@ -18,8 +18,10 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"math"
|
||||
"math/big"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -80,9 +82,7 @@ func (mv *memoryView) extend(sub memoryView) {
|
||||
if mv.objects == nil && len(sub.objects) != 0 {
|
||||
mv.objects = make(map[uint32]object)
|
||||
}
|
||||
for key, value := range sub.objects {
|
||||
mv.objects[key] = value
|
||||
}
|
||||
maps.Copy(mv.objects, sub.objects)
|
||||
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.
|
||||
func (mv *memoryView) revert() {
|
||||
// Erase instructions in reverse order.
|
||||
for i := len(mv.instructions) - 1; i >= 0; i-- {
|
||||
llvmInst := mv.instructions[i]
|
||||
for _, llvmInst := range slices.Backward(mv.instructions) {
|
||||
|
||||
if llvmInst.IsAInstruction().IsNil() {
|
||||
// The IR builder will try to create constant versions of
|
||||
// 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
|
||||
}
|
||||
numOperands := inst.OperandsCount()
|
||||
for i := 0; i < numOperands; i++ {
|
||||
for i := range numOperands {
|
||||
// Using mark '2' (which means read/write access)
|
||||
// because this might be a store instruction.
|
||||
err := mv.markExternal(inst.Operand(i), 2)
|
||||
@@ -215,7 +215,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
|
||||
// need any marking.
|
||||
case llvm.StructTypeKind:
|
||||
numElements := llvmType.StructElementTypesCount()
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
|
||||
err := mv.markExternal(element, mark)
|
||||
if err != nil {
|
||||
@@ -224,7 +224,7 @@ func (mv *memoryView) markExternal(llvmValue llvm.Value, mark uint8) error {
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
numElements := llvmType.ArrayLength()
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
|
||||
err := mv.markExternal(element, mark)
|
||||
if err != nil {
|
||||
@@ -299,7 +299,8 @@ func (mv *memoryView) put(index uint32, obj object) {
|
||||
}
|
||||
|
||||
// Load the value behind the given pointer. Returns nil if the pointer points to
|
||||
// an external global.
|
||||
// an external global or if the load is out of bounds of the object (in which
|
||||
// case the caller defers the load to runtime).
|
||||
func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if checks && mv.hasExternalStore(p) {
|
||||
panic("interp: load from object with external store")
|
||||
@@ -312,14 +313,23 @@ func (mv *memoryView) load(p pointerValue, size uint32) value {
|
||||
if p.offset() == 0 && size == obj.size {
|
||||
return obj.buffer.clone()
|
||||
}
|
||||
if checks && p.offset()+size > obj.size {
|
||||
panic("interp: load out of bounds")
|
||||
if p.offset()+size > obj.size {
|
||||
// The load is out of bounds of the object. This can happen for valid
|
||||
// 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)
|
||||
loadedValue := rawValue{
|
||||
buf: v.buf[p.offset() : p.offset()+size],
|
||||
loadedBuf := v.buf[p.offset() : p.offset()+size]
|
||||
if _, writable := mv.objects[p.index()]; writable {
|
||||
// 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 loadedValue
|
||||
return rawValue{buf: loadedBuf}
|
||||
}
|
||||
|
||||
// Store to the value behind the given pointer. This overwrites the value in the
|
||||
@@ -330,26 +340,37 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
|
||||
if checks && mv.hasExternalLoadOrStore(p) {
|
||||
panic("interp: store to object with external load/store")
|
||||
}
|
||||
obj := mv.get(p.index())
|
||||
index := 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 {
|
||||
// External global, return false (for a failure).
|
||||
return false
|
||||
}
|
||||
if checks && p.offset()+v.len(mv.r) > obj.size {
|
||||
valueLen := v.len(mv.r)
|
||||
if checks && p.offset()+valueLen > obj.size {
|
||||
panic("interp: store out of bounds")
|
||||
}
|
||||
if p.offset() == 0 && v.len(mv.r) == obj.buffer.len(mv.r) {
|
||||
obj.buffer = v
|
||||
if p.offset() == 0 && valueLen == obj.buffer.len(mv.r) {
|
||||
obj.buffer = v.clone()
|
||||
} else {
|
||||
obj = obj.clone()
|
||||
if !writable {
|
||||
obj = obj.clone()
|
||||
}
|
||||
buffer := obj.buffer.asRawValue(mv.r)
|
||||
obj.buffer = buffer
|
||||
v := v.asRawValue(mv.r)
|
||||
for i := uint32(0); i < v.len(mv.r); i++ {
|
||||
for i := range valueLen {
|
||||
buffer.buf[p.offset()+i] = v.buf[i]
|
||||
}
|
||||
}
|
||||
mv.put(p.index(), obj)
|
||||
mv.put(index, obj)
|
||||
return true // success
|
||||
}
|
||||
|
||||
@@ -371,7 +392,7 @@ type value interface {
|
||||
// literalValue contains simple integer values that don't need to be stored in a
|
||||
// buffer.
|
||||
type literalValue struct {
|
||||
value interface{}
|
||||
value any
|
||||
}
|
||||
|
||||
// Make a literalValue given the number of bits.
|
||||
@@ -994,7 +1015,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for i := uint32(0); i < ptrSize; i++ {
|
||||
for i := range ptrSize {
|
||||
v.buf[i] = ptr.pointer
|
||||
}
|
||||
} else if !llvmValue.IsAConstantExpr().IsNil() {
|
||||
@@ -1035,7 +1056,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
panic(err)
|
||||
}
|
||||
ptrValue.pointer += totalOffset
|
||||
for i := uint32(0); i < ptrSize; i++ {
|
||||
for i := range ptrSize {
|
||||
v.buf[i] = ptrValue.pointer
|
||||
}
|
||||
case llvm.ICmp:
|
||||
@@ -1092,7 +1113,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
}
|
||||
case llvm.StructTypeKind:
|
||||
numElements := llvmType.StructElementTypesCount()
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
offset := r.targetData.ElementOffset(llvmType, i)
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
@@ -1103,7 +1124,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
|
||||
numElements := llvmType.ArrayLength()
|
||||
childType := llvmType.ElementType()
|
||||
childTypeSize := r.targetData.TypeAllocSize(childType)
|
||||
for i := 0; i < numElements; i++ {
|
||||
for i := range numElements {
|
||||
offset := i * int(childTypeSize)
|
||||
field := rawValue{
|
||||
buf: v.buf[offset:],
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
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
|
||||
}
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
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
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
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
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
||||
@@ -246,6 +246,7 @@ func pathsToOverride(goMinor int, needsSyscallPackage bool) map[string]bool {
|
||||
"internal/futex/": false,
|
||||
"internal/fuzz/": false,
|
||||
"internal/itoa/": false,
|
||||
"internal/poll/": false,
|
||||
"internal/reflectlite/": false,
|
||||
"internal/gclayout": false,
|
||||
"internal/task/": false,
|
||||
|
||||
+8
-4
@@ -22,13 +22,12 @@ import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/google/shlex"
|
||||
"github.com/tinygo-org/tinygo/cgo"
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"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.
|
||||
type Program struct {
|
||||
config *compileopts.Config
|
||||
@@ -435,7 +434,7 @@ func (p *Package) Check() error {
|
||||
}
|
||||
checker.GoVersion = fmt.Sprintf("go%d.%d", major, minor)
|
||||
}
|
||||
initFileVersions(&p.info)
|
||||
p.info.FileVersions = make(map[*ast.File]string)
|
||||
|
||||
// Do typechecking of the package.
|
||||
packageName := p.ImportPath
|
||||
@@ -485,7 +484,7 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
if !filepath.IsAbs(file) {
|
||||
file = filepath.Join(p.Dir, file)
|
||||
}
|
||||
f, err := p.parseFile(file, parser.ParseComments)
|
||||
f, err := p.parseFile(file, parser.ParseComments|parser.SkipObjectResolution)
|
||||
if err != nil {
|
||||
fileErrs = append(fileErrs, err)
|
||||
return
|
||||
@@ -508,6 +507,11 @@ func (p *Package) parseFiles() ([]*ast.File, error) {
|
||||
var initialCFlags []string
|
||||
initialCFlags = append(initialCFlags, p.program.config.CFlags(true)...)
|
||||
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())
|
||||
p.CFlags = append(initialCFlags, cflags...)
|
||||
p.CGoHeaders = headerCode
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//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)
|
||||
}
|
||||
}
|
||||
@@ -209,6 +209,8 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
|
||||
// Test runs the tests in the given package. Returns whether the test passed and
|
||||
// possibly an error if the test failed to run.
|
||||
func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, outpath string) (bool, error) {
|
||||
optionsCopy := *options
|
||||
options = &optionsCopy
|
||||
options.TestConfig.CompileTestBinary = true
|
||||
config, err := builder.NewConfig(options)
|
||||
if err != nil {
|
||||
@@ -395,7 +397,7 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
|
||||
fileExt = filepath.Ext(config.Target.FlashFilename)
|
||||
case "openocd":
|
||||
fileExt = ".hex"
|
||||
case "bmp":
|
||||
case "bmp", "probe-rs":
|
||||
fileExt = ".elf"
|
||||
case "adb":
|
||||
fileExt = ".hex"
|
||||
@@ -535,6 +537,16 @@ func Flash(pkgName, port, outpath string, options *compileopts.Options) error {
|
||||
if err != nil {
|
||||
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":
|
||||
// Run pre-flash adb shell commands.
|
||||
for _, preCmd := range config.Target.ADBPreCommands {
|
||||
@@ -697,6 +709,21 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
|
||||
daemon.Stdout = 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":
|
||||
port = ":2331"
|
||||
gdbCommands = append(gdbCommands, "load", "monitor reset halt")
|
||||
@@ -759,15 +786,15 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
|
||||
}
|
||||
defer func() {
|
||||
daemon.Process.Signal(os.Interrupt)
|
||||
var stopped uint32
|
||||
var stopped atomic.Uint32
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if atomic.LoadUint32(&stopped) == 0 {
|
||||
if stopped.Load() == 0 {
|
||||
daemon.Process.Kill()
|
||||
}
|
||||
}()
|
||||
daemon.Wait()
|
||||
atomic.StoreUint32(&stopped, 1)
|
||||
stopped.Store(1)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -1019,7 +1046,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
|
||||
|
||||
func touchSerialPortAt1200bps(port string) (err error) {
|
||||
retryCount := 3
|
||||
for i := 0; i < retryCount; i++ {
|
||||
for range retryCount {
|
||||
// Open port
|
||||
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
|
||||
if e != nil {
|
||||
@@ -1217,7 +1244,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not list mount points: %w", err)
|
||||
}
|
||||
for _, line := range strings.Split(string(tab), "\n") {
|
||||
for line := range strings.SplitSeq(string(tab), "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) <= 2 {
|
||||
continue
|
||||
@@ -1246,7 +1273,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
|
||||
}
|
||||
|
||||
// Extract data to convert to a []mountPoint slice.
|
||||
for _, line := range strings.Split(out.String(), "\n") {
|
||||
for line := range strings.SplitSeq(out.String(), "\n") {
|
||||
words := strings.Fields(line)
|
||||
if len(words) < 3 {
|
||||
continue
|
||||
@@ -1658,18 +1685,18 @@ func (m globalValuesFlag) String() string {
|
||||
}
|
||||
|
||||
func (m globalValuesFlag) Set(value string) error {
|
||||
equalsIndex := strings.IndexByte(value, '=')
|
||||
if equalsIndex < 0 {
|
||||
before, after, ok := strings.Cut(value, "=")
|
||||
if !ok {
|
||||
return errors.New("expected format pkgpath.Var=value")
|
||||
}
|
||||
pathAndName := value[:equalsIndex]
|
||||
pathAndName := before
|
||||
pointIndex := strings.LastIndexByte(pathAndName, '.')
|
||||
if pointIndex < 0 {
|
||||
return errors.New("expected format pkgpath.Var=value")
|
||||
}
|
||||
path := pathAndName[:pointIndex]
|
||||
name := pathAndName[pointIndex+1:]
|
||||
stringValue := value[equalsIndex+1:]
|
||||
stringValue := after
|
||||
if m[path] == nil {
|
||||
m[path] = make(map[string]string)
|
||||
}
|
||||
@@ -1753,6 +1780,7 @@ func main() {
|
||||
printSize := flag.String("size", "", "print sizes (none, short, full, html)")
|
||||
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")
|
||||
printAllocsCoverString := flag.String("print-allocs-cover", "", "like -print-allocs, but in go coverage tool format")
|
||||
printCommands := flag.Bool("x", false, "Print commands")
|
||||
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")
|
||||
@@ -1833,8 +1861,14 @@ func main() {
|
||||
}
|
||||
|
||||
var printAllocs *regexp.Regexp
|
||||
if *printAllocsString != "" {
|
||||
printAllocs, err = regexp.Compile(*printAllocsString)
|
||||
printAllocsCover := false
|
||||
printAllocsPattern := *printAllocsString
|
||||
if *printAllocsCoverString != "" {
|
||||
printAllocsPattern = *printAllocsCoverString
|
||||
printAllocsCover = true
|
||||
}
|
||||
if printAllocsPattern != "" {
|
||||
printAllocs, err = regexp.Compile(printAllocsPattern)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
@@ -1882,6 +1916,7 @@ func main() {
|
||||
PrintSizes: *printSize,
|
||||
PrintStacks: *printStacks,
|
||||
PrintAllocs: printAllocs,
|
||||
PrintAllocsCover: printAllocsCover,
|
||||
Tags: []string(tags),
|
||||
TestConfig: testConfig,
|
||||
GlobalValues: globalVarValues,
|
||||
@@ -2019,7 +2054,6 @@ func main() {
|
||||
// This uses an additional semaphore to reduce the memory usage.
|
||||
testSema := make(chan struct{}, cap(options.Semaphore))
|
||||
for i, pkgName := range explicitPkgNames {
|
||||
pkgName := pkgName
|
||||
buf := &bufs[i]
|
||||
testSema <- struct{}{}
|
||||
wg.Add(1)
|
||||
|
||||
+96
-21
@@ -46,15 +46,6 @@ var supportedLinuxArches = map[string]string{
|
||||
"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())
|
||||
|
||||
func TestBuild(t *testing.T) {
|
||||
@@ -76,6 +67,8 @@ func TestBuild(t *testing.T) {
|
||||
"init_multi.go",
|
||||
"interface.go",
|
||||
"json.go",
|
||||
"localtypes/",
|
||||
"localtypes.go",
|
||||
"map.go",
|
||||
"map_bigkey.go",
|
||||
"math.go",
|
||||
@@ -252,6 +245,21 @@ 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) {
|
||||
emuCheck(t, options)
|
||||
|
||||
@@ -390,7 +398,7 @@ func emuCheck(t *testing.T, options compileopts.Options) {
|
||||
t.Fatal("failed to load target spec:", err)
|
||||
}
|
||||
if spec.Emulator != "" {
|
||||
emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
|
||||
emulatorCommand, _, _ := strings.Cut(spec.Emulator, " ")
|
||||
_, err := exec.LookPath(emulatorCommand)
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrNotFound) {
|
||||
@@ -465,6 +473,12 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
options.Directory = path
|
||||
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)
|
||||
if err != nil {
|
||||
@@ -474,12 +488,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
// Build the test binary.
|
||||
stdout := &bytes.Buffer{}
|
||||
_, 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()
|
||||
})
|
||||
if err != nil {
|
||||
w := &bytes.Buffer{}
|
||||
diagnostics.CreateDiagnostics(err).WriteTo(w, "")
|
||||
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
|
||||
for line := range strings.SplitSeq(strings.TrimRight(w.String(), "\n"), "\n") {
|
||||
t.Log(line)
|
||||
}
|
||||
if stdout.Len() != 0 {
|
||||
@@ -491,11 +511,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
|
||||
|
||||
actual := stdout.Bytes()
|
||||
if config.EmulatorName() == "simavr" {
|
||||
// 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)
|
||||
actual = cleanSimAVRTestOutput(actual)
|
||||
}
|
||||
if name == "testing.go" {
|
||||
// Strip actual time.
|
||||
@@ -522,6 +538,28 @@ 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.
|
||||
func TestWebAssembly(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -537,7 +575,6 @@ func TestWebAssembly(t *testing.T) {
|
||||
{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{}},
|
||||
} {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
tmpdir := t.TempDir()
|
||||
@@ -659,7 +696,6 @@ func TestWasmExport(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -829,7 +865,6 @@ func TestWasmExportJS(t *testing.T) {
|
||||
{name: "c-shared", buildMode: "c-shared"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Build the wasm binary.
|
||||
@@ -877,7 +912,6 @@ func TestWasmExit(t *testing.T) {
|
||||
{name: "exit-1-sleep", output: "slept\nexit code: 1\n"},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
options := optionsFromTarget("wasm", sema)
|
||||
@@ -920,6 +954,48 @@ 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) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -952,7 +1028,6 @@ func TestTest(t *testing.T) {
|
||||
)
|
||||
}
|
||||
for _, targ := range targs {
|
||||
targ := targ
|
||||
t.Run(targ.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ func ListSerialPorts() ([]SerialPortInfo, error) {
|
||||
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.
|
||||
func extractPanicAddress(line []byte) uint64 {
|
||||
|
||||
@@ -20,7 +20,7 @@ type reader struct {
|
||||
|
||||
func (r *reader) Read(b []byte) (n int, err error) {
|
||||
if len(b) != 0 {
|
||||
libc_arc4random_buf(unsafe.Pointer(&b[0]), uint(len(b)))
|
||||
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(b)), uint(len(b)))
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
|
||||
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
|
||||
|
||||
// If you update the above build constraint, you'll probably also need to update
|
||||
// src/runtime/rand_hwrng.go.
|
||||
|
||||
@@ -25,7 +25,7 @@ func (r *reader) Read(b []byte) (n int, err error) {
|
||||
// See for example: https://github.com/golang/go/issues/33542
|
||||
// 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).
|
||||
ok := libc_RtlGenRandom(unsafe.Pointer(&b[0]), len(b))
|
||||
ok := libc_RtlGenRandom(unsafe.Pointer(unsafe.SliceData(b)), len(b))
|
||||
if !ok {
|
||||
return 0, errRandom
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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},
|
||||
}
|
||||
}
|
||||
+121
-2
@@ -55,6 +55,16 @@ func VersionName(version uint16) string {
|
||||
// only supports Elliptic Curve based groups. See RFC 8446, Section 4.2.7.
|
||||
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
|
||||
//
|
||||
// Not Implemented.
|
||||
@@ -68,14 +78,42 @@ type ConnectionState struct {
|
||||
//
|
||||
// Minimum (empty) fields for fortio.org/log http logging and others
|
||||
// to compile and run.
|
||||
PeerCertificates []*x509.Certificate
|
||||
CipherSuite uint16
|
||||
Version uint16
|
||||
ServerName string
|
||||
PeerCertificates []*x509.Certificate
|
||||
CipherSuite uint16
|
||||
NegotiatedProtocol string
|
||||
NegotiatedProtocolIsMutual bool // Deprecated: this value is always true.
|
||||
}
|
||||
|
||||
// ClientAuthType declares the policy the server will follow for
|
||||
// TLS Client Authentication.
|
||||
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
|
||||
// by a client to resume a TLS session with a given server. ClientSessionCache
|
||||
// implementations should expect to be called concurrently from different
|
||||
@@ -100,6 +138,45 @@ type ClientSessionCache interface {
|
||||
// RFC 8446, Section 4.2.3.
|
||||
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
|
||||
// guide application logic in the GetCertificate and GetConfigForClient callbacks.
|
||||
type ClientHelloInfo struct {
|
||||
@@ -459,6 +536,48 @@ type Config struct {
|
||||
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.
|
||||
type ticketKey struct {
|
||||
aesKey [16]byte
|
||||
|
||||
+42
-3
@@ -20,13 +20,52 @@ import (
|
||||
"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
|
||||
// using conn as the underlying transport.
|
||||
// The config cannot be nil: users must set either ServerName or
|
||||
// InsecureSkipVerify in the config.
|
||||
func Client(conn net.Conn, config *Config) *net.TLSConn {
|
||||
panic("tls.Client() not implemented")
|
||||
return nil
|
||||
func Client(conn net.Conn, config *Config) *Conn {
|
||||
return &Conn{Conn: conn}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
//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)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
.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
|
||||
@@ -0,0 +1,66 @@
|
||||
// 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
|
||||
@@ -85,6 +85,9 @@ var (
|
||||
SIO = (*SIO_Type)(unsafe.Add(unsafe.Pointer(REG_BASE), uintptr(0x0134)))
|
||||
|
||||
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
|
||||
@@ -825,3 +828,60 @@ const (
|
||||
OAMOBJ_ATT2_PB_Pos = 0xC
|
||||
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
|
||||
)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//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()
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
.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
|
||||
@@ -0,0 +1,45 @@
|
||||
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
|
||||
@@ -0,0 +1,77 @@
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
//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
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
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
|
||||
)
|
||||
@@ -0,0 +1,111 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//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)
|
||||
}
|
||||
@@ -22,6 +22,7 @@ func main() {
|
||||
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
|
||||
|
||||
kb := keyboard.Port()
|
||||
machine.USBDev.Configure(machine.UARTConfig{}) // no-op if already init'd by serial.usb
|
||||
|
||||
for {
|
||||
if !button.Get() {
|
||||
|
||||
@@ -25,7 +25,7 @@ func main() {
|
||||
func escapesToHeap() {
|
||||
n := rand.Intn(100)
|
||||
println("Doing ", n, " iterations")
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
s := make([]byte, i)
|
||||
_ = append(s, 42)
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func main() {
|
||||
printItf(Number(3))
|
||||
s := Stringer(thing)
|
||||
println("Stringer.String():", s.String())
|
||||
var itf interface{} = s
|
||||
var itf any = s
|
||||
println("Stringer.(*Thing).String():", itf.(Stringer).String())
|
||||
|
||||
// unusual calls
|
||||
@@ -124,7 +124,7 @@ func strlen(s string) int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func printItf(val interface{}) {
|
||||
func printItf(val any) {
|
||||
switch val := val.(type) {
|
||||
case Doubler:
|
||||
println("is Doubler:", val.Double())
|
||||
|
||||
@@ -6,8 +6,85 @@ import (
|
||||
"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() {
|
||||
msc.Port(machine.Flash)
|
||||
msc.Port(&ramDisk{})
|
||||
machine.USBDev.Configure(machine.UARTConfig{})
|
||||
|
||||
for {
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
@@ -45,11 +45,8 @@ func Compare(a, b []byte) int {
|
||||
// This function was copied from the Go 1.23 source tree (with runtime_cmpstring
|
||||
// manually inlined).
|
||||
func CompareString(a, b string) int {
|
||||
l := len(a)
|
||||
if len(b) < l {
|
||||
l = len(b)
|
||||
}
|
||||
for i := 0; i < l; i++ {
|
||||
l := min(len(b), len(a))
|
||||
for i := range l {
|
||||
c1, c2 := a[i], b[i]
|
||||
if c1 < c2 {
|
||||
return -1
|
||||
@@ -170,7 +167,7 @@ const PrimeRK = 16777619
|
||||
// This function was removed in Go 1.22.
|
||||
func HashStrBytes(sep []byte) (uint32, uint32) {
|
||||
hash := uint32(0)
|
||||
for i := 0; i < len(sep); i++ {
|
||||
for i := range sep {
|
||||
hash = hash*PrimeRK + uint32(sep[i])
|
||||
}
|
||||
var pow, sq uint32 = 1, PrimeRK
|
||||
@@ -249,7 +246,7 @@ func IndexRabinKarpBytes(s, sep []byte) int {
|
||||
hashsep, pow := HashStrBytes(sep)
|
||||
n := len(sep)
|
||||
var h uint32
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
h = h*PrimeRK + uint32(s[i])
|
||||
}
|
||||
if h == hashsep && Equal(s[:n], sep) {
|
||||
@@ -276,7 +273,7 @@ func IndexRabinKarp[T string | []byte](s, sep T) int {
|
||||
hashss, pow := HashStr(sep)
|
||||
n := len(sep)
|
||||
var h uint32
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
h = h*PrimeRK + uint32(s[i])
|
||||
}
|
||||
if h == hashss && string(s[:n]) == string(sep) {
|
||||
|
||||
@@ -12,8 +12,8 @@ func CaseUnmarshaler[T ~uint8 | ~uint16 | ~uint32](cases []string) func(v *T, te
|
||||
return &emptyTextError{}
|
||||
}
|
||||
s := string(text)
|
||||
for i := 0; i < len(cases); i++ {
|
||||
if cases[i] == s {
|
||||
for i, c := range cases {
|
||||
if c == s {
|
||||
*v = T(i)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build go1.23
|
||||
|
||||
package cm
|
||||
|
||||
import "structs"
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !go1.23
|
||||
|
||||
package cm
|
||||
|
||||
// HostLayout marks a struct as using host memory layout.
|
||||
// See [structs.HostLayout] in Go 1.23 or later.
|
||||
type HostLayout struct {
|
||||
_ hostLayout // prevent accidental conversion with plain struct{}
|
||||
}
|
||||
|
||||
type hostLayout struct{}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user