Compare commits

..

1 Commits

Author SHA1 Message Date
Patricio Whittingslow 5c78cf05cd begin debugging gvisor crash 2026-07-06 13:27:24 -03:00
322 changed files with 1528 additions and 13526 deletions
+4 -4
View File
@@ -40,13 +40,13 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-${{ matrix.os }}-v1
key: llvm-source-20-${{ matrix.os }}-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -71,7 +71,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-22-${{ matrix.os }}-v1
key: llvm-build-20-${{ matrix.os }}-v2
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -131,7 +131,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }}
+2 -2
View File
@@ -20,7 +20,7 @@ jobs:
env:
# Oldest versions currently supported by TinyGo
LLVM: "15"
Go: "1.25" # when updating this, also update minorMin in builder/config.go
Go: "1.24" # when updating this, also update minorMin in builder/config.go
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -46,7 +46,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-compat
key: llvm-source-20-linux-compat
path: llvm-project/compiler-rt
- name: Download LLVM source
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
+14 -14
View File
@@ -23,7 +23,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Run go mod tidy
run: go mod tidy
@@ -36,7 +36,7 @@ jobs:
# statically linked binary.
runs-on: ubuntu-latest
container:
image: golang:1.27rc2-alpine
image: golang:1.26-alpine
outputs:
version: ${{ steps.version.outputs.version }}
steps:
@@ -66,7 +66,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-alpine-v1
key: llvm-source-20-linux-alpine-v2
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -91,7 +91,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-22-linux-alpine-v1
key: llvm-build-20-linux-alpine-v2
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -115,7 +115,7 @@ jobs:
uses: actions/cache@v5
id: cache-binaryen
with:
key: binaryen-linux-alpine-v3
key: binaryen-linux-alpine-v2
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
@@ -160,7 +160,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -204,12 +204,12 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: '18'
- name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
@@ -220,7 +220,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-asserts-v1
key: llvm-source-20-linux-asserts-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -245,7 +245,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-22-linux-asserts-v1
key: llvm-build-20-linux-asserts-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -323,13 +323,13 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Restore LLVM source cache
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-v1
key: llvm-source-20-linux-v1
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -354,7 +354,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-22-linux-${{ matrix.goarch }}-v1
key: llvm-build-20-linux-${{ matrix.goarch }}-v1
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -378,7 +378,7 @@ jobs:
uses: actions/cache@v5
id: cache-binaryen
with:
key: binaryen-linux-${{ matrix.goarch }}-v5
key: binaryen-linux-${{ matrix.goarch }}-v4
path: build/wasm-opt
- name: Build Binaryen
if: steps.cache-binaryen.outputs.cache-hit != 'true'
+1 -1
View File
@@ -29,7 +29,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-source
with:
key: llvm-source-22-linux-nix-v1
key: llvm-source-20-linux-nix-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
+1 -5
View File
@@ -19,10 +19,6 @@ jobs:
- name: Add GOBIN to $PATH
run: |
echo "$HOME/go/bin" >> $GITHUB_PATH
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '~1.25'
- name: Checkout
uses: actions/checkout@v6
with:
@@ -34,7 +30,7 @@ jobs:
uses: actions/cache@v5
id: cache-llvm-source
with:
key: llvm-source-22-sizediff-v1
key: llvm-source-20-sizediff-v1
path: |
llvm-project/compiler-rt
- name: Download LLVM source
+6 -6
View File
@@ -34,13 +34,13 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.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-22-windows-v3
key: llvm-source-20-windows-v3
path: |
llvm-project/clang/lib/Headers
llvm-project/clang/include
@@ -65,7 +65,7 @@ jobs:
uses: actions/cache/restore@v5
id: cache-llvm-build
with:
key: llvm-build-22-windows-v1
key: llvm-build-20-windows-v4
path: llvm-build
- name: Build LLVM
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
@@ -129,7 +129,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
@@ -150,7 +150,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
@@ -176,7 +176,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v6
with:
go-version: '1.27.0-rc.2'
go-version: '1.26.4'
cache: true
- name: Download TinyGo build
uses: actions/download-artifact@v8
+2 -2
View File
@@ -1,5 +1,5 @@
# tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.27rc2 AS tinygo-llvm
FROM golang:1.26 AS tinygo-llvm
RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-17 ninja-build && \
@@ -33,7 +33,7 @@ RUN cd /tinygo/ && \
# tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc).
FROM golang:1.27rc2 AS tinygo-compiler
FROM golang:1.26 AS tinygo-compiler
# Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
+15 -47
View File
@@ -133,7 +133,7 @@ endif
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb dtlto executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontenddriver frontendhlsl frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsdriver windowsmanifest
ifeq ($(OS),Windows_NT)
EXE = .exe
@@ -142,9 +142,6 @@ 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++
@@ -172,7 +169,7 @@ endif
MD5SUM ?= md5sum
# Libraries that should be linked in for the statically linked Clang.
CLANG_LIB_NAMES = clangAnalysis clangAnalysisLifetimeSafety clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangOptions clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIB_NAMES = clangAnalysis clangAPINotes clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangExtractAPI clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangInstallAPI clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangSupport clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
# Libraries that should be linked in for the statically linked LLD.
@@ -180,7 +177,7 @@ LLD_LIB_NAMES = lldCOFF lldCommon lldELF lldMachO lldMinGW lldWasm
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
# Other libraries that are needed to link TinyGo.
EXTRA_LIB_NAMES = LLVMDTLTO LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA
EXTRA_LIB_NAMES = LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA
# All libraries to be built and linked with the tinygo binary (lib/lib*.a).
LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
@@ -272,7 +269,7 @@ gen-device-renesas: build/gen-device-svd
GO111MODULE=off $(GO) fmt ./src/device/renesas
$(LLVM_PROJECTDIR)/llvm:
git clone -b tinygo_22.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
git clone -b tinygo_20.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources
# Configure LLVM.
@@ -307,7 +304,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=22
MIN_NODEJS_VERSION=18
.PHONY: check-nodejs-version
check-nodejs-version:
@@ -319,9 +316,9 @@ 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_CFLAGS="$(CGO_CFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOENVFLAGS) $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags "byollvm llvm22 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 llvm22 osusergo" $(GOTESTPKGS)
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
TEST_PACKAGES_SLOW = \
@@ -413,7 +410,6 @@ TEST_PACKAGES_LINUX := \
context \
crypto/aes \
crypto/des \
crypto/ecdh \
crypto/hmac \
debug/dwarf \
debug/plan9obj \
@@ -479,11 +475,6 @@ TEST_PACKAGES_NONBAREMETAL = \
math \
$(nil)
TEST_PACKAGES_FAST_WASI = $(filter-out $(TEST_PACKAGES_NOWASI), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NOWASI = \
crypto/ecdsa \
$(nil)
# Report platforms on which each standard library package is known to pass tests
report-stdlib-tests-pass:
$(eval jointmp := $(shell echo /tmp/join.$$$$))
@@ -498,12 +489,10 @@ 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)
@@ -518,11 +507,8 @@ 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: 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
@# 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)
@# 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.
@@ -544,12 +530,12 @@ tinygo-test-wasi:
tinygo-test-wasip1:
GOOS=wasip1 GOARCH=wasm $(TINYGO) test $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_SLOW) ./tests/runtime_wasi
tinygo-test-wasip1-fast:
$(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
$(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
tinygo-test-wasip2-slow:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST) ./tests/runtime_wasi
tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \
@@ -579,13 +565,13 @@ tinygo-test-baremetal:
# Test external packages in a large corpus.
test-corpus:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml
test-corpus-fast:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus -short . -corpus=testdata/corpus.yaml
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml
test-corpus-wasi:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip1
test-corpus-wasip2:
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2
.PHONY: testchdir
testchdir:
@@ -657,8 +643,6 @@ smoketest: testchdir
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-ice examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -o test.efi -target=uefi-amd64 examples/test
@$(MD5SUM) test.efi
# test simulated boards on play.tinygo.org
ifneq ($(WASM), 0)
GOOS=js GOARCH=wasm $(TINYGO) build -size short -o test.wasm -tags=arduino_uno examples/blinky1
@@ -769,8 +753,6 @@ 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
@@ -809,8 +791,6 @@ endif
@$(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=pico-w 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
@@ -849,14 +829,8 @@ endif
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico2-w examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=tiny2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=badger2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=blinky2350 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=pico-plus2 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=metro-rp2350 examples/blinky1
@@ -898,8 +872,6 @@ ifneq ($(STM32), 0)
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-h753zi examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
@$(MD5SUM) test.hex
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
@@ -928,8 +900,6 @@ 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
@@ -984,8 +954,6 @@ ifneq ($(XTENSA), 0)
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stack examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stamp-s3a examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5stick-c examples/machinetest
@$(MD5SUM) test.bin
$(TINYGO) build -size short -o test.bin -target m5paper examples/machinetest
+10 -10
View File
@@ -14,7 +14,6 @@ import (
"fmt"
"go/types"
"hash/crc32"
"maps"
"math/bits"
"os"
"os/exec"
@@ -138,7 +137,9 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
if _, ok := globalValues[pkgPath]; !ok {
globalValues[pkgPath] = map[string]string{}
}
maps.Copy(globalValues[pkgPath], vals)
for k, v := range vals {
globalValues[pkgPath][k] = v
}
}
// Check for a libc dependency.
@@ -277,6 +278,7 @@ 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()] {
@@ -773,6 +775,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// TODO: do this as part of building the package to be able to link the
// bitcode files together.
for _, pkg := range lprogram.Sorted() {
pkg := pkg
for _, filename := range pkg.CFiles {
abspath := filepath.Join(pkg.OriginalDir(), filename)
job := &compileJob{
@@ -839,25 +842,22 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
}
ldflags = append(ldflags, "-mllvm", "-mcpu="+config.CPU())
ldflags = append(ldflags, "-mllvm", "-mattr="+config.Features()) // needed for MIPS softfloat
switch config.LinkerFlavor() {
case "coff":
// Options for driving ld.lld in PE/COFF mode.
if config.GOOS() == "windows" {
// Options for the MinGW wrapper for the lld COFF linker.
ldflags = append(ldflags,
"-Xlink=/opt:lldlto="+strconv.Itoa(speedLevel),
"--thinlto-cache-dir="+filepath.Join(cacheDir, "thinlto"))
case "darwin":
} else if config.GOOS() == "darwin" {
// Options for the ld64-compatible lld linker.
ldflags = append(ldflags,
"--lto-O"+strconv.Itoa(speedLevel),
"-cache_path_lto", filepath.Join(cacheDir, "thinlto"))
case "gnu":
} else {
// 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.Cut(config.Triple(), "-"); arch == "wasm32" {
if arch := strings.Split(config.Triple(), "-")[0]; arch == "wasm32" {
optLevel, _, _ := config.OptLevel()
opt := "-" + optLevel
+4 -61
View File
@@ -5,7 +5,6 @@ import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/tinygo-org/tinygo/compileopts"
@@ -37,7 +36,6 @@ func TestClangAttributes(t *testing.T) {
"nintendoswitch",
"riscv-qemu",
"tkey",
"uefi-amd64",
"wasip1",
"wasip2",
"wasm",
@@ -49,6 +47,7 @@ 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})
})
@@ -130,10 +129,8 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
defer mod.Dispose()
// Check whether the LLVM target matches.
// Use ClangTriple since LLVM 22 normalizes wasm32-unknown-wasi to wasip1.
expectedTriple := compileopts.ClangTriple(config.Triple())
if mod.Target() != expectedTriple {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", expectedTriple, mod.Target())
if mod.Target() != config.Triple() {
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
}
// Check the "target-cpu" and "target-features" string attribute of the add
@@ -157,65 +154,11 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
// The reason is that Debian has patched Clang in a way that
// modifies the LLVM features string, changing lots of FPU/float
// related flags. We want to test vanilla Clang, not Debian Clang.
//
// Rather than requiring an exact match (which breaks across LLVM
// versions as new features are added), check that every feature
// TinyGo specifies is consistent with what Clang produces:
// - A "+feature" in TinyGo must appear in Clang's output.
// - A "-feature" in TinyGo must not be "+feature" in Clang's output.
checkFeatureFlags(t, config.Features(), features)
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
}
}
}
// checkFeatureFlags verifies that all features specified by TinyGo's target
// configuration are consistent with Clang's output. This allows Clang to add
// new features across LLVM versions without breaking the test.
func checkFeatureFlags(t *testing.T, targetFeatures, clangFeatures string) {
t.Helper()
// Build a set of Clang's features for fast lookup.
clangSet := make(map[string]bool) // feature name -> enabled
for _, f := range strings.Split(clangFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
enabled := f[0] == '+'
name := f[1:]
clangSet[name] = enabled
}
// Check each feature that TinyGo specifies.
var missing, conflicts []string
for _, f := range strings.Split(targetFeatures, ",") {
f = strings.TrimSpace(f)
if len(f) < 2 {
continue
}
wantEnabled := f[0] == '+'
name := f[1:]
clangEnabled, inClang := clangSet[name]
if wantEnabled && (!inClang || !clangEnabled) {
// TinyGo requires +feature but Clang doesn't enable it.
missing = append(missing, f)
} else if !wantEnabled && inClang && clangEnabled {
// TinyGo requires -feature but Clang enables it.
conflicts = append(conflicts, fmt.Sprintf("target has %q but Clang has %q", f, "+"+name))
}
}
if len(missing) > 0 {
t.Errorf("target specifies features not present in Clang output: %s\n\ttarget features: %s\n\tclang features: %s",
strings.Join(missing, ", "), targetFeatures, clangFeatures)
}
if len(conflicts) > 0 {
t.Errorf("target disables features that Clang enables: %s",
strings.Join(conflicts, "; "))
}
}
// This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests.
+3 -29
View File
@@ -204,18 +204,7 @@ var avrBuiltins = []string{
// Builtins needed specifically for windows/386.
var windowsI386Builtins = []string{
"i386/chkstk.S", // __chkstk_ms
"i386/chkstk2.S", // _alloca (__alloca)
}
// Builtins needed specifically for windows/amd64.
var windowsAMD64Builtins = []string{
"x86_64/chkstk.S",
}
// Builtins needed specifically for windows/arm64.
var windowsARM64Builtins = []string{
"aarch64/chkstk.S",
"i386/chkstk.S", // also _alloca
}
// libCompilerRT is a library with symbols required by programs compiled with
@@ -244,28 +233,13 @@ var libCompilerRT = Library{
builtins = append(builtins, aeabiBuiltins...)
case "avr":
builtins = append(builtins, avrBuiltins...)
case "x86_64":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsAMD64Builtins...)
}
case "aarch64":
builtins = append(builtins, genericBuiltins128...)
if isWindowsTriple(target) {
builtins = append(builtins, windowsARM64Builtins...)
}
case "riscv64":
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
builtins = append(builtins, genericBuiltins128...)
case "i386":
if isWindowsTriple(target) {
if strings.Split(target, "-")[2] == "windows" {
builtins = append(builtins, windowsI386Builtins...)
}
}
return builtins, nil
},
}
func isWindowsTriple(target string) bool {
parts := strings.Split(target, "-")
return len(parts) > 2 && parts[2] == "windows"
}
+1 -1
View File
@@ -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.Cut(s, "\n")
line := strings.SplitN(s, "\n", 2)[0]
if !strings.HasPrefix(line, "deps:") {
return nil, errors.New("readDepFile: expected 'deps:' prefix")
}
+17 -17
View File
@@ -23,7 +23,7 @@
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "clang/Driver/DriverDiagnostic.h"
#include "clang/Options/Options.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/Utils.h"
@@ -35,7 +35,6 @@
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCCodeEmitter.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCInstPrinter.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCObjectWriter.h"
@@ -68,7 +67,8 @@
#include <optional>
#include <system_error>
using namespace clang;
using namespace clang::options;
using namespace clang::driver;
using namespace clang::driver::options;
using namespace llvm;
using namespace llvm::opt;
@@ -109,7 +109,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
// Construct the invocation.
// Target Options
Opts.Triple = llvm::Triple(llvm::Triple::normalize(Args.getLastArgValue(OPT_triple)));
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
if (Arg *A = Args.getLastArg(options::OPT_darwin_target_variant_triple))
Opts.DarwinTargetVariantTriple = llvm::Triple(A->getValue());
if (Arg *A = Args.getLastArg(OPT_darwin_target_variant_sdk_version_EQ)) {
@@ -125,8 +125,8 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
Opts.Features = Args.getAllArgValues(OPT_target_feature);
// Use the default target triple if unspecified.
if (Opts.Triple.getTriple().empty())
Opts.Triple = llvm::Triple(llvm::sys::getDefaultTargetTriple());
if (Opts.Triple.empty())
Opts.Triple = llvm::sys::getDefaultTargetTriple();
// Language Options
Opts.IncludePaths = Args.getAllArgValues(OPT_I);
@@ -267,7 +267,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::string Error;
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
if (!TheTarget)
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
@@ -327,7 +327,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
assert(STI && "Unable to create subtarget info!");
MCContext Ctx(Opts.Triple, MAI.get(), MRI.get(), STI.get(), &SrcMgr,
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
&MCOptions);
bool PIC = false;
@@ -390,8 +390,8 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
if (Opts.OutputType == AssemblerInvocation::FT_Asm) {
std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
Opts.Triple, Opts.OutputAsmVariant, *MAI, *MCII, *MRI));
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
std::unique_ptr<MCCodeEmitter> CE;
if (Opts.ShowEncoding)
@@ -400,7 +400,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(IP),
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), IP,
std::move(CE), std::move(MAB)));
} else if (Opts.OutputType == AssemblerInvocation::FT_Null) {
Str.reset(createNullStreamer(Ctx));
@@ -422,7 +422,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
: MAB->createObjectWriter(*Out);
Triple T = Opts.Triple;
Triple T(Opts.Triple);
Str.reset(TheTarget->createMCObjectStreamer(
T, Ctx, std::move(MAB), std::move(OW), std::move(CE), *STI));
Str.get()->initSections(Opts.NoExecStack, *STI);
@@ -453,7 +453,7 @@ static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
std::unique_ptr<MCTargetAsmParser> TAP(
TheTarget->createMCAsmParser(*STI, *Parser, *MCII, MCOptions));
if (!TAP)
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
// Set values for symbols, if any.
for (auto &S : Opts.SymbolDefs) {
@@ -506,12 +506,12 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
InitializeAllAsmParsers();
// Construct our diagnostic client.
DiagnosticOptions DiagOpts;
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
TextDiagnosticPrinter *DiagClient
= new TextDiagnosticPrinter(errs(), DiagOpts);
= new TextDiagnosticPrinter(errs(), &*DiagOpts);
DiagClient->setPrefix("clang -cc1as");
IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
DiagnosticsEngine Diags(DiagID, DiagOpts, DiagClient);
DiagnosticsEngine Diags(DiagID, &*DiagOpts, DiagClient);
// Set an error handler, so that any LLVM backend diagnostics go through our
// error handler.
@@ -528,7 +528,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
llvm::outs(), "clang -cc1as [options] file...",
"Clang Integrated Assembler", /*ShowHidden=*/false,
/*ShowAllAliases=*/false,
llvm::opt::Visibility(clang::options::CC1AsOption));
llvm::opt::Visibility(driver::options::CC1AsOption));
return 0;
}
+2 -2
View File
@@ -20,7 +20,7 @@ struct AssemblerInvocation {
/// @{
/// The name of the target triple to assemble for.
llvm::Triple Triple;
std::string Triple;
/// If given, the name of the target CPU to determine which instructions
/// are legal.
@@ -142,7 +142,7 @@ struct AssemblerInvocation {
public:
AssemblerInvocation() {
Triple = llvm::Triple();
Triple = "";
NoInitialTextSection = 0;
InputFile = "-";
OutputPath = "-";
+4 -4
View File
@@ -27,9 +27,9 @@ bool tinygo_clang_driver(int argc, char **argv) {
std::vector<const char*> args(argv, argv + argc);
// The compiler invocation needs a DiagnosticsEngine so it can report problems
clang::DiagnosticOptions DiagOpts;
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), DiagOpts, &DiagnosticPrinter, false);
llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> DiagOpts = new clang::DiagnosticOptions();
clang::TextDiagnosticPrinter DiagnosticPrinter(llvm::errs(), &*DiagOpts);
clang::DiagnosticsEngine Diags(llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs>(new clang::DiagnosticIDs()), &*DiagOpts, &DiagnosticPrinter, false);
// Create the clang driver
clang::driver::Driver TheDriver(args[0], llvm::sys::getDefaultTargetTriple(), Diags);
@@ -60,7 +60,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
}
// Create the actual diagnostics engine.
Clang->createDiagnostics();
Clang->createDiagnostics(*llvm::vfs::getRealFileSystem());
if (!Clang->hasDiagnostics()) {
return false;
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
var commands = map[string][]string{}
func init() {
llvmMajor, _, _ := strings.Cut(llvm.Version, ".")
llvmMajor := strings.Split(llvm.Version, ".")[0]
commands["clang"] = []string{"clang-" + llvmMajor}
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
+2 -2
View File
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
}
// Version range supported by TinyGo.
const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml
const minorMax = 27
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.
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
+1 -1
View File
@@ -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.Cut(config.Triple(), "-")
arch := strings.Split(config.Triple(), "-")[0]
job.result = filepath.Join(tmpdir, "libSystem.dylib")
objpath := filepath.Join(tmpdir, "libSystem.o")
inpath := filepath.Join(goenv.Get("TINYGOROOT"), "lib/macos-minimal-sdk/src", arch, "libSystem.s")
+9 -132
View File
@@ -65,6 +65,15 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
// Sort the segments by address. This is what esptool does too.
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
// Write first to an in-memory buffer, primarily so that we can easily
// calculate a hash over the entire image.
// An added benefit is that we don't need to check for errors all the time.
@@ -79,86 +88,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
chip = format[:len(format)-len("-img")]
}
// For ESP32 (original): separate RAM segments (loadable by ROM bootloader)
// from flash-mapped segments (DROM/IROM, require MMU setup by startup code).
// The ROM bootloader on ESP32 does NOT handle flash-mapped segments —
// it tries to memcpy to the virtual address, which crashes.
var flashSegments []*espImageSegment
if chip == "esp32" {
var ramSegments []*espImageSegment
for _, seg := range segments {
if (seg.addr >= 0x3F400000 && seg.addr < 0x3F800000) || // DROM
(seg.addr >= 0x400D0000 && seg.addr < 0x40400000) { // IROM
flashSegments = append(flashSegments, seg)
} else {
ramSegments = append(ramSegments, seg)
}
}
segments = ramSegments
}
// ESP32 flash XIP: compute where the DROM segment will be placed in flash
// (page-aligned, right after the RAM segments) and patch the
// _drom_flash_addr variable so the startup code can program the cache MMU.
// This must happen before the checksum/hash are computed so the patched
// value is covered by both.
const esp32FlashBase = 0x1000 // esptool flashes the image at 0x1000
// The ESP32 flash cache MMU supports configurable page sizes down to 256 B. 64 KiB is the reset/default size.
// If the startup code ever changes the MMU page size, this constant must change too.
const esp32PageSize = 0x10000 // 64KB MMU pages
var esp32DromFlashAddr uint32
if chip == "esp32" && len(flashSegments) > 0 {
// Compute the size of the RAM portion of the image (everything the ROM
// bootloader loads, up to and including the appended SHA256 hash).
ramImageSize := 0
if makeImage {
ramImageSize += 4096
}
ramImageSize += 24 // image header (8) + trailer fields (16)
for _, seg := range segments {
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
}
ramImageSize += 16 - ramImageSize%16 // footer padding + checksum byte
ramImageSize += 32 // appended SHA256 hash
// DROM flash address must be 64KB page-aligned.
esp32DromFlashAddr = uint32(esp32FlashBase+ramImageSize+esp32PageSize-1) &^ (esp32PageSize - 1)
// Patch _drom_flash_addr in whichever RAM segment contains it.
syms, _ := inf.Symbols()
var dromSymAddr uint64
for _, s := range syms {
if s.Name == "_drom_flash_addr" {
dromSymAddr = s.Value
break
}
}
if dromSymAddr == 0 {
return fmt.Errorf("ESP32: _drom_flash_addr symbol not found")
}
patched := false
for _, seg := range segments {
if dromSymAddr >= uint64(seg.addr) && dromSymAddr+4 <= uint64(seg.addr)+uint64(len(seg.data)) {
off := int(dromSymAddr - uint64(seg.addr))
binary.LittleEndian.PutUint32(seg.data[off:], esp32DromFlashAddr)
patched = true
break
}
}
if !patched {
return fmt.Errorf("ESP32: _drom_flash_addr (0x%x) not in any RAM segment", dromSymAddr)
}
}
// Calculate checksum over the segment data. This is used in the image
// footer.
checksum := uint8(0xef)
for _, segment := range segments {
for _, b := range segment.data {
checksum ^= b
}
}
if makeImage {
// The bootloader starts at 0x1000, or 4096.
// TinyGo doesn't use a separate bootloader and runs the entire
@@ -262,58 +191,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
outf.Write(hash[:])
}
// For ESP32: append flash-mapped segments (DROM/IROM) at page-aligned flash
// offsets after the RAM portion. The startup code maps them via the flash
// cache MMU (DROM at esp32DromFlashAddr, patched into _drom_flash_addr).
if len(flashSegments) > 0 {
const flashBase = esp32FlashBase
const pageSize = esp32PageSize
dromFlashAddr := esp32DromFlashAddr
// Separate DROM and IROM segments.
var dromSegs, iromSegs []*espImageSegment
for _, seg := range flashSegments {
if seg.addr >= 0x3F400000 && seg.addr < 0x3F800000 {
dromSegs = append(dromSegs, seg)
} else {
iromSegs = append(iromSegs, seg)
}
}
// Write DROM segments at the computed page-aligned flash offset.
dromSize := 0
if len(dromSegs) > 0 {
targetImageOffset := int(dromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range dromSegs {
outf.Write(seg.data)
dromSize += len(seg.data)
}
}
// Write IROM segments immediately after DROM, at the next page boundary.
// IROM flash addr = dromFlashAddr + ceil(dromSize/pageSize)*pageSize
// (must match the computation in the startup assembly).
if len(iromSegs) > 0 {
dromPages := (dromSize + pageSize - 1) / pageSize
if dromPages == 0 {
dromPages = 1
}
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
targetImageOffset := int(iromFlashAddr - flashBase)
if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
}
outf.Write(make([]byte, targetImageOffset-outf.Len()))
for _, seg := range iromSegs {
outf.Write(seg.data)
}
}
}
// QEMU (or more precisely, qemu-system-xtensa from Espressif) expects the
// image to be a certain size.
if makeImage {
+2 -2
View File
@@ -195,11 +195,11 @@ type intHeap struct {
sort.IntSlice
}
func (h *intHeap) Push(x any) {
func (h *intHeap) Push(x interface{}) {
h.IntSlice = append(h.IntSlice, x.(int))
}
func (h *intHeap) Pop() any {
func (h *intHeap) Pop() interface{} {
x := h.IntSlice[len(h.IntSlice)-1]
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
return x
+2 -1
View File
@@ -133,7 +133,7 @@ func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJ
// Note: -fdebug-prefix-map is necessary to make the output archive
// reproducible. Otherwise the temporary directory is stored in the archive
// itself, which varies each run.
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+compileopts.ClangTriple(target), "-fdebug-prefix-map="+dir+"="+remapDir)
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-gdwarf-4", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
resourceDir := goenv.ClangResourceDir(false)
if resourceDir != "" {
args = append(args, "-resource-dir="+resourceDir)
@@ -232,6 +232,7 @@ 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
View File
@@ -28,8 +28,8 @@ func buildMuslAllTypes(arch, muslDir, outputBitsDir string) error {
if err != nil {
return err
}
lines := strings.SplitSeq(string(data), "\n")
for line := range lines {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "TYPEDEF ") {
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
value := matches[1]
+5 -3
View File
@@ -42,15 +42,16 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 4313, 323, 0, 2260},
{"microbit", "examples/serial", 2838, 382, 8, 2256},
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488},
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
{"microbit", "examples/serial", 2736, 356, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
// 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()
@@ -84,6 +85,7 @@ 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()
+1 -1
View File
@@ -116,7 +116,7 @@ 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.SplitSeq(message, "\n") {
for _, line := range strings.Split(message, "\n") {
matches := regexp.MustCompile(`referenced by .* \(((.*):([0-9]+))\)`).FindStringSubmatch(line)
if matches != nil {
parsedError = true
+1 -1
View File
@@ -40,7 +40,7 @@ func convertBinToUF2(input []byte, targetAddr uint32, uf2FamilyID string) ([]byt
}
bl.SetNumBlocks(len(blocks))
for i := range blocks {
for i := 0; i < len(blocks); i++ {
bl.SetBlockNo(i)
bl.SetData(blocks[i])
+8 -10
View File
@@ -40,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[any]string
anonDecls map[interface{}]string
cflags []string // CFlags from #cgo lines
ldflags []string // LDFlags from #cgo lines
visitedFiles map[string][]byte
@@ -259,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[any]string{},
anonDecls: map[interface{}]string{},
visitedFiles: map[string][]byte{},
}
@@ -302,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 strings.Builder
var cgoHeader string
for i := 0; i < len(f.Decls); i++ {
decl := f.Decls[i]
genDecl, ok := decl.(*ast.GenDecl)
@@ -337,8 +337,7 @@ 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())
var fragment strings.Builder
fragment.WriteString(fmt.Sprintf("# %d %#v\n", position.Line, position.Filename))
fragment := 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).
@@ -355,13 +354,12 @@ func Process(files []*ast.File, dir, importPath string, fset *token.FileSet, cfl
} else { // comment
c = " " + c[2:len(c)-2]
}
fragment.WriteString(c)
fragment.WriteByte('\n')
fragment += c + "\n"
}
cgoHeader.WriteString(fragment.String())
cgoHeader += fragment
}
p.cgoHeaders[i] = cgoHeader.String()
p.cgoHeaders[i] = cgoHeader
}
// Define CFlags that will be used while parsing the package.
@@ -1219,7 +1217,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 any) string {
func (p *cgoPackage) getUnnamedDeclName(prefix string, itf interface{}) string {
if name, ok := p.anonDecls[itf]; ok {
return name
}
+1
View File
@@ -45,6 +45,7 @@ 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")
+7 -13
View File
@@ -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 := range numDiagnostics {
for i := 0; i < numDiagnostics; i++ {
diagnostic := C.clang_getDiagnostic(unit, C.uint(i))
addDiagnostic(diagnostic)
@@ -278,7 +278,7 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
Text: strings.Join(doc, "\n"),
})
}
for i := range numArgs {
for i := 0; i < numArgs; i++ {
arg := C.tinygo_clang_Cursor_getArgument(c, C.uint(i))
argName := getString(C.tinygo_clang_getCursorSpelling(arg))
argType := C.clang_getArgType(cursorType, C.uint(i))
@@ -330,8 +330,10 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
NamePos: pos,
Name: typeName,
},
Assign: pos,
Type: f.makeASTType(underlyingType, pos),
Type: f.makeASTType(underlyingType, pos),
}
if underlyingType.kind != C.CXType_Enum {
typeSpec.Assign = pos
}
return typeSpec, nil
case C.CXCursor_VarDecl:
@@ -532,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) any {
func (f *cgoFile) getUniqueLocationID(pos token.Pos, cursor C.GoCXCursor) interface{} {
clangLocation := C.tinygo_clang_getCursorLocation(cursor)
var file C.CXFile
var line C.unsigned
@@ -804,14 +806,6 @@ func (f *cgoFile) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
f.addError(pos, fmt.Sprintf("unknown elaborated type (libclang type kind %s)", typeKindSpelling))
typeName = "<unknown>"
}
case C.CXType_Unexposed:
// LLVM 22+ may report certain builtin type aliases (e.g. __size_t)
// as Unexposed. Resolve via the canonical type.
canonical := C.clang_getCanonicalType(typ)
if canonical.kind != C.CXType_Unexposed && canonical.kind != C.CXType_Invalid {
return f.makeASTType(canonical, pos)
}
// If still unexposed, fall through to the error below.
case C.CXType_Record:
cursor := C.tinygo_clang_getTypeDeclaration(typ)
name := getString(C.tinygo_clang_getCursorSpelling(cursor))
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
package cgo
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm21
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-21 -I/usr/include/llvm-c-21 -I/usr/lib/llvm-21/include -I/usr/lib64/llvm21/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@21/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@21/include
#cgo freebsd CFLAGS: -I/usr/local/llvm21/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-21/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@21/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@21/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm21/lib -lclang
*/
import "C"
-15
View File
@@ -1,15 +0,0 @@
//go:build !byollvm && llvm22
package cgo
/*
#cgo linux CFLAGS: -I/usr/include/llvm-22 -I/usr/include/llvm-c-22 -I/usr/lib/llvm-22/include -I/usr/lib64/llvm22/include
#cgo darwin,amd64 CFLAGS: -I/usr/local/opt/llvm@22/include
#cgo darwin,arm64 CFLAGS: -I/opt/homebrew/opt/llvm@22/include
#cgo freebsd CFLAGS: -I/usr/local/llvm22/include
#cgo linux LDFLAGS: -L/usr/lib/llvm-22/lib -lclang
#cgo darwin,amd64 LDFLAGS: -L/usr/local/opt/llvm@22/lib -lclang
#cgo darwin,arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@22/lib -lclang
#cgo freebsd LDFLAGS: -L/usr/local/llvm22/lib -lclang
*/
import "C"
+4 -4
View File
@@ -12,17 +12,17 @@ import "C"
// C. It is useful if an API uses function pointers and you cannot pass a Go
// pointer but only a C pointer.
type refMap struct {
refs map[unsafe.Pointer]any
refs map[unsafe.Pointer]interface{}
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 any) unsafe.Pointer {
func (m *refMap) Put(v interface{}) unsafe.Pointer {
m.lock.Lock()
defer m.lock.Unlock()
if m.refs == nil {
m.refs = make(map[unsafe.Pointer]any, 1)
m.refs = make(map[unsafe.Pointer]interface{}, 1)
}
ref := C.malloc(1)
m.refs[ref] = v
@@ -31,7 +31,7 @@ func (m *refMap) Put(v any) unsafe.Pointer {
// Get returns a stored value previously inserted with Put. Use the same
// reference as you got from Put.
func (m *refMap) Get(ref unsafe.Pointer) any {
func (m *refMap) Get(ref unsafe.Pointer) interface{} {
m.lock.Lock()
defer m.lock.Unlock()
return m.refs[ref]
+14 -27
View File
@@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"regexp"
"slices"
"strconv"
"strings"
@@ -61,15 +60,13 @@ func (c *Config) BuildMode() string {
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
// will be returned.
func (c *Config) Features() string {
var features string
if c.Target.Features == "" {
features = c.Options.LLVMFeatures
} else if c.Options.LLVMFeatures == "" {
features = c.Target.Features
} else {
features = c.Target.Features + "," + c.Options.LLVMFeatures
return c.Options.LLVMFeatures
}
return patchFeatures(features)
if c.Options.LLVMFeatures == "" {
return c.Target.Features
}
return c.Target.Features + "," + c.Options.LLVMFeatures
}
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
@@ -143,7 +140,13 @@ func (c *Config) GC() string {
func (c *Config) NeedsStackObjects() bool {
switch c.GC() {
case "conservative", "custom", "precise", "boehm":
return slices.Contains(c.BuildTags(), "tinygo.wasm")
for _, tag := range c.BuildTags() {
if tag == "tinygo.wasm" {
return true
}
}
return false
default:
return false
}
@@ -242,7 +245,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.Cut(triple, "-")
arch := strings.Split(triple, "-")[0]
if arch == "arm64" {
return "aarch64"
}
@@ -349,7 +352,7 @@ func (c *Config) CFlags(libclang bool) []string {
// Use the same optimization level as TinyGo.
cflags = append(cflags, "-O"+c.Options.Opt)
// Set the LLVM target triple.
cflags = append(cflags, "--target="+ClangTriple(c.Triple()))
cflags = append(cflags, "--target="+c.Triple())
// Set the -mcpu (or similar) flag.
if c.Target.CPU != "" {
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
@@ -463,22 +466,6 @@ 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 {
-9
View File
@@ -1,9 +0,0 @@
//go:build !llvm22 && !llvm14 && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
package compileopts
// patchFeatures applies LLVM-version-specific feature name mappings.
// For LLVM 20/21, features in the target JSON files are already correct.
func patchFeatures(features string) string {
return features
}
-26
View File
@@ -1,26 +0,0 @@
//go:build llvm22
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 22 renamed several Xtensa target features.
func patchFeatures(features string) string {
// Xtensa feature renames in LLVM 22:
// atomctl → (removed, no direct replacement)
// memctl → (removed, no direct replacement)
// esp32s3 → esp32s3ops
// timerint → timers3 (for esp32/esp32s3) or timers1 (for esp8266)
// Since we can't distinguish which timer variant at this level,
// just remove the obsolete features. The CPU definition already
// implies the correct features in LLVM 22.
replacer := strings.NewReplacer(
"+atomctl,", "",
"+memctl,", "",
"+esp32s3,", "+esp32s3ops,",
"+timerint,", "",
",+timerint", "",
)
return replacer.Replace(features)
}
-16
View File
@@ -1,16 +0,0 @@
//go:build llvm14 || llvm15 || llvm16 || llvm17 || llvm18 || llvm19
package compileopts
import "strings"
// patchFeatures applies LLVM-version-specific feature name mappings.
// LLVM 19 and earlier do not have +bulk-memory-opt or
// +call-indirect-overlong for WebAssembly (added in LLVM 20).
func patchFeatures(features string) string {
features = strings.ReplaceAll(features, ",+bulk-memory-opt", "")
features = strings.ReplaceAll(features, "+bulk-memory-opt,", "")
features = strings.ReplaceAll(features, ",+call-indirect-overlong", "")
features = strings.ReplaceAll(features, "+call-indirect-overlong,", "")
return features
}
+16 -8
View File
@@ -3,7 +3,6 @@ package compileopts
import (
"fmt"
"regexp"
"slices"
"strings"
"time"
)
@@ -68,7 +67,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 := slices.Contains(validBuildModeOptions, o.BuildMode)
valid := isInArray(validBuildModeOptions, o.BuildMode)
if !valid {
return fmt.Errorf(`invalid buildmode option '%s': valid values are %s`,
o.BuildMode,
@@ -76,7 +75,7 @@ func (o *Options) Verify() error {
}
}
if o.GC != "" {
valid := slices.Contains(validGCOptions, o.GC)
valid := isInArray(validGCOptions, o.GC)
if !valid {
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
o.GC,
@@ -85,7 +84,7 @@ func (o *Options) Verify() error {
}
if o.Scheduler != "" {
valid := slices.Contains(validSchedulerOptions, o.Scheduler)
valid := isInArray(validSchedulerOptions, o.Scheduler)
if !valid {
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
o.Scheduler,
@@ -94,7 +93,7 @@ func (o *Options) Verify() error {
}
if o.Serial != "" {
valid := slices.Contains(validSerialOptions, o.Serial)
valid := isInArray(validSerialOptions, o.Serial)
if !valid {
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
o.Serial,
@@ -103,7 +102,7 @@ func (o *Options) Verify() error {
}
if o.PrintSizes != "" {
valid := slices.Contains(validPrintSizeOptions, o.PrintSizes)
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
if !valid {
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
o.PrintSizes,
@@ -112,7 +111,7 @@ func (o *Options) Verify() error {
}
if o.PanicStrategy != "" {
valid := slices.Contains(validPanicStrategyOptions, o.PanicStrategy)
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
if !valid {
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
o.PanicStrategy,
@@ -121,10 +120,19 @@ func (o *Options) Verify() error {
}
if o.Opt != "" {
if !slices.Contains(validOptOptions, o.Opt) {
if !isInArray(validOptOptions, o.Opt) {
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
}
}
return nil
}
func isInArray(arr []string, item string) bool {
for _, i := range arr {
if i == item {
return true
}
}
return false
}
+1 -4
View File
@@ -38,8 +38,7 @@ 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"`
LinkerFlavor string `json:"linker-flavor,omitempty"` // how to drive the configured linker (for example: gnu, coff, darwin)
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
RTLib string `json:"rtlib,omitempty"` // compiler runtime library (libgcc, compiler-rt)
Libc string `json:"libc,omitempty"`
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.
@@ -475,12 +474,10 @@ func defaultTarget(options *Options) (*TargetSpec, error) {
"-m", "i386pep",
"--image-base", "0x400000",
)
spec.RTLib = "compiler-rt"
case "arm64":
spec.LDFlags = append(spec.LDFlags,
"-m", "arm64pe",
)
spec.RTLib = "compiler-rt"
}
spec.LDFlags = append(spec.LDFlags,
"-Bdynamic",
-49
View File
@@ -112,52 +112,3 @@ func TestOverrideProperties(t *testing.T) {
}
}
func TestConfigLinkerFlavor(t *testing.T) {
tests := []struct {
name string
target *TargetSpec
goos string
want string
}{
{
name: "default gnu",
target: &TargetSpec{},
goos: "linux",
want: "gnu",
},
{
name: "default coff",
target: &TargetSpec{},
goos: "windows",
want: "coff",
},
{
name: "default darwin",
target: &TargetSpec{},
goos: "darwin",
want: "darwin",
},
{
name: "target override",
target: &TargetSpec{
LinkerFlavor: "coff",
},
goos: "linux",
want: "coff",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tc.target.GOOS = tc.goos
config := &Config{
Options: &Options{},
Target: tc.target,
}
if got := config.LinkerFlavor(); got != tc.want {
t.Fatalf("LinkerFlavor() = %q, want %q", got, tc.want)
}
})
}
}
-12
View File
@@ -1,12 +0,0 @@
//go:build llvm22
package compileopts
import "strings"
// ClangTriple returns the target triple to pass to Clang's --target flag.
// LLVM 22 deprecated the "wasm32-unknown-wasi" triple in favor of
// "wasm32-unknown-wasip1", so we substitute it here to avoid warnings.
func ClangTriple(triple string) string {
return strings.Replace(triple, "wasm32-unknown-wasi", "wasm32-unknown-wasip1", 1)
}
-9
View File
@@ -1,9 +0,0 @@
//go:build !llvm22
package compileopts
// ClangTriple returns the target triple to pass to Clang's --target flag.
// For pre-LLVM 22, the triple is used as-is.
func ClangTriple(triple string) string {
return triple
}
-47
View File
@@ -35,9 +35,6 @@ const (
// Whether this is a readonly parameter (for example, a string pointer).
paramIsReadonly
// Whether this parameter is passed through backing storage.
paramIsIndirect
)
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -105,18 +102,6 @@ func (b *builder) createInvoke(fnType llvm.Type, fn llvm.Value, args []llvm.Valu
// Expand an argument type to a list that can be used in a function call
// parameter list.
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
if c.isIndirectAggregate(t) {
return []paramInfo{{
llvmType: c.dataPtrType,
name: name,
elemSize: c.targetData.TypeAllocSize(t),
flags: paramIsGoParam | paramIsReadonly | paramIsIndirect,
}}
}
return c.expandDirectFormalParamType(t, name, goType)
}
func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
switch t.TypeKind() {
case llvm.StructTypeKind:
fieldInfos := c.flattenAggregateType(t, name, goType)
@@ -130,38 +115,6 @@ func (c *compilerContext) expandDirectFormalParamType(t llvm.Type, name string,
return []paramInfo{c.getParamInfo(t, name, goType)}
}
func (c *compilerContext) storedParamType(t llvm.Type, exported bool) llvm.Type {
if c.isIndirectParam(t, exported) {
return c.dataPtrType
}
return t
}
func (c *compilerContext) isIndirectParam(t llvm.Type, exported bool) bool {
return !exported && c.isIndirectAggregate(t)
}
func (b *builder) appendStoredValueTypes(valueTypes []llvm.Type, values []ssa.Value, exported bool) []llvm.Type {
for _, value := range values {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(value.Type()), exported))
}
return valueTypes
}
func (b *builder) appendStoredParamTypes(valueTypes []llvm.Type, params []*types.Var, exported bool) []llvm.Type {
for _, param := range params {
valueTypes = append(valueTypes, b.storedParamType(b.getLLVMType(param.Type()), exported))
}
return valueTypes
}
func (b *builder) prependIndirectResult(sig *types.Signature, exported bool, params []llvm.Value, name string) []llvm.Value {
if resultType, indirect := b.hasIndirectResult(sig); !exported && indirect {
return append([]llvm.Value{b.createIndirectStorage(resultType, name)}, params...)
}
return params
}
// expandFormalParamOffsets returns a list of offsets from the start of an
// object of type t after it would have been split up by expandFormalParam. This
// is useful for debug information, where it is necessary to know the offset
+38 -22
View File
@@ -30,15 +30,17 @@ func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
// actual channel send operation during goroutine lowering.
func (b *builder) createChanSend(instr *ssa.Send) {
ch := b.getValue(instr.Chan, getPos(instr))
chanValue := b.getValue(instr.X, getPos(instr))
// store value-to-send
valueType := b.getLLVMType(instr.X.Type())
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var storage valueStorage
var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize {
storage.ptr = llvm.ConstNull(b.dataPtrType)
valueAlloca = llvm.ConstNull(b.dataPtrType)
} else {
storage = b.getValueStorage(instr.X, "chan.value")
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
b.CreateStore(chanValue, valueAlloca)
}
// Allocate buffer for the channel operation.
@@ -46,13 +48,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the send.
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, 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:
// https://bugs.llvm.org/show_bug.cgi?id=41742
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
b.endValueStorage(storage)
if !isZeroSize {
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
}
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
@@ -62,17 +66,37 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
ch := b.getValue(unop.X, getPos(unop))
// Allocate memory to receive into.
result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan")
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
var valueAlloca, valueAllocaSize llvm.Value
if isZeroSize {
valueAlloca = llvm.ConstNull(b.dataPtrType)
} else {
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
}
// Allocate buffer for the channel operation.
channelOp := b.getLLVMRuntimeType("channelOp")
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
// Do the receive.
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "")
received := result.finish(b, commaOk, "chan.received")
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
var received llvm.Value
if isZeroSize {
received = llvm.ConstNull(valueType)
} else {
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
}
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
return received
if unop.CommaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, received, 0, "")
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
return tuple
} else {
return received
}
}
// createChanClose closes the given channel.
@@ -146,7 +170,9 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
case types.SendOnly:
// Store this value in an alloca and put a pointer to this alloca
// in the send state.
alloca := b.getSelectSendStorage(state.Send)
sendValue := b.getValue(state.Send, state.Pos)
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
b.CreateStore(sendValue, alloca)
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
default:
panic("unreachable")
@@ -254,17 +280,7 @@ func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
// receive can proceed at a time) so we'll get that alloca, bitcast
// it to the correct type, and dereference it.
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
return b.loadFromStorage(recvbuf, expr.Type(), "select.received")
typ := b.getLLVMType(expr.Type())
return b.CreateLoad(typ, recvbuf, "")
}
}
func (b *builder) getSelectSendStorage(value ssa.Value) llvm.Value {
typ := b.getLLVMType(value.Type())
if b.isIndirectAggregate(typ) {
return b.getValuePointer(value)
}
llvmValue := b.getValue(value, getPos(value))
ptr := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, "select.send.value")
b.CreateStore(llvmValue, ptr)
return ptr
}
+73 -327
View File
@@ -90,8 +90,7 @@ type compilerContext struct {
astComments map[string]*ast.CommentGroup
embedGlobals map[string][]*loader.EmbedFile
pkg *types.Package
loaderPkg *loader.Package // current package being compiled (for AST access)
packageDir string // directory for this package
packageDir string // directory for this package
runtimePkg *types.Package
localTypeNames typeutil.Map // *types.Named (synthetic local from generic instantiation) -> string
}
@@ -155,8 +154,6 @@ type builder struct {
llvmFn llvm.Value
info functionInfo
locals map[ssa.Value]llvm.Value // local variables
indirectValues map[ssa.Value]llvm.Value
indirectReturn llvm.Value
blockInfo []blockInfo
currentBlock *ssa.BasicBlock
currentBlockInfo *blockInfo
@@ -171,7 +168,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 []any
allDeferFuncs []interface{}
deferFuncs map[*ssa.Function]int
deferInvokeFuncs map[string]int
deferClosureFuncs map[*ssa.Function]int
@@ -195,7 +192,6 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
llvmFn: fn,
info: c.getFunctionInfo(f),
locals: make(map[ssa.Value]llvm.Value),
indirectValues: make(map[ssa.Value]llvm.Value),
dilocals: make(map[*types.Var]llvm.Metadata),
}
}
@@ -302,7 +298,6 @@ 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
@@ -425,19 +420,19 @@ func (c *compilerContext) makeLLVMType(goType types.Type) llvm.Type {
return c.ctx.Int8Type()
case types.Int16, types.Uint16:
return c.ctx.Int16Type()
case types.Int32, types.Uint32, types.UntypedRune:
case types.Int32, types.Uint32:
return c.ctx.Int32Type()
case types.Int, types.Uint, types.UntypedInt:
case types.Int, types.Uint:
return c.intType
case types.Int64, types.Uint64:
return c.ctx.Int64Type()
case types.Float32:
return c.ctx.FloatType()
case types.Float64, types.UntypedFloat:
case types.Float64:
return c.ctx.DoubleType()
case types.Complex64:
return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)
case types.Complex128, types.UntypedComplex:
case types.Complex128:
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
case types.String, types.UntypedString:
return c.getLLVMRuntimeType("_string")
@@ -1285,28 +1280,10 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// Load function parameters
llvmParamIndex := 0
if _, indirectResult := b.hasIndirectResult(b.fn.Signature); indirectResult && !b.info.exported {
b.indirectReturn = b.llvmFn.Param(llvmParamIndex)
b.indirectReturn.SetName("return")
llvmParamIndex++
}
for _, param := range b.fn.Params {
llvmType := b.getLLVMType(param.Type())
if b.isIndirectParam(llvmType, b.info.exported) {
llvmParam := b.llvmFn.Param(llvmParamIndex)
llvmParam.SetName(param.Name())
b.indirectValues[param] = llvmParam
llvmParamIndex++
continue
}
var paramInfos []paramInfo
if b.info.exported {
paramInfos = b.expandDirectFormalParamType(llvmType, param.Name(), param.Type())
} else {
paramInfos = b.expandFormalParamType(llvmType, param.Name(), param.Type())
}
fields := make([]llvm.Value, 0, 1)
for _, info := range paramInfos {
for _, info := range b.expandFormalParamType(llvmType, param.Name(), param.Type()) {
param := b.llvmFn.Param(llvmParamIndex)
param.SetName(info.name)
fields = append(fields, param)
@@ -1377,6 +1354,15 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// diagnostic.
func (b *builder) createFunction() {
b.createFunctionStart(false)
// createFunctionStart returns early (leaving blockInfo unset) when it hits
// an error such as a redeclared symbol. Don't plow into the block loop and
// panic on an empty blockInfo — the error was already recorded.
if b.blockInfo == nil {
// TINYGO-DEBUG: surface the redeclaration collision.
fmt.Printf("TINYGO-DEBUG skipping fn (redeclared/errored): %s pkg=%s\n",
b.fn.RelString(nil), b.fn.Pkg.Pkg.Path())
return
}
// Fill blocks with instructions.
for _, block := range b.fn.DomPreorder() {
@@ -1444,7 +1430,7 @@ func (b *builder) createFunction() {
for _, phi := range b.phis {
block := phi.ssa.Block()
for i, edge := range phi.ssa.Edges {
llvmVal := b.getCallArgument(edge, false)
llvmVal := b.getValue(edge, getPos(phi.ssa))
llvmBlock := b.blockInfo[block.Preds[i].Index].exit
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
}
@@ -1453,9 +1439,6 @@ func (b *builder) createFunction() {
if b.NeedsStackObjects {
// Track phi nodes.
for _, phi := range b.phis {
if b.isOversizedAggregate(phi.ssa.Type()) {
continue
}
insertPoint := llvm.NextInstruction(phi.llvm)
for !insertPoint.IsAPHINode().IsNil() {
insertPoint = llvm.NextInstruction(insertPoint)
@@ -1547,7 +1530,10 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.diagnostics = append(b.diagnostics, err)
b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type()))
} else {
b.setValue(instr, value)
b.locals[instr] = value
if len(*instr.Referrers()) != 0 && b.NeedsStackObjects {
b.trackExpr(instr, value)
}
}
case *ssa.DebugRef:
// ignore
@@ -1567,8 +1553,10 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.CreateBr(blockJump)
case *ssa.MapUpdate:
m := b.getValue(instr.Map, getPos(instr))
key := b.getValue(instr.Key, getPos(instr))
value := b.getValue(instr.Value, getPos(instr))
mapType := instr.Map.Type().Underlying().(*types.Map)
b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos())
b.createMapUpdate(mapType.Key(), m, key, value, instr.Pos())
case *ssa.Panic:
value := b.getValue(instr.X, getPos(instr))
b.createRuntimeInvoke("_panic", []llvm.Value{value}, "")
@@ -1577,7 +1565,19 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
if b.hasDeferFrame() {
b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "")
}
b.createReturn(instr.Results, getPos(instr))
if len(instr.Results) == 0 {
b.CreateRetVoid()
} else if len(instr.Results) == 1 {
b.CreateRet(b.getValue(instr.Results[0], getPos(instr)))
} else {
// Multiple return values. Put them all in a struct.
retVal := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
for i, result := range instr.Results {
val := b.getValue(result, getPos(instr))
retVal = b.CreateInsertValue(retVal, val, i, "")
}
b.CreateRet(retVal)
}
case *ssa.RunDefers:
// Note where we're going to put the rundefers block
run := b.insertBasicBlock("rundefers.block")
@@ -1591,246 +1591,18 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
b.createChanSend(instr)
case *ssa.Store:
llvmAddr := b.getValue(instr.Addr, getPos(instr))
llvmVal := b.getValue(instr.Val, getPos(instr))
b.createNilCheck(instr.Addr, llvmAddr, "store")
llvmType := b.getLLVMType(instr.Val.Type())
if b.targetData.TypeAllocSize(llvmType) == 0 {
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store
return
}
b.storeValue(llvmAddr, instr.Val)
b.CreateStore(llvmVal, llvmAddr)
default:
b.addError(instr.Pos(), "unknown instruction: "+instr.String())
}
}
func (b *builder) setValue(value ssa.Value, llvmValue llvm.Value) {
if b.isAggregateValue(value.Type()) && !llvmValue.IsNil() && llvmValue.Type().TypeKind() == llvm.PointerTypeKind {
b.indirectValues[value] = llvmValue
return
}
b.locals[value] = llvmValue
if len(*value.Referrers()) != 0 && b.NeedsStackObjects {
b.trackExpr(value, llvmValue)
}
}
func (b *builder) createReturn(results []ssa.Value, pos token.Pos) {
if len(results) == 0 {
b.CreateRetVoid()
} else if !b.indirectReturn.IsNil() {
if len(results) == 1 {
b.storeValue(b.indirectReturn, results[0])
} else {
returnType := b.getLLVMResultType(b.fn.Signature)
for i, result := range results {
fieldPtr := b.CreateStructGEP(returnType, b.indirectReturn, i, "")
b.storeValue(fieldPtr, result)
}
}
b.CreateRetVoid()
} else if len(results) == 1 {
b.CreateRet(b.getValue(results[0], pos))
} else {
result := llvm.ConstNull(b.llvmFn.GlobalValueType().ReturnType())
for i, value := range results {
result = b.CreateInsertValue(result, b.getValue(value, pos), i, "")
}
b.CreateRet(result)
}
}
func (b *builder) isOversizedAggregate(typ types.Type) bool {
if !b.isAggregateValue(typ) {
return false
}
return b.isIndirectAggregate(b.getLLVMType(typ))
}
func (b *builder) isAggregateValue(typ types.Type) bool {
if tuple, ok := typ.(*types.Tuple); ok {
for i := 0; i < tuple.Len(); i++ {
if !isLLVMValueType(tuple.At(i).Type()) {
return false
}
}
} else {
switch typ.Underlying().(type) {
case *types.Array, *types.Struct:
default:
return false
}
if !isLLVMValueType(typ) {
return false
}
}
return true
}
func (b *builder) getValuePointer(value ssa.Value) llvm.Value {
if ptr, ok := b.indirectValues[value]; ok {
return ptr
}
llvmType := b.getLLVMType(value.Type())
ptr := b.createIndirectStorage(llvmType, value.Name())
b.storeValue(ptr, value)
return ptr
}
func (b *builder) getCallArgument(value ssa.Value, exported bool) llvm.Value {
paramType := b.getLLVMType(value.Type())
if b.isIndirectParam(paramType, exported) {
return b.getValuePointer(value)
}
return b.getValue(value, getPos(value))
}
func (b *builder) createIndirectStorage(typ llvm.Type, name string) llvm.Value {
// Use runtime.alloc here so storage that escapes remains valid. The
// allocation optimizer moves bounded non-escaping storage to the stack.
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
layout := b.createObjectLayout(typ, b.fn.Pos())
ptr := b.createAlloc(size, layout, b.targetData.ABITypeAlignment(typ), name)
if b.NeedsStackObjects {
b.trackPointer(ptr)
}
return ptr
}
func (b *builder) copyIndirectAggregate(dst, src llvm.Value, typ llvm.Type) {
size := llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false)
b.createMemCopy("memcpy", dst, src, size)
}
func (b *builder) storeValue(dst llvm.Value, value ssa.Value) {
typ := b.getLLVMType(value.Type())
if src, ok := b.indirectValues[value]; ok {
b.copyIndirectAggregate(dst, src, typ)
} else {
b.CreateStore(b.getValue(value, getPos(value)), dst)
}
}
func (b *builder) copyToIndirectStorage(src llvm.Value, typ llvm.Type, name string) llvm.Value {
dst := b.createIndirectStorage(typ, name)
b.copyIndirectAggregate(dst, src, typ)
return dst
}
func (b *builder) loadFromStorage(ptr llvm.Value, typ types.Type, name string) llvm.Value {
llvmType := b.getLLVMType(typ)
if b.isIndirectAggregate(llvmType) {
return b.copyToIndirectStorage(ptr, llvmType, name)
}
return b.CreateLoad(llvmType, ptr, name)
}
func (b *builder) getValueField(value ssa.Value, index int, resultType types.Type, name string) (llvm.Value, bool) {
if !b.isAggregateValue(value.Type()) {
return llvm.Value{}, false
}
valueType := b.getLLVMType(value.Type())
if _, indirect := b.indirectValues[value]; !indirect && !b.isIndirectAggregate(valueType) {
return llvm.Value{}, false
}
fieldPtr := b.CreateStructGEP(valueType, b.getValuePointer(value), index, "")
return b.loadFromStorage(fieldPtr, resultType, name), true
}
func (b *builder) zeroIndirectStorage(ptr llvm.Value, typ llvm.Type) {
memset := b.getMemsetFunc()
b.createCall(memset.GlobalValueType(), memset, []llvm.Value{
ptr,
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(typ), false),
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
}, "")
}
type valueStorage struct {
ptr, size llvm.Value
temporary bool
}
func (b *builder) getValueStorage(value ssa.Value, name string) valueStorage {
typ := b.getLLVMType(value.Type())
if b.isIndirectAggregate(typ) {
return valueStorage{ptr: b.getValuePointer(value)}
}
ptr, size := b.createTemporaryAlloca(typ, name)
b.storeValue(ptr, value)
return valueStorage{ptr: ptr, size: size, temporary: true}
}
func (b *builder) endValueStorage(storage valueStorage) {
if storage.temporary {
b.emitLifetimeEnd(storage.ptr, storage.size)
}
}
type runtimeValueResult struct {
valueType llvm.Type
resultType llvm.Type
result llvm.Value
valuePtr llvm.Value
valueSize llvm.Value
temporary bool
zero bool
commaOk bool
}
func (b *builder) createRuntimeValueResult(valueType llvm.Type, commaOk, zeroAsNull bool, name string) runtimeValueResult {
result := runtimeValueResult{
valueType: valueType,
resultType: valueType,
valueSize: llvm.ConstInt(b.uintptrType, b.targetData.TypeAllocSize(valueType), false),
commaOk: commaOk,
}
if commaOk {
result.resultType = b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false)
}
if b.isIndirectAggregate(result.resultType) {
result.result = b.createIndirectStorage(result.resultType, name+".result")
result.valuePtr = result.result
if commaOk {
result.valuePtr = b.CreateStructGEP(result.resultType, result.result, 0, "")
}
return result
}
if zeroAsNull && b.targetData.TypeAllocSize(valueType) == 0 {
result.valuePtr = llvm.ConstNull(b.dataPtrType)
result.zero = true
return result
}
result.valuePtr, result.valueSize = b.createTemporaryAlloca(valueType, name+".value")
result.temporary = true
return result
}
func (r runtimeValueResult) finish(b *builder, commaOk llvm.Value, name string) llvm.Value {
if !r.result.IsNil() {
if r.commaOk {
b.CreateStore(commaOk, b.CreateStructGEP(r.resultType, r.result, 1, ""))
}
return r.result
}
var value llvm.Value
if r.zero {
value = llvm.ConstNull(r.valueType)
} else {
value = b.CreateLoad(r.valueType, r.valuePtr, name)
}
if r.temporary {
b.emitLifetimeEnd(r.valuePtr, r.valueSize)
}
if !r.commaOk {
return value
}
result := llvm.Undef(r.resultType)
result = b.CreateInsertValue(result, value, 0, "")
return b.CreateInsertValue(result, commaOk, 1, "")
}
// createBuiltin lowers a builtin Go function (append, close, delete, etc.) to
// LLVM IR. It uses runtime calls for some builtins.
func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, callName string, pos token.Pos) (llvm.Value, error) {
@@ -2216,7 +1988,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
if fn := instr.StaticCallee(); fn != nil {
// Direct function call, either to a named or anonymous (directly
// applied) function call. If it is anonymous, it may be a closure.
name := b.getFunctionInfo(fn).linkName
name := fn.RelString(nil)
switch {
case name == "device.Asm" || name == "device/arm.Asm" || name == "device/arm64.Asm" || name == "device/avr.Asm" || name == "device/riscv.Asm":
return b.createInlineAsm(instr.Args)
@@ -2265,10 +2037,14 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
}
}
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getValue(param, getPos(instr)))
}
// Try to call the function directly for trivially static calls.
var callee, context llvm.Value
var calleeType llvm.Type
var invokeTypecode, invokeReceiver llvm.Value
exported := false
if fn := instr.StaticCallee(); fn != nil {
calleeType, callee = b.getFunction(fn)
@@ -2298,18 +2074,19 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
exported = info.exported
} else if call, ok := instr.Value.(*ssa.Builtin); ok {
// Builtin function (append, close, delete, etc.).)
var params []llvm.Value
var argTypes []types.Type
for _, arg := range instr.Args {
argTypes = append(argTypes, arg.Type())
params = append(params, b.getValue(arg, getPos(instr)))
}
return b.createBuiltin(argTypes, params, call.Name(), instr.Pos())
} else if instr.IsInvoke() {
// Interface method call (aka invoke call).
itf := b.getValue(instr.Value, getPos(instr)) // interface value (runtime._interface)
invokeTypecode = b.CreateExtractValue(itf, 0, "invoke.func.typecode")
invokeReceiver = b.CreateExtractValue(itf, 1, "invoke.func.value")
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver
// Prefix the params with receiver value and suffix with typecode.
params = append([]llvm.Value{value}, params...)
params = append(params, typecode)
callee = b.getInvokeFunction(instr)
calleeType = callee.GlobalValueType()
context = llvm.Undef(b.dataPtrType)
@@ -2323,23 +2100,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
b.createNilCheck(instr.Value, callee, "fpcall")
}
var params []llvm.Value
for _, param := range instr.Args {
params = append(params, b.getCallArgument(param, exported))
}
if instr.IsInvoke() {
params = append([]llvm.Value{invokeReceiver}, params...)
params = append(params, invokeTypecode)
}
if !exported {
if resultType, indirectResult := b.hasIndirectResult(instr.Signature()); indirectResult {
result := b.createIndirectStorage(resultType, "call.result")
params = append([]llvm.Value{result}, params...)
params = append(params, context)
b.createInvoke(calleeType, callee, params, "")
return result, nil
}
// This function takes a context parameter.
// Add it to the end of the parameter list.
params = append(params, context)
@@ -2378,9 +2139,6 @@ func (b *builder) getValue(expr ssa.Value, pos token.Pos) llvm.Value {
return value
default:
// other (local) SSA value
if value, ok := b.indirectValues[expr]; ok {
return b.CreateLoad(b.getLLVMType(expr.Type()), value, "")
}
if value, ok := b.locals[expr]; ok {
return value
} else {
@@ -2405,10 +2163,13 @@ func (c *compilerContext) maxSliceSize(elementType llvm.Type) uint64 {
if elementSize == 0 {
elementSize = 1
}
maxSize := min(
// len(slice) is an int. Make sure the length remains small enough to fit in
// an int.
maxPointerValue/elementSize, maxIntegerValue)
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
}
return maxSize
}
@@ -2463,15 +2224,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// This instruction changes the type, but the underlying value remains
// the same. This is often a no-op, but sometimes we have to change the
// LLVM type as well.
llvmType := b.getLLVMType(expr.Type())
if b.isIndirectAggregate(llvmType) {
sourceType := b.getLLVMType(expr.X.Type())
if !b.isIndirectAggregate(sourceType) || b.targetData.TypeAllocSize(sourceType) != b.targetData.TypeAllocSize(llvmType) {
return llvm.Value{}, errors.New("todo: indirect aggregate ChangeType with different layout")
}
return b.getValuePointer(expr.X), nil
}
x := b.getValue(expr.X, getPos(expr))
llvmType := b.getLLVMType(expr.Type())
if x.Type() == llvmType {
// Different Go type but same LLVM type (for example, named int).
// This is the common case.
@@ -2501,15 +2255,9 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
if _, ok := expr.Tuple.(*ssa.Select); ok {
return b.getChanSelectResult(expr), nil
}
if value, ok := b.getValueField(expr.Tuple, expr.Index, expr.Type(), expr.Name()); ok {
return value, nil
}
value := b.getValue(expr.Tuple, getPos(expr))
return b.CreateExtractValue(value, expr.Index, ""), nil
case *ssa.Field:
if value, ok := b.getValueField(expr.X, expr.Field, expr.Type(), expr.Name()); ok {
return value, nil
}
value := b.getValue(expr.X, getPos(expr))
result := b.CreateExtractValue(value, expr.Field, "")
return result, nil
@@ -2532,11 +2280,11 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
case *ssa.Global:
panic("global is not an expression")
case *ssa.Index:
collection := b.getValue(expr.X, getPos(expr))
index := b.getValue(expr.Index, getPos(expr))
switch xType := expr.X.Type().Underlying().(type) {
case *types.Basic: // extract byte from string
collection := b.getValue(expr.X, getPos(expr))
// Value type must be a string, which is a basic type.
if xType.Info()&types.IsString == 0 {
panic("lookup on non-string?")
@@ -2570,12 +2318,13 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// Can't load directly from array (as index is non-constant), so
// have to do it using an alloca+gep+load.
arrayType := b.getLLVMType(expr.X.Type())
storage := b.getValueStorage(expr.X, "index.alloca")
arrayType := collection.Type()
alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca")
b.CreateStore(collection, alloca)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep")
result := b.loadFromStorage(ptr, expr.Type(), "index.load")
b.endValueStorage(storage)
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep")
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load")
b.emitLifetimeEnd(alloca, allocaSize)
return result, nil
default:
panic("unknown *ssa.Index type")
@@ -2634,21 +2383,17 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
case *ssa.Lookup: // map lookup
value := b.getValue(expr.X, getPos(expr))
index := b.getValue(expr.Index, getPos(expr))
valueType := expr.Type()
if expr.CommaOk {
valueType = valueType.(*types.Tuple).At(0).Type()
}
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.Index, expr.CommaOk, expr.Pos())
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, index, expr.CommaOk, expr.Pos())
case *ssa.MakeChan:
return b.createMakeChan(expr), nil
case *ssa.MakeClosure:
return b.parseMakeClosure(expr)
case *ssa.MakeInterface:
if b.isOversizedAggregate(expr.X.Type()) {
typ := b.getLLVMType(expr.X.Type())
ptr := b.copyToIndirectStorage(b.getValuePointer(expr.X), typ, "interface.value")
return b.createMakeInterfaceFromPointer(ptr, expr.X.Type()), nil
}
val := b.getValue(expr.X, getPos(expr))
return b.createMakeInterface(val, expr.X.Type(), expr.Pos()), nil
case *ssa.MakeMap:
@@ -2682,7 +2427,8 @@ 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.createAlloc(sliceSize, layoutValue, elemAlign, "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
// the bounds check.
@@ -2715,8 +2461,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil
}
case *ssa.Phi:
phiType := b.storedParamType(b.getLLVMType(expr.Type()), false)
phi := b.CreatePHI(phiType, "")
phi := b.CreatePHI(b.getLLVMType(expr.Type()), "")
b.phis = append(b.phis, phiNode{expr, phi})
return phi, nil
case *ssa.Range:
@@ -3719,7 +3464,8 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
return fn, nil
} else {
b.createNilCheck(unop.X, x, "deref")
return b.loadFromStorage(x, unop.Type(), ""), nil
load := b.CreateLoad(valueType, x, "")
return load, nil
}
case token.XOR: // ^x, toggle all bits in integer
return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
+18 -174
View File
@@ -4,7 +4,6 @@ import (
"flag"
"go/types"
"os"
"regexp"
"strconv"
"strings"
"testing"
@@ -52,7 +51,6 @@ func TestCompiler(t *testing.T) {
{"gc.go", "", ""},
{"zeromap.go", "", ""},
{"generics.go", "", ""},
{"large.go", "", ""},
}
if goMinor >= 20 {
tests = append(tests, testCase{"go1.20.go", "", ""})
@@ -60,9 +58,6 @@ func TestCompiler(t *testing.T) {
if goMinor >= 21 {
tests = append(tests, testCase{"go1.21.go", "", ""})
}
if goMinor >= 27 {
tests = append(tests, testCase{"go1.27.go", "", ""})
}
for _, tc := range tests {
name := tc.file
@@ -127,138 +122,30 @@ func TestCompiler(t *testing.T) {
t.Fatal("failed to read golden file:", err)
}
if diff := diffIR(string(expected), mod.String()); diff != "" {
t.Errorf("output does not match expected output (re-run with -update to regenerate):\n%s", diff)
if !fuzzyEqualIR(mod.String(), string(expected)) {
t.Errorf("output does not match expected output:\n%s", mod.String())
}
})
}
}
func TestOptimizedLargeAggregateABI(t *testing.T) {
options := &compileopts.Options{Target: "wasm"}
mod, errs := testCompilePackage(t, options, "large-optimized.go")
if len(errs) != 0 {
for _, err := range errs {
t.Error(err)
}
return
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
return false
}
defer mod.Dispose()
passOptions := llvm.NewPassBuilderOptions()
defer passOptions.Dispose()
if err := mod.RunPasses("default<O2>", llvm.TargetMachine{}, passOptions); err != nil {
t.Fatal(err)
}
resultFn := mod.NamedFunction("main.makeLargeOptimizedValue")
if resultFn.IsNil() {
t.Fatal("missing function main.makeLargeOptimizedValue")
}
if resultType := resultFn.GlobalValueType().ReturnType(); resultType.TypeKind() != llvm.VoidTypeKind {
t.Errorf("large aggregate result was promoted to %s", resultType)
}
for _, name := range []string{
"main.makeLargeOptimizedValue",
"main.readLargeOptimizedValue",
"main.readMixedLargeOptimizedValue",
} {
fn := mod.NamedFunction(name)
if fn.IsNil() {
t.Fatalf("missing function %s", name)
}
if paramType := fn.GlobalValueType().ParamTypes()[0]; paramType.TypeKind() != llvm.PointerTypeKind {
t.Errorf("%s aggregate parameter was promoted to %s", name, paramType)
for i, line1 := range lines1 {
line2 := lines2[i]
if line1 != line2 {
return false
}
}
}
// normalizeIR canonicalizes LLVM IR so a single golden file keeps matching
// across LLVM versions. Golden files are written against LLVM <21; newer LLVM
// prints some attributes differently.
func normalizeIR(s string) string {
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
// which LLVM printed before any co-occurring attribute such as
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
// equivalent 'captures(none)' instead, and after such attributes (e.g.
// "ptr readonly captures(none)"). Normalize both name and position back
// to the old spelling.
s = normalizeCapturesAttr(s)
// LLVM 21+ also added an explicit 'nocreateundeforpoison' attribute to
// certain intrinsic declarations (e.g. llvm.umin) that were implicitly
// assumed not to create undef/poison before. It's unrelated to the
// behavior under test, so ignore it for comparison.
s = strings.ReplaceAll(s, "nocreateundeforpoison ", "")
// LLVM 22 dropped the (redundant) i64 size argument from
// llvm.lifetime.start/end. Normalize away that argument so golden files
// written against the two-argument form still match.
s = lifetimeSizeArgRe.ReplaceAllString(s, "$1")
return s
}
// diffIR compares two LLVM IR strings, ignoring irrelevant lines (comments,
// empty lines, etc.) and normalizing LLVM-version-specific spellings via
// normalizeIR. It returns "" when they are equal. Otherwise it returns a
// compact diff of only the region that differs: the common prefix and suffix
// are trimmed, then the differing expected lines (prefixed "-") are shown
// followed by the differing actual lines (prefixed "+").
func diffIR(expected, actual string) string {
exp := filterIrrelevantIRLines(strings.Split(normalizeIR(expected), "\n"))
act := filterIrrelevantIRLines(strings.Split(normalizeIR(actual), "\n"))
// Trim the common prefix.
start := 0
for start < len(exp) && start < len(act) && exp[start] == act[start] {
start++
}
// Trim the common suffix.
e, a := len(exp), len(act)
for e > start && a > start && exp[e-1] == act[a-1] {
e--
a--
}
if start == e && start == a {
return "" // equal
}
var b strings.Builder
b.WriteString("first difference at relevant line ")
b.WriteString(strconv.Itoa(start + 1))
b.WriteString(":\n")
for _, line := range exp[start:e] {
b.WriteString("- ")
b.WriteString(line)
b.WriteByte('\n')
}
for _, line := range act[start:a] {
b.WriteString("+ ")
b.WriteString(line)
b.WriteByte('\n')
}
return b.String()
}
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
// around).
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
// lifetimeSizeArgRe matches the i64 size argument of an
// llvm.lifetime.start/end call or declaration, which LLVM 22 removed.
var lifetimeSizeArgRe = regexp.MustCompile(`(@llvm\.lifetime\.(?:start|end)\.p0\()i64(?: immarg| \d+), `)
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
// written against LLVM <21 keep matching.
func normalizeCapturesAttr(s string) string {
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
return s
return true
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
@@ -301,9 +188,9 @@ func TestCompilerErrors(t *testing.T) {
t.Error(err)
}
errorsFileString := strings.ReplaceAll(string(errorsFile), "\r\n", "\n")
for line := range strings.SplitSeq(errorsFileString, "\n") {
if after, ok := strings.CutPrefix(line, "// ERROR: "); ok {
expectedErrors = append(expectedErrors, after)
for _, line := range strings.Split(errorsFileString, "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
expectedErrors = append(expectedErrors, strings.TrimPrefix(line, "// ERROR: "))
}
}
@@ -327,49 +214,6 @@ func TestCompilerErrors(t *testing.T) {
}
}
func TestAggregateValueCount(t *testing.T) {
t.Parallel()
ctx := llvm.NewContext()
defer ctx.Dispose()
byteType := ctx.Int8Type()
tests := []struct {
name string
typ llvm.Type
count uint64
exceeded bool
}{
{"empty", llvm.ArrayType(byteType, 0), 0, false},
{"limit", llvm.ArrayType(byteType, 1024), 1024, false},
{"over limit", llvm.ArrayType(byteType, 1025), 0, true},
{"combined limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 512),
llvm.ArrayType(byteType, 512),
}, false), 1024, false},
{"combined over limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 1000),
llvm.ArrayType(byteType, 1000),
}, false), 0, true},
{"comma-ok over limit", ctx.StructType([]llvm.Type{
llvm.ArrayType(byteType, 1024),
ctx.Int1Type(),
}, false), 0, true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
count, exceeded := aggregateValueCount(test.typ, 0)
if exceeded != test.exceeded {
t.Errorf("expected exceeded=%t, got %t", test.exceeded, exceeded)
}
if !exceeded && count != test.count {
t.Errorf("expected count=%d, got %d", test.count, count)
}
})
}
}
// Build a package given a number of compiler options and a file.
func testCompilePackage(t *testing.T, options *compileopts.Options, file string) (llvm.Module, []error) {
target, err := compileopts.LoadTarget(options)
+90 -97
View File
@@ -32,7 +32,7 @@ func (b *builder) supportsRecover() bool {
// proposal of WebAssembly:
// https://github.com/WebAssembly/exception-handling
return false
case "xtensa":
case "riscv64", "xtensa":
// TODO: add support for these architectures
return false
default:
@@ -116,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:
// * Registers are either clobbered or, on 386, saved for longjmp to
// restore if the ABI requires them to survive calls.
// * All registers (both callee-saved and caller saved) are clobbered
// after the inline assembly returns.
// * The assembly stores the address just past the end of the assembly
// into the jump buffer.
// * The return value (eax, rax, r0, etc) is set to zero in the inline
@@ -130,12 +130,8 @@ 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},~{ecx},~{edx},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
constraints = "={eax},{ebx},~{ebx},~{ecx},~{edx},~{esi},~{edi},~{ebp},~{xmm0},~{xmm1},~{xmm2},~{xmm3},~{xmm4},~{xmm5},~{xmm6},~{xmm7},~{fpsr},~{fpcr},~{flags},~{dirflag},~{memory}"
// This doesn't include the floating point stack because TinyGo uses
// newer floating point instructions.
case "x86_64":
@@ -217,20 +213,12 @@ sw $$ra, 4($$5)
// So only add them when using hardfloat.
constraints += ",~{$f0},~{$f1},~{$f2},~{$f3},~{$f4},~{$f5},~{$f6},~{$f7},~{$f8},~{$f9},~{$f10},~{$f11},~{$f12},~{$f13},~{$f14},~{$f15},~{$f16},~{$f17},~{$f18},~{$f19},~{$f20},~{$f21},~{$f22},~{$f23},~{$f24},~{$f25},~{$f26},~{$f27},~{$f28},~{$f29},~{$f30},~{$f31}"
}
case "riscv32", "riscv64":
if b.archFamily() == "riscv32" {
asmString = `
case "riscv32":
asmString = `
la a2, 1f
sw a2, 4(a1)
li a0, 0
1:`
} else {
asmString = `
la a2, 1f
sd a2, 8(a1)
li a0, 0
1:`
}
constraints = "={a0},{a1},~{a1},~{a2},~{a3},~{a4},~{a5},~{a6},~{a7},~{s0},~{s1},~{s2},~{s3},~{s4},~{s5},~{s6},~{s7},~{s8},~{s9},~{s10},~{s11},~{t0},~{t1},~{t2},~{t3},~{t4},~{t5},~{t6},~{ra},~{f0},~{f1},~{f2},~{f3},~{f4},~{f5},~{f6},~{f7},~{f8},~{f9},~{f10},~{f11},~{f12},~{f13},~{f14},~{f15},~{f16},~{f17},~{f18},~{f19},~{f20},~{f21},~{f22},~{f23},~{f24},~{f25},~{f26},~{f27},~{f28},~{f29},~{f30},~{f31},~{memory}"
default:
// This case should have been handled by b.supportsRecover().
@@ -367,44 +355,6 @@ type tarjanNode struct {
cyclic bool
}
type llvmValueList struct {
values []llvm.Value
types []llvm.Type
}
func newLLVMValueList(values ...llvm.Value) llvmValueList {
var list llvmValueList
list.append(values...)
return list
}
func (l *llvmValueList) append(values ...llvm.Value) {
for _, value := range values {
l.values = append(l.values, value)
l.types = append(l.types, value.Type())
}
}
func (l *llvmValueList) appendSSAValues(values []ssa.Value, lower func(ssa.Value) llvm.Value) {
for _, value := range values {
l.append(lower(value))
}
}
func (b *builder) loadDeferredCallParams(structType llvm.Type, ptr llvm.Value) []llvm.Value {
fieldTypes := structType.StructElementTypes()
values := make([]llvm.Value, 0, len(fieldTypes)-2)
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(fieldTypes); i++ {
fieldPtr := b.CreateInBoundsGEP(structType, ptr, []llvm.Value{
zero,
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
}, "gep")
values = append(values, b.CreateLoad(fieldTypes[i], fieldPtr, "param"))
}
return values
}
// createDefer emits a single defer instruction, to be run when this function
// returns.
func (b *builder) createDefer(instr *ssa.Defer) {
@@ -412,28 +362,31 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// make a linked list.
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
var values llvmValueList
lowerArgument := func(value ssa.Value) llvm.Value {
return b.getCallArgument(value, false)
}
var values []llvm.Value
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
if instr.Call.IsInvoke() {
// Method call on an interface.
// Get callback type number.
key := b.getInvokeFunctionName(&instr.Call)
if _, ok := b.deferInvokeFuncs[key]; !ok {
b.deferInvokeFuncs[key] = len(b.allDeferFuncs)
methodName := instr.Call.Method.FullName()
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
}
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[key]), false)
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by the call parameters).
itf := b.getValue(instr.Call.Value, getPos(instr)) // interface
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
values = newLLVMValueList(callback, next, typecode, receiverValue)
values.appendSSAValues(instr.Call.Args, lowerArgument)
values = []llvm.Value{callback, next, typecode, receiverValue}
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
for _, arg := range instr.Call.Args {
val := b.getValue(arg, getPos(instr))
values = append(values, val)
valueTypes = append(valueTypes, val.Type())
}
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
// Regular function call.
@@ -445,11 +398,12 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = newLLVMValueList(callback, next)
exported := b.getFunctionInfo(callee).exported
values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value {
return b.getCallArgument(value, exported)
})
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
// Immediately applied function literal with free variables.
@@ -472,9 +426,14 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = newLLVMValueList(callback, next)
values.appendSSAValues(instr.Call.Args, lowerArgument)
values.append(context)
values = []llvm.Value{callback, next}
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
values = append(values, context)
valueTypes = append(valueTypes, context.Type())
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
var argTypes []types.Type
@@ -497,8 +456,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields).
values = newLLVMValueList(callback, next)
values.append(argValues...)
values = []llvm.Value{callback, next}
for _, param := range argValues {
values = append(values, param)
valueTypes = append(valueTypes, param.Type())
}
} else {
funcValue := b.getValue(instr.Call.Value, getPos(instr))
@@ -513,15 +475,20 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// Collect all values to be put in the struct (starting with
// runtime._defer fields, followed by all parameters including the
// context pointer).
values = newLLVMValueList(callback, next, funcValue)
values.appendSSAValues(instr.Call.Args, lowerArgument)
values = []llvm.Value{callback, next, funcValue}
valueTypes = append(valueTypes, funcValue.Type())
for _, param := range instr.Call.Args {
llvmParam := b.getValue(param, getPos(instr))
values = append(values, llvmParam)
valueTypes = append(valueTypes, llvmParam.Type())
}
}
// Make a struct out of the collected values to put in the deferred call
// struct.
deferredCallType := b.ctx.StructType(values.types, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
deferredCall := llvm.ConstNull(deferredCallType)
for i, value := range values.values {
for i, value := range values {
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
}
@@ -537,9 +504,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
// This may be hit a variable number of times, so use a heap allocation.
size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(deferredCallType, instr.Pos())
align := b.targetData.ABITypeAlignment(deferredCallType)
alloca = b.createAlloc(sizeValue, layoutValue, align, "defer.alloc.call")
nilPtr := llvm.ConstNull(b.dataPtrType)
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
@@ -623,11 +589,19 @@ func (b *builder) createRunDefers() {
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
}
valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false)
for _, arg := range callback.Args {
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
}
// Extract the params from the struct (including receiver).
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
deferredCallType := b.ctx.StructType(valueTypes, false)
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
var fnPtr llvm.Value
var fnType llvm.Type
@@ -656,7 +630,6 @@ func (b *builder) createRunDefers() {
// with a strict calling convention.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
}
forwardParams = b.prependIndirectResult(callback.Signature(), false, forwardParams, "defer.result")
b.createCall(fnType, fnPtr, forwardParams, "")
@@ -665,21 +638,27 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it.
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
exported := b.getFunctionInfo(callback).exported
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported)
for _, param := range getParams(callback.Signature) {
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := range getParams(callback.Signature) {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Plain TinyGo functions add some extra parameters to implement async functionality and function receivers.
// These parameters should not be supplied when calling into an external C/ASM function.
if !exported {
if !b.getFunctionInfo(callback).exported {
// Add the context parameter. We know it is ignored by the receiving
// function, but we have to pass one anyway.
forwardParams = append(forwardParams, llvm.Undef(b.dataPtrType))
}
forwardParams = b.prependIndirectResult(callback.Signature, exported, forwardParams, "defer.result")
// Call real function.
fnType, fn := b.getFunction(callback)
@@ -689,16 +668,24 @@ func (b *builder) createRunDefers() {
// Get the real defer struct type and cast to it.
fn := callback.Fn.(*ssa.Function)
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false)
params := fn.Signature.Params()
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
valueTypes = append(valueTypes, b.dataPtrType) // closure
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
forwardParams := []llvm.Value{}
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 2; i < len(valueTypes); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
forwardParam := b.CreateLoad(valueTypes[i], gep, "param")
forwardParams = append(forwardParams, forwardParam)
}
// Call deferred function.
fnType, llvmFn := b.getFunction(fn)
forwardParams = b.prependIndirectResult(fn.Signature, false, forwardParams, "defer.result")
b.createCall(fnType, llvmFn, forwardParams, "")
case *ssa.Builtin:
db := b.deferBuiltinFuncs[callback]
@@ -708,14 +695,20 @@ func (b *builder) createRunDefers() {
//Get signature from call results
params := callback.Type().Underlying().(*types.Signature).Params()
for v := range params.Variables() {
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
for i := 0; i < params.Len(); i++ {
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
}
deferredCallType := b.ctx.StructType(valueTypes, false)
// Extract the params from the struct.
argValues := b.loadDeferredCallParams(deferredCallType, deferData)
var argValues []llvm.Value
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
for i := 0; i < params.Len(); i++ {
gep := b.CreateInBoundsGEP(deferredCallType, deferData, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
forwardParam := b.CreateLoad(valueTypes[i+2], gep, "param")
argValues = append(argValues, forwardParam)
}
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
if err != nil {
+22 -97
View File
@@ -10,91 +10,6 @@ import (
"tinygo.org/x/go-llvm"
)
// LLVM recursively expands each struct field and array element in parameters
// and results into separate values. It gets very slow with too many values, so
// pass larger aggregates indirectly before LLVM expands them.
const maxDirectAggregateValues = 1024
func (c *compilerContext) getLLVMResultType(sig *types.Signature) llvm.Type {
switch sig.Results().Len() {
case 0:
return c.ctx.VoidType()
case 1:
return c.getLLVMType(sig.Results().At(0).Type())
default:
results := make([]llvm.Type, sig.Results().Len())
for i := range results {
results[i] = c.getLLVMType(sig.Results().At(i).Type())
}
return c.ctx.StructType(results, false)
}
}
func (c *compilerContext) hasIndirectResult(sig *types.Signature) (llvm.Type, bool) {
resultType := c.getLLVMResultType(sig)
return resultType, c.isIndirectAggregate(resultType)
}
func (c *compilerContext) isIndirectAggregate(typ llvm.Type) bool {
switch typ.TypeKind() {
case llvm.ArrayTypeKind, llvm.StructTypeKind:
_, exceeded := aggregateValueCount(typ, 0)
return exceeded
default:
return false
}
}
func aggregateValueCount(typ llvm.Type, count uint64) (uint64, bool) {
switch typ.TypeKind() {
case llvm.ArrayTypeKind:
length := uint64(typ.ArrayLength())
if length == 0 {
return count, false
}
elementCount, exceeded := aggregateValueCount(typ.ElementType(), 0)
if exceeded {
return count, true
}
if elementCount != 0 && length > (maxDirectAggregateValues-count)/elementCount {
return count, true
}
return count + length*elementCount, false
case llvm.StructTypeKind:
for _, field := range typ.StructElementTypes() {
var exceeded bool
count, exceeded = aggregateValueCount(field, count)
if exceeded {
return count, true
}
}
return count, false
default:
count++
return count, count > maxDirectAggregateValues
}
}
func isLLVMValueType(typ types.Type) bool {
switch typ := typ.Underlying().(type) {
case *types.Basic:
return typ.Kind() != types.Invalid
case *types.Array:
return isLLVMValueType(typ.Elem())
case *types.Struct:
for field := range typ.Fields() {
if !isLLVMValueType(field.Type()) {
return false
}
}
return true
case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
return true
default:
return false
}
}
// createFuncValue creates a function value from a raw function pointer with no
// context.
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
@@ -133,18 +48,28 @@ func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
// getLLVMFunctionType returns a LLVM function type for a given signature.
func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
returnType, indirectResult := c.hasIndirectResult(typ)
// Get the return type.
var returnType llvm.Type
switch typ.Results().Len() {
case 0:
// No return values.
returnType = c.ctx.VoidType()
case 1:
// Just one return value.
returnType = c.getLLVMType(typ.Results().At(0).Type())
default:
// Multiple return values. Put them together in a struct.
// This appears to be the common way to handle multiple return values in
// LLVM.
members := make([]llvm.Type, typ.Results().Len())
for i := 0; i < typ.Results().Len(); i++ {
members[i] = c.getLLVMType(typ.Results().At(i).Type())
}
returnType = c.ctx.StructType(members, false)
}
// Get the parameter types.
var paramTypes []llvm.Type
if indirectResult {
// LLVM expands aggregate returns into scalar leaves before deciding
// whether to pass them indirectly, so a large IR return can exhaust
// memory. Returning void avoids that expansion and cannot be demoted
// again. Keep the result pointer first so the context remains last.
paramTypes = append(paramTypes, c.dataPtrType)
returnType = c.ctx.VoidType()
}
if typ.Recv() != nil {
recv := c.getLLVMType(typ.Recv().Type())
if recv.StructName() == "runtime._interface" {
@@ -156,8 +81,8 @@ func (c *compilerContext) getLLVMFunctionType(typ *types.Signature) llvm.Type {
paramTypes = append(paramTypes, info.llvmType)
}
}
for v := range typ.Params().Variables() {
subType := c.getLLVMType(v.Type())
for i := 0; i < typ.Params().Len(); i++ {
subType := c.getLLVMType(typ.Params().At(i).Type())
for _, info := range c.expandFormalParamType(subType, "", nil) {
paramTypes = append(paramTypes, info.llvmType)
}
@@ -187,7 +112,7 @@ func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
// Store the bound variables in a single object, allocating it on the heap
// if necessary.
context := b.emitPointerPack(boundVars, expr.Pos())
context := b.emitPointerPack(boundVars)
// Create the closure.
_, fn := b.getFunction(f)
+11 -7
View File
@@ -5,7 +5,6 @@ package compiler
import (
"go/token"
"slices"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm"
@@ -29,10 +28,10 @@ func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comm
// Make the runtime call.
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
if align == 0 || align&(align-1) != 0 {
panic("invalid alignment")
if align != 0 {
// TODO: make sure all callsites set the correct alignment.
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
}
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
return call
}
@@ -89,7 +88,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.StructElementTypesCount()
for i := range numElements {
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -98,7 +97,7 @@ func (b *builder) trackValue(value llvm.Value) {
return
}
numElements := typ.ArrayLength()
for i := range numElements {
for i := 0; i < numElements; i++ {
subValue := b.CreateExtractValue(value, i, "")
b.trackValue(subValue)
}
@@ -119,7 +118,12 @@ func typeHasPointers(t llvm.Type) bool {
case llvm.PointerTypeKind:
return true
case llvm.StructTypeKind:
return slices.ContainsFunc(t.StructElementTypes(), typeHasPointers)
for _, subType := range t.StructElementTypes() {
if typeHasPointers(subType) {
return true
}
}
return false
case llvm.ArrayTypeKind:
if t.ArrayLength() == 0 {
return false
+15 -29
View File
@@ -47,16 +47,20 @@ func (b *builder) createGo(instr *ssa.Go) {
return
}
// Get all function parameters to pass to the goroutine.
var params []llvm.Value
for _, param := range instr.Call.Args {
params = append(params, b.expandFormalParam(b.getValue(param, getPos(instr)))...)
}
var prefix string
var funcPtr llvm.Value
var funcType llvm.Type
var context llvm.Value
hasContext := false
exported := false
if callee := instr.Call.StaticCallee(); callee != nil {
// Static callee is known. This makes it easier to start a new
// goroutine.
var context llvm.Value
switch value := instr.Call.Value.(type) {
case *ssa.Function:
// Goroutine call is regular function call. No context is necessary.
@@ -69,10 +73,10 @@ func (b *builder) createGo(instr *ssa.Go) {
panic("StaticCallee returned an unexpected value")
}
if !context.IsNil() {
params = append(params, context) // context parameter
hasContext = true
}
funcType, funcPtr = b.getFunction(callee)
exported = b.getFunctionInfo(callee).exported
} else if instr.Call.IsInvoke() {
// This is a method call on an interface value.
itf := b.getValue(instr.Call.Value, getPos(instr))
@@ -80,32 +84,23 @@ func (b *builder) createGo(instr *ssa.Go) {
itfValue := b.CreateExtractValue(itf, 1, "")
funcPtr = b.getInvokeFunction(&instr.Call)
funcType = funcPtr.GlobalValueType()
params = append(params, itfValue)
context = itfTypeCode
params = append([]llvm.Value{itfValue}, params...) // start with receiver
params = append(params, itfTypeCode) // end with typecode
} else {
// This is a function pointer.
// At the moment, two extra params are passed to the newly started
// goroutine:
// * The function context, for closures.
// * The function pointer (for tasks).
var context llvm.Value
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value, getPos(instr)))
funcType = b.getLLVMFunctionType(instr.Call.Value.Type().Underlying().(*types.Signature))
params = append(params, context, funcPtr)
hasContext = true
prefix = b.getFunctionInfo(b.fn).linkName
prefix = b.fn.RelString(nil)
}
for _, param := range instr.Call.Args {
params = append(params, b.getGoroutineCallArgument(param, exported)...)
}
if !context.IsNil() {
params = append(params, context)
}
if hasContext && instr.Call.StaticCallee() == nil {
params = append(params, funcPtr)
}
params = b.prependIndirectResult(instr.Call.Signature(), exported, params, "go.result")
paramBundle := b.emitPointerPack(params, instr.Pos())
paramBundle := b.emitPointerPack(params)
var stackSize llvm.Value
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
if b.AutomaticStackSize {
@@ -127,15 +122,6 @@ func (b *builder) createGo(instr *ssa.Go) {
b.createCall(fnType, start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.dataPtrType)}, "")
}
func (b *builder) getGoroutineCallArgument(value ssa.Value, exported bool) []llvm.Value {
typ := b.getLLVMType(value.Type())
arg := b.getCallArgument(value, exported)
if b.isIndirectParam(typ, exported) {
return []llvm.Value{b.copyToIndirectStorage(arg, typ, "go.param")}
}
return b.expandFormalParam(arg)
}
// Create an exported wrapper function for functions with the //go:wasmexport
// pragma. This wrapper function is quite complex when the scheduler is enabled:
// it needs to start a new goroutine each time the exported function is called.
@@ -153,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.getFunctionInfo(b.fn).linkName+suffix, exportedFnType)
exportedFn := llvm.AddFunction(b.mod, b.fn.RelString(nil)+suffix, exportedFnType)
b.addStandardAttributes(exportedFn)
llvmutil.AppendToGlobal(b.mod, "llvm.used", exportedFn)
exportedFn.AddFunctionAttr(b.ctx.CreateStringAttribute("wasm-export-name", b.info.wasmExport))
@@ -428,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 := range numParams {
for i := 0; i < numParams; i++ {
gep := b.CreateInBoundsGEP(stateStruct, statePtr, []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
+10 -12
View File
@@ -146,14 +146,13 @@ 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)
var constraints strings.Builder
constraints.WriteString("={r0}")
constraints := "={r0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints.WriteString(",0")
constraints += ",0"
} else {
constraints.WriteString(",{r" + strconv.Itoa(i) + "}")
constraints += ",{r" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -162,9 +161,9 @@ func (b *builder) emitSVCall(args []ssa.Value, pos token.Pos) (llvm.Value, error
// Implement the ARM calling convention by marking r1-r3 as
// clobbered. r0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints.WriteString(",~{r1},~{r2},~{r3}")
constraints += ",~{r1},~{r2},~{r3}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
@@ -185,14 +184,13 @@ 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)
var constraints strings.Builder
constraints.WriteString("={x0}")
constraints := "={x0}"
for i, arg := range args[1:] {
arg = arg.(*ssa.MakeInterface).X
if i == 0 {
constraints.WriteString(",0")
constraints += ",0"
} else {
constraints.WriteString(",{x" + strconv.Itoa(i) + "}")
constraints += ",{x" + strconv.Itoa(i) + "}"
}
llvmValue := b.getValue(arg, pos)
llvmArgs = append(llvmArgs, llvmValue)
@@ -201,9 +199,9 @@ func (b *builder) emitSV64Call(args []ssa.Value, pos token.Pos) (llvm.Value, err
// Implement the ARM64 calling convention by marking x1-x7 as
// clobbered. x0 is used as an output register so doesn't have to be
// marked as clobbered.
constraints.WriteString(",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}")
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, false, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
return b.CreateCall(fnType, target, llvmArgs, ""), nil
}
+157 -134
View File
@@ -84,11 +84,7 @@ const (
//
// An interface value is a {typecode, value} tuple named runtime._interface.
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val}, pos)
return b.createMakeInterfaceFromPointer(itfValue, typ)
}
func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value {
itfValue := b.emitPointerPack([]llvm.Value{val})
itfType := b.getTypeCode(typ)
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
itf = b.CreateInsertValue(itf, itfType, 0, "")
@@ -102,16 +98,9 @@ func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.
// doesn't match the underlying type of the interface.
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
if b.isIndirectAggregate(llvmType) {
return valuePtr
}
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
}
func (b *builder) extractValuePointerFromInterface(itf llvm.Value) llvm.Value {
return b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
}
func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
pkgpathName := "reflect/types.type.pkgpath.empty"
if pkgpath != "" {
@@ -136,16 +125,6 @@ func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value {
return pkgPathPtr
}
// isGenericMethod returns true for a method that has its own type parameters
// (independent of any type parameters on its receiver), e.g. the N method in
// "func (r *Rand) N[Int intType](n Int) Int { ... }". Like the reflect
// package, such methods are excluded from runtime method sets: they aren't
// instantiated, so their signature can't be represented in a type code, and
// they can never satisfy an interface method anyway.
func isGenericMethod(fn *types.Func) bool {
return fn.Signature().TypeParams().Len() > 0
}
// getTypeCode returns a reference to a type code.
// A type code is a pointer to a constant global that describes the type.
// This function returns a pointer to the 'kind' field (which might not be the
@@ -155,26 +134,22 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
typ = types.Unalias(typ)
ms := c.program.MethodSets.MethodSet(typ)
hasMethodSet := ms.Len() != 0
_, isInterface := typ.Underlying().(*types.Interface)
if isInterface {
hasMethodSet = false
}
// As defined in https://pkg.go.dev/reflect#Type:
// NumMethod returns the number of methods accessible using Method.
// 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
var hasMethodSet bool
for method := range ms.Methods() {
if isGenericMethod(method.Obj().(*types.Func)) {
continue
}
hasMethodSet = true
if isInterface || method.Obj().Exported() {
for i := 0; i < ms.Len(); i++ {
if isInterface || ms.At(i).Obj().Exported() {
numMethods++
}
}
if isInterface {
hasMethodSet = false
}
// Short-circuit all the global pointer logic here for pointers to pointers.
if typ, ok := typ.(*types.Pointer); ok {
@@ -218,12 +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 method := range ms.Methods() {
fn := method.Obj().(*types.Func)
if isGenericMethod(fn) {
continue
}
methods = append(methods, fn)
for i := 0; i < ms.Len(); i++ {
methods = append(methods, ms.At(i).Obj().(*types.Func))
}
methodSetType := types.NewStruct([]*types.Var{
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
@@ -519,7 +490,10 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
c.getTypeMethodSet(typ),
}, typeFields...)
}
alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4)
alignment := c.targetData.TypeAllocSize(c.dataPtrType)
if alignment < 4 {
alignment = 4
}
globalValue := c.ctx.ConstStruct(typeFields, false)
global.SetInitializer(globalValue)
if isLocal {
@@ -628,21 +602,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
case *types.Named:
tn := t.Obj()
if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() {
name := tn.Name()
if tn.Pkg() != nil {
name = tn.Pkg().Path() + "." + name
}
isLocal := false
if targs := t.TypeArgs(); targs.Len() != 0 {
parts := make([]string, targs.Len())
for i := range parts {
var local bool
parts[i], local = c.getTypeCodeName(targs.At(i))
isLocal = isLocal || local
}
name += "[" + strings.Join(parts, ",") + "]"
}
return "named:" + name, isLocal
// Package-scope or builtin: the printed name is unique.
return "named:" + t.String(), false
}
if tn.Parent() != nil {
// Ordinary function-local type. Use the un-//line-adjusted
@@ -749,8 +710,8 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// 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
// canonical function name as prefix. The function name encodes the type
// 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.
@@ -759,9 +720,9 @@ func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bo
// 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 (the canonical function name and raw token.Pos),
// so any package compiling the same instance produces the same
// identifier.
// 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
@@ -822,12 +783,12 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
walk(m, false)
case *ssa.Type:
mset := c.program.MethodSets.MethodSet(m.Type())
for method := range mset.Methods() {
walk(c.program.MethodValue(method), false)
for i := 0; i < mset.Len(); i++ {
walk(c.program.MethodValue(mset.At(i)), false)
}
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
for method := range pmset.Methods() {
walk(c.program.MethodValue(method), false)
for i := 0; i < pmset.Len(); i++ {
walk(c.program.MethodValue(pmset.At(i)), false)
}
}
}
@@ -849,8 +810,9 @@ func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) {
// 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 the canonical function
// name plus a per-function counter assigned in source order.
// 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
@@ -884,8 +846,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
}
}
targs := t.TypeArgs()
for t := range targs.Types() {
visit(t)
for i := 0; i < targs.Len(); i++ {
visit(targs.At(i))
}
visit(t.Underlying())
case *types.Pointer:
@@ -900,23 +862,23 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
visit(t.Key())
visit(t.Elem())
case *types.Struct:
for field := range t.Fields() {
visit(field.Type())
for i := 0; i < t.NumFields(); i++ {
visit(t.Field(i).Type())
}
case *types.Signature:
if p := t.Params(); p != nil {
for v := range p.Variables() {
visit(v.Type())
for i := 0; i < p.Len(); i++ {
visit(p.At(i).Type())
}
}
if r := t.Results(); r != nil {
for v := range r.Variables() {
visit(v.Type())
for i := 0; i < r.Len(); i++ {
visit(r.At(i).Type())
}
}
case *types.Tuple:
for v := range t.Variables() {
visit(v.Type())
for i := 0; i < t.Len(); i++ {
visit(t.At(i).Type())
}
case *types.Interface:
// A synthetic local type can be reachable only through a
@@ -925,8 +887,8 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
// 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 i := 0; i < t.NumMethods(); i++ {
visit(t.Method(i).Type())
}
}
}
@@ -961,7 +923,7 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
sort.Slice(found, func(i, j int) bool {
return found[i].Obj().Pos() < found[j].Obj().Pos()
})
enclosing := c.canonicalFunctionName(fn)
enclosing := fn.RelString(nil)
for i, named := range found {
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
}
@@ -970,18 +932,15 @@ func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) {
// 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 {
typeName, _ := c.getTypeCodeName(typ)
globalName := typeName + "$methodset"
globalName := typ.String() + "$methodset"
global := c.mod.NamedGlobal(globalName)
if global.IsNil() {
ms := c.program.MethodSets.MethodSet(typ)
// Create method set.
var signatures, wrappers []llvm.Value
for method := range ms.Methods() {
if isGenericMethod(method.Obj().(*types.Func)) {
continue
}
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
signatures = append(signatures, signatureGlobal)
fn := c.program.MethodValue(method)
@@ -996,7 +955,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// Construct global value.
globalValue := c.ctx.ConstStruct([]llvm.Value{
llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false),
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
llvm.ConstArray(c.dataPtrType, signatures),
c.ctx.ConstStruct(wrappers, false),
}, false)
@@ -1012,15 +971,14 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
// getMethodSignatureName returns a unique name (that can be used as the name of
// a global) for the given method.
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
name := method.Name()
var prefix string
signature := methodSignature(method)
var globalName string
if token.IsExported(method.Name()) {
prefix = "reflect/methods."
globalName = "reflect/methods." + signature
} else {
prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods."
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
}
signature, _ := c.getTypeCodeName(method.Type())
return prefix + name + ":" + signature
return globalName
}
// getMethodSignature returns a global variable which is a reference to an
@@ -1099,21 +1057,6 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
if expr.CommaOk {
nextBlock := b.insertBasicBlock("typeassert.next")
b.currentBlockInfo.exit = nextBlock
if b.isIndirectAggregate(assertedType) {
resultType := b.getLLVMType(expr.Type())
result := b.createIndirectStorage(resultType, "typeassert.result")
b.zeroIndirectStorage(result, resultType)
b.CreateCondBr(commaOk, okBlock, nextBlock)
b.SetInsertPointAtEnd(okBlock)
valuePtr := b.extractValuePointerFromInterface(itf)
b.copyIndirectAggregate(b.CreateStructGEP(resultType, result, 0, ""), valuePtr, assertedType)
b.CreateBr(nextBlock)
b.SetInsertPointAtEnd(nextBlock)
b.CreateStore(commaOk, b.CreateStructGEP(resultType, result, 1, ""))
return result
}
b.CreateCondBr(commaOk, okBlock, nextBlock)
// Retrieve the value from the interface if the type assert was
@@ -1240,33 +1183,26 @@ 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 {
fnName := c.getInvokeFunctionName(instr)
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 v := range sig.Params().Variables() {
paramTuple = append(paramTuple, v)
for i := 0; i < sig.Params().Len(); i++ {
paramTuple = append(paramTuple, sig.Params().At(i))
}
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))
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
c.addStandardDeclaredAttributes(llvmFn)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
if _, indirect := c.hasIndirectResult(sig); indirect {
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-indirect-result", "true"))
}
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
}
return llvmFn
}
func (c *compilerContext) getInvokeFunctionName(instr *ssa.CallCommon) string {
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
return s + "." + instr.Method.Name() + "$invoke"
}
// createInterfaceTypeAssert creates a call to a declared-but-not-defined
// $typeassert function for the given interface. This function will be defined
// by the interface lowering pass as a type-ID comparison chain, avoiding the
@@ -1301,7 +1237,6 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
// Get the expanded receiver type.
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
var expandedReceiverType []llvm.Type
receiverIndirect := c.isIndirectAggregate(receiverType)
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
expandedReceiverType = append(expandedReceiverType, info.llvmType)
}
@@ -1316,13 +1251,7 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
}
// create wrapper function
resultOffset := 0
if _, indirect := c.hasIndirectResult(fn.Signature); indirect {
resultOffset = 1
}
paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...)
paramTypes = append(paramTypes, c.dataPtrType)
paramTypes = append(paramTypes, llvmFnType.ParamTypes()[resultOffset+len(expandedReceiverType):]...)
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.addStandardAttributes(wrapper)
@@ -1348,15 +1277,8 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
block := b.ctx.AddBasicBlock(wrapper, "entry")
b.SetInsertPointAtEnd(block)
params := append([]llvm.Value{}, wrapper.Params()[:resultOffset]...)
receiverParam := wrapper.Param(resultOffset)
if receiverIndirect {
params = append(params, receiverParam)
} else {
receiverValue := b.emitPointerUnpack(receiverParam, []llvm.Type{receiverType})[0]
params = append(params, b.expandFormalParam(receiverValue)...)
}
params = append(params, wrapper.Params()[resultOffset+1:]...)
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
b.CreateCall(llvmFnType, llvmFn, params, "")
b.CreateRetVoid()
@@ -1367,3 +1289,104 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
return wrapper
}
// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
// String() string
// Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
return method.Name() + signature(method.Type().(*types.Signature))
}
// Make a readable version of a function (pointer) signature.
// Examples:
//
// () string
// (string, int) (int, error)
func signature(sig *types.Signature) string {
s := ""
if sig.Params().Len() == 0 {
s += "()"
} else {
s += "("
for i := 0; i < sig.Params().Len(); i++ {
if i > 0 {
s += ", "
}
s += typestring(sig.Params().At(i).Type())
}
s += ")"
}
if sig.Results().Len() == 0 {
// keep as-is
} else if sig.Results().Len() == 1 {
s += " " + typestring(sig.Results().At(0).Type())
} else {
s += " ("
for i := 0; i < sig.Results().Len(); i++ {
if i > 0 {
s += ", "
}
s += typestring(sig.Results().At(i).Type())
}
s += ")"
}
return s
}
// typestring returns a stable (human-readable) type string for the given type
// that can be used for interface equality checks. It is almost (but not
// exactly) the same as calling t.String(). The main difference is some
// normalization around `byte` vs `uint8` for example.
func typestring(t types.Type) string {
// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
switch t := types.Unalias(t).(type) {
case *types.Array:
return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
case *types.Basic:
return basicTypeNames[t.Kind()]
case *types.Chan:
switch t.Dir() {
case types.SendRecv:
return "chan (" + typestring(t.Elem()) + ")"
case types.SendOnly:
return "chan<- (" + typestring(t.Elem()) + ")"
case types.RecvOnly:
return "<-chan (" + typestring(t.Elem()) + ")"
default:
panic("unknown channel direction")
}
case *types.Interface:
methods := make([]string, t.NumMethods())
for i := range methods {
method := t.Method(i)
methods[i] = method.Name() + signature(method.Type().(*types.Signature))
}
return "interface{" + strings.Join(methods, ";") + "}"
case *types.Map:
return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
case *types.Named:
return t.String()
case *types.Pointer:
return "*" + typestring(t.Elem())
case *types.Signature:
return "func" + signature(t)
case *types.Slice:
return "[]" + typestring(t.Elem())
case *types.Struct:
fields := make([]string, t.NumFields())
for i := range fields {
field := t.Field(i)
fields[i] = field.Name() + " " + typestring(field.Type())
if tag := t.Tag(i); tag != "" {
fields[i] += " " + strconv.Quote(tag)
}
}
return "struct{" + strings.Join(fields, ";") + "}"
default:
panic("unknown type: " + t.String())
}
}
+5 -6
View File
@@ -54,7 +54,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
// pointer value directly. It returns the pointer with the packed data.
// If the values are all constants, they are be stored in a constant global and
// deduplicated.
func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value {
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
valueTypes := make([]llvm.Type, len(values))
for i, value := range values {
valueTypes[i] = value.Type()
@@ -128,9 +128,8 @@ func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(packedType, pos)
align := b.targetData.ABITypeAlignment(packedType)
packedAlloc := b.createAlloc(sizeValue, layoutValue, align, "")
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
}
@@ -207,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 := range buf {
for i := 0; i < len(buf); i++ {
ch := uint64(buf[i])
value = c.builder.CreateInsertValue(value, llvm.ConstInt(elementType, ch, false), i, "")
}
@@ -391,7 +390,7 @@ func (c *compilerContext) buildPointerBitmap(
return
}
elementSize /= ptrAlign
for i := range len {
for i := 0; i < len; i++ {
c.buildPointerBitmap(
dst,
ptrAlign,
@@ -418,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.SplitSeq(c.Features, ",") {
for _, feature := range strings.Split(c.Features, ",") {
if feature == "+thumb-mode" {
isThumb = true
}
+6 -56
View File
@@ -44,7 +44,7 @@ func CreateTemporaryAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type, n
alloca = CreateEntryBlockAlloca(builder, t, name)
size = llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return
}
@@ -58,14 +58,14 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
builder.SetInsertPointBefore(inst)
size := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(t), false)
fnType, fn := getLifetimeStartFunc(mod)
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
if next := llvm.NextInstruction(inst); !next.IsNil() {
builder.SetInsertPointBefore(next)
} else {
builder.SetInsertPointAtEnd(inst.InstructionParent())
}
fnType, fn = getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
builder.CreateCall(fnType, fn, []llvm.Value{size, alloca}, "")
return alloca
}
@@ -74,27 +74,7 @@ func CreateInstructionAlloca(builder llvm.Builder, mod llvm.Module, t llvm.Type,
// createTemporaryAlloca.
func EmitLifetimeEnd(builder llvm.Builder, mod llvm.Module, ptr, size llvm.Value) {
fnType, fn := getLifetimeEndFunc(mod)
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, ptr), "")
}
// lifetimeCallArgs returns the arguments to pass to a call of the
// llvm.lifetime.start/end intrinsics. LLVM 22 dropped the (redundant,
// already required to match the alloca size) i64 size argument, so the
// intrinsic now only takes the pointer.
func lifetimeCallArgs(size, ptr llvm.Value) []llvm.Value {
if Version() >= 22 {
return []llvm.Value{ptr}
}
return []llvm.Value{size, ptr}
}
// lifetimeFuncType returns the function type of the llvm.lifetime.start/end
// intrinsics, which lost their i64 size parameter in LLVM 22.
func lifetimeFuncType(ctx llvm.Context, ptrType llvm.Type) llvm.Type {
if Version() >= 22 {
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ptrType}, false)
}
return llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
builder.CreateCall(fnType, fn, []llvm.Value{size, ptr}, "")
}
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
@@ -104,7 +84,7 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := lifetimeFuncType(ctx, ptrType)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -118,7 +98,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
fn := mod.NamedFunction(fnName)
ctx := mod.Context()
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
fnType := lifetimeFuncType(ctx, ptrType)
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
if fn.IsNil() {
fn = llvm.AddFunction(mod, fnName, fnType)
}
@@ -238,36 +218,6 @@ func Version() int {
return major
}
// NoCaptureAttrName returns the name of the LLVM attribute kind that marks a
// pointer parameter as guaranteed not to escape by capture.
//
// LLVM 21 removed the old boolean 'nocapture' enum attribute in favor of the
// more expressive 'captures' int attribute, where 'captures(none)' (encoded
// as the value 0) is the equivalent of the old 'nocapture'. LLVM 20 supports
// both, but its own optimizer still emits 'nocapture', so the cutoff for
// reading/writing the new name is LLVM 21.
func NoCaptureAttrName() string {
if Version() >= 21 {
return "captures"
}
return "nocapture"
}
// IsNoCapture reports whether attr (looked up using the kind returned by
// NoCaptureAttrName) indicates that the pointer it is attached to does not
// escape by capture. It returns false for a nil attribute.
func IsNoCapture(attr llvm.Attribute) bool {
if attr.IsNil() {
return false
}
if Version() >= 21 {
// captures(none) is encoded as the value 0; any other value permits
// some form of capture.
return attr.GetEnumValue() == 0
}
return true
}
// ByteOrder returns the byte order for the given target triple. Most targets are little
// endian, but for example MIPS can be big-endian.
func ByteOrder(target string) binary.ByteOrder {
+34 -23
View File
@@ -7,7 +7,6 @@ import (
"go/token"
"go/types"
"golang.org/x/tools/go/ssa"
"strings"
"tinygo.org/x/go-llvm"
)
@@ -72,20 +71,19 @@ func (b *builder) getRuntimeFunctionValue(name string, sig *types.Signature) llv
// createMapLookup returns the value in a map. It calls a runtime function
// depending on the map key type to load the map value and its comma-ok value.
func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
llvmValueType := b.getLLVMType(valueType)
// Allocate the memory for the resulting type. Do not zero this memory: it
// will be zeroed by the hashmap get implementation if the key is not
// present in the map.
result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap")
mapValueAlloca := result.valuePtr
mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
// We need the map size (with type uintptr) to pass to the hashmap*Get
// functions. This is necessary because those *Get functions are valid on
// nil maps, and they'll need to zero the value pointer by that number of
// bytes.
mapValueSize := result.valueSize
mapValueSize := mapValueAllocaSize
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
}
@@ -95,46 +93,60 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, k
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize}
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
} else {
// Key stored at actual type: either binary-comparable or with
// compiler-generated hash/equal.
mapKey := b.getValueStorage(key, "hashmap.key")
params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize}
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, mapKeyAlloca)
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
fnName := "hashmapBinaryGet"
if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericGet"
}
commaOkValue = b.createRuntimeCall(fnName, params, "")
b.endValueStorage(mapKey)
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
}
// The value is set to the zero value if the key doesn't exist.
return result.finish(b, commaOkValue, ""), nil
// Load the resulting value from the hashmap. The value is set to the zero
// value if the key doesn't exist in the hashmap.
mapValue := b.CreateLoad(llvmValueType, mapValueAlloca, "")
b.emitLifetimeEnd(mapValueAlloca, mapValueAllocaSize)
if commaOk {
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
return tuple, nil
} else {
return mapValue, nil
}
}
// createMapUpdate updates a map key to a given value, by creating an
// appropriate runtime call.
func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) {
storedValue := b.getValueStorage(value, "hashmap.value")
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
valueAlloca, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
b.CreateStore(value, valueAlloca)
keyType = keyType.Underlying()
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// key is a string
params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr}
params := []llvm.Value{m, key, valueAlloca}
b.createRuntimeInvoke("hashmapStringSet", params, "")
} else {
// Key stored at actual type.
keyStorage := b.getValueStorage(key, "hashmap.key")
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
b.CreateStore(key, keyAlloca)
fnName := "hashmapBinarySet"
if !hashmapIsBinaryKey(keyType) {
fnName = "hashmapGenericSet"
}
params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr}
params := []llvm.Value{m, keyAlloca, valueAlloca}
b.createRuntimeInvoke(fnName, params, "")
b.endValueStorage(keyStorage)
b.emitLifetimeEnd(keyAlloca, keySize)
}
b.endValueStorage(storedValue)
b.emitLifetimeEnd(valueAlloca, valueSize)
}
// createMapDelete deletes a key from a map by calling the appropriate runtime
@@ -281,15 +293,14 @@ func hashmapCanonicalTypeName(t types.Type) string {
}
return t.String()
case *types.Struct:
var s strings.Builder
s.WriteString("struct{")
s := "struct{"
for i := 0; i < t.NumFields(); i++ {
if i > 0 {
s.WriteString("; ")
s += "; "
}
s.WriteString(hashmapCanonicalTypeName(t.Field(i).Type()))
s += hashmapCanonicalTypeName(t.Field(i).Type())
}
return s.String() + "}"
return s + "}"
case *types.Array:
return fmt.Sprintf("[%d]%s", t.Len(), hashmapCanonicalTypeName(t.Elem()))
}
+2 -1
View File
@@ -29,7 +29,8 @@ func (s *stdSizes) Alignof(T types.Type) int64 {
// is the largest of the values unsafe.Alignof(x.f) for each
// field f of x, but at least 1."
max := int64(1)
for f := range t.Fields() {
for i := 0; i < t.NumFields(); i++ {
f := t.Field(i)
if a := s.Alignof(f.Type()); a > max {
max = a
}
+56 -140
View File
@@ -8,7 +8,6 @@ import (
"go/ast"
"go/token"
"go/types"
"slices"
"strconv"
"strings"
@@ -79,27 +78,24 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
return llvmFn.GlobalValueType(), llvmFn
}
retType, indirectResult := c.hasIndirectResult(fn.Signature)
if info.exported {
indirectResult = false
var retType llvm.Type
if fn.Signature.Results() == nil {
retType = c.ctx.VoidType()
} else if fn.Signature.Results().Len() == 1 {
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()))
}
retType = c.ctx.StructType(results, false)
}
var paramInfos []paramInfo
if indirectResult {
paramInfos = append(paramInfos, paramInfo{
llvmType: c.dataPtrType,
name: "return",
elemSize: c.targetData.TypeAllocSize(retType),
})
retType = c.ctx.VoidType()
}
for _, param := range getParams(fn.Signature) {
paramType := c.getLLVMType(param.Type())
if info.exported {
paramInfos = append(paramInfos, c.expandDirectFormalParamType(paramType, param.Name(), param.Type())...)
} else {
paramInfos = append(paramInfos, c.expandFormalParamType(paramType, param.Name(), param.Type())...)
}
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
paramInfos = append(paramInfos, paramFragmentInfos...)
}
// Add an extra parameter as the function context. This context is used in
@@ -109,20 +105,12 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
}
var paramTypes []llvm.Type
hasIndirectABI := indirectResult
for _, info := range paramInfos {
paramTypes = append(paramTypes, info.llvmType)
hasIndirectABI = hasIndirectABI || info.flags&paramIsIndirect != 0
}
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
if hasIndirectABI {
// Argument promotion only rewrites functions whose uses are all direct
// calls. Keep an address use so LLVM cannot reconstruct the large
// aggregate signature that this ABI exists to avoid.
llvmutil.AppendToGlobal(c.mod, "llvm.used", llvmFn)
}
if strings.HasPrefix(c.Triple, "wasm") {
// C functions without prototypes like this:
// void foo();
@@ -152,7 +140,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// not.
// (It may be safe to add the nocapture parameter to the context
// parameter, but I'd like to stay on the safe side here).
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0)
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
llvmFn.AddAttributeAtIndex(i+1, nocapture)
}
if paramInfo.flags&paramIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
@@ -171,9 +159,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
// Mark it as noreturn so LLVM can optimize away code.
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
case "internal/abi.NoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
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.
@@ -195,44 +183,44 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
case "runtime.sliceAppend":
// Appending a slice will only read the to-be-appended slice, it won't
// be modified.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromBytes":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.stringFromRunes":
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "runtime.hashmapSet":
// The key (param 2) and value (param 3) pointers are only read via
// memcpy/hash/equal and are never captured. The indirect calls
// through m.keyHash and m.keyEqual function pointers prevent LLVM's
// functionattrs pass from inferring this automatically.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.hashmapGet":
// The key (param 2) is read-only and never captured.
// The value (param 3) is written to (receives the result) but never captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.hashmapDelete":
// The key (param 2) is read-only and never captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.hashmapGenericSet":
// Same as hashmapBinarySet: key (param 2) and value (param 3) are
// not captured.
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.hashmapGenericGet":
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.hashmapGenericDelete":
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
case "runtime.trackPointer":
// This function is necessary for tracking pointers on the stack in a
// portable way (see gc_stack_portable.go). Indicate to the optimizer
// that the only thing we'll do is read the pointer.
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
case "__mulsi3", "__divmodsi4", "__udivmodsi4":
if strings.Split(c.Triple, "-")[0] == "avr" {
@@ -267,7 +255,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName))
}
nocaptureKind := llvm.AttributeKindID(llvmutil.NoCaptureAttrName())
nocaptureKind := llvm.AttributeKindID("nocapture")
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
for i, typ := range paramTypes {
if typ.TypeKind() == llvm.PointerTypeKind {
@@ -331,7 +319,7 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
}
info := functionInfo{
// Pick the default linkName.
linkName: c.canonicalFunctionName(f),
linkName: f.RelString(nil),
}
// Check for a few runtime functions that are treated specially.
@@ -358,18 +346,6 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
return info
}
func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string {
typeArgs := f.TypeArgs()
if len(typeArgs) == 0 {
return f.RelString(nil)
}
parts := make([]string, len(typeArgs))
for i, ta := range typeArgs {
parts[i], _ = c.getTypeCodeName(ta)
}
return f.Origin().RelString(nil) + "[" + 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) {
@@ -396,51 +372,6 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
}
}
// Also scan file-level //go:linkname directives. These appear as
// free-standing comments in *ast.File.Comments (not attached to any
// declaration), and are used by modern golang.org/x/sys/unix and others.
// Function-attached directives (above) take precedence — we only add
// file-level ones if no doc-comment linkname was found for this function.
//
// TODO: the hasUnsafeImport gate enforced downstream (see the
// //go:linkname case below) is package-level. gc enforces it per
// file, on the file containing the directive. For file-level
// linknames this is more important than for function-attached ones,
// because the directive can live in a file separate from the
// function. A stricter implementation would check whether the file
// returned by fileForFunc imports "unsafe", not whether any file in
// the package does.
hasFunctionLinkname := false
for _, comment := range pragmas {
if strings.HasPrefix(comment.Text, "//go:linkname ") {
parts := strings.Fields(comment.Text)
if len(parts) == 3 && parts[1] == f.Name() {
hasFunctionLinkname = true
break
}
}
}
if !hasFunctionLinkname {
if file := c.fileForFunc(f); file != nil {
for _, group := range file.Comments {
// Skip the function's own doc comment — already handled above.
if decl, ok := syntax.(*ast.FuncDecl); ok && group == decl.Doc {
continue
}
for _, comment := range group.List {
if !strings.HasPrefix(comment.Text, "//go:linkname ") {
continue
}
parts := strings.Fields(comment.Text)
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
pragmas = append(pragmas, comment)
}
}
}
}
// Parse each pragma.
for _, comment := range pragmas {
parts := strings.Fields(comment.Text)
@@ -458,7 +389,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.wasmName = info.linkName
info.exported = true
case "//go:interrupt":
if slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.interrupt = true
}
case "//go:wasm-module":
@@ -512,7 +443,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
info.inline = inlineHint
case "//go:noinline":
info.inline = inlineNone
case "//go:linkname", "//go:linknamestd":
case "//go:linkname":
if len(parts) != 3 || parts[1] != f.Name() {
continue
}
@@ -520,14 +451,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 slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.linkName = parts[2]
}
case "//go:section":
// Only enable go:section when the package imports "unsafe".
// go:section also implies go:noinline since inlining could
// move the code to a different section than that requested.
if len(parts) == 2 && slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
info.section = parts[1]
info.inline = inlineNone
}
@@ -536,7 +467,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 slices.Contains(f.Pkg.Pkg.Imports(), types.Unsafe) {
if hasUnsafeImport(f.Pkg.Pkg) {
info.nobounds = true
}
case "//go:noescape":
@@ -636,8 +567,8 @@ func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
hasHostLayout = false // package structs added in go1.23
}
}
for field := range typ.Fields() {
ftyp := field.Type()
for i := 0; i < typ.NumFields(); i++ {
ftyp := typ.Field(i).Type()
if types.Unalias(ftyp).String() == "structs.HostLayout" {
hasHostLayout = true
continue
@@ -669,8 +600,8 @@ func getParams(sig *types.Signature) []*types.Var {
if sig.Recv() != nil {
params = append(params, sig.Recv())
}
for v := range sig.Params().Variables() {
params = append(params, v)
for i := 0; i < sig.Params().Len(); i++ {
params = append(params, sig.Params().At(i))
}
return params
}
@@ -735,34 +666,6 @@ 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) {
@@ -801,7 +704,10 @@ 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 := max(info.align, c.targetData.ABITypeAlignment(llvmType))
alignment := c.targetData.ABITypeAlignment(llvmType)
if info.align > alignment {
alignment = info.align
}
if alignment <= 0 || alignment&(alignment-1) != 0 {
// Check for power-of-two (or 0).
// See: https://stackoverflow.com/a/108360
@@ -875,7 +781,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 slices.Contains(g.Pkg.Pkg.Imports(), types.Unsafe) {
if hasUnsafeImport(g.Pkg.Pkg) {
info.linkName = parts[2]
}
}
@@ -891,3 +797,13 @@ 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
}
+27 -32
View File
@@ -26,25 +26,24 @@ 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}"
var constraints strings.Builder
constraints.WriteString("={rax},0")
constraints := "={rax},0"
for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{
constraints += "," + [...]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.WriteString(",~{rcx},~{r11}")
constraints += ",~{rcx},~{r11}"
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "syscall", constraints.String(), true, false, llvm.InlineAsmDialectIntel, false)
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "386" && b.GOOS == "linux":
@@ -56,23 +55,22 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{b.uintptrType}
// Constraints will look something like:
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
var constraints strings.Builder
constraints.WriteString("={eax},0")
constraints := "={eax},0"
for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{
constraints += "," + [...]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.String(), true, false, llvm.InlineAsmDialectIntel, false)
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm" && b.GOOS == "linux":
@@ -90,10 +88,9 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
argTypes := []llvm.Type{}
// Constraints will look something like:
// ={r0},0,{r1},{r2},{r7},~{r3}
var constraints strings.Builder
constraints.WriteString("={r0}")
constraints := "={r0}"
for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{
constraints += "," + [...]string{
"0", // tie to output
"{r1}",
"{r2}",
@@ -101,20 +98,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.WriteString(",{r7}") // syscall number
constraints += ",{r7}" // syscall number
for i := len(call.Args) - 1; i < 4; i++ {
// r0-r3 get clobbered after the syscall returns
constraints.WriteString(",~{r" + strconv.Itoa(i) + "}")
constraints += ",~{r" + strconv.Itoa(i) + "}"
}
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case b.GOARCH == "arm64" && b.GOOS == "linux":
@@ -123,32 +120,31 @@ 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}
var constraints strings.Builder
constraints.WriteString("={x0}")
constraints := "={x0}"
for i, arg := range call.Args[1:] {
constraints.WriteString("," + [...]string{
constraints += "," + [...]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.WriteString(",{x8}") // syscall number
constraints += ",{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.WriteString(",~{x" + strconv.Itoa(i) + "}")
constraints += ",~{x" + strconv.Itoa(i) + "}"
}
constraints.WriteString(",~{x16},~{x17}") // scratch registers
constraints += ",~{x16},~{x17}" // scratch registers
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints.String(), true, false, 0, false)
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
return b.CreateCall(fnType, target, args, ""), nil
case (b.GOARCH == "mips" || b.GOARCH == "mipsle") && b.GOOS == "linux":
@@ -167,8 +163,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
// faster and smaller code.
args := []llvm.Value{num}
argTypes := []llvm.Type{b.uintptrType}
var constraints strings.Builder
constraints.WriteString("={$2},={$7},0")
constraints := "={$2},={$7},0"
syscallParams := call.Args[1:]
if len(syscallParams) > 7 {
// There is one syscall that uses 7 parameters: sync_file_range.
@@ -177,7 +172,7 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
syscallParams = syscallParams[:7]
}
for i, arg := range syscallParams {
constraints.WriteString("," + [...]string{
constraints += "," + [...]string{
"{$4}", // arg1
"{$5}", // arg2
"{$6}", // arg3
@@ -185,7 +180,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())
@@ -226,10 +221,10 @@ func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
"addu $$sp, $$sp, 32\n" +
".set at\n"
}
constraints.WriteString(",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}")
constraints += ",~{$3},~{$4},~{$5},~{$6},~{$8},~{$9},~{$10},~{$11},~{$12},~{$13},~{$14},~{$15},~{$24},~{$25},~{hi},~{lo},~{memory}"
returnType := b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false)
fnType := llvm.FunctionType(returnType, argTypes, false)
target := llvm.InlineAsm(fnType, asm, constraints.String(), true, true, 0, false)
target := llvm.InlineAsm(fnType, asm, constraints, true, true, 0, false)
call := b.CreateCall(fnType, target, args, "")
resultCode := b.CreateExtractValue(call, 0, "") // r2
errorFlag := b.CreateExtractValue(call, 1, "") // r7
-5
View File
@@ -66,11 +66,6 @@ func complexSub(x, y complex64) complex64 {
return x - y
}
func shiftNested(x uint64) uint64 {
k := 3
return x >> (1 << k) // https://github.com/tinygo-org/tinygo/issues/5496
}
func complexMul(x, y complex64) complex64 {
return x * y
}
-7
View File
@@ -176,13 +176,6 @@ entry:
ret { float, float } %3
}
; Function Attrs: nounwind
define hidden i64 @main.shiftNested(i64 %x, ptr %context) unnamed_addr #1 {
entry:
%0 = lshr i64 %x, 8
ret i64 %0
}
; Function Attrs: nounwind
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, ptr %context) unnamed_addr #1 {
entry:
+2 -2
View File
@@ -278,7 +278,7 @@ entry:
for.body: ; preds = %for.body, %entry
%defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack1, align 4
@@ -330,7 +330,7 @@ for.loop: ; preds = %for.body, %entry
for.body: ; preds = %for.loop
%defer.next = load ptr, ptr %deferPtr, align 4
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), ptr undef) #4
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
store i32 0, ptr %defer.alloc.call, align 4
%defer.alloc.call.repack13 = getelementptr inbounds nuw i8, ptr %defer.alloc.call, i32 4
store ptr %defer.next, ptr %defer.alloc.call.repack13, align 4
+1 -1
View File
@@ -131,7 +131,7 @@ entry:
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #3
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #3
store double %v.r, ptr %0, align 8
%.repack1 = getelementptr inbounds nuw i8, ptr %0, i32 8
-37
View File
@@ -19,49 +19,12 @@ func Add[T Coord](a, b Point[T]) Point[T] {
}
}
func aliasSize[F float32 | float64]() uintptr {
return unsafe.Sizeof(F(0))
}
func aliasSize32() uintptr {
type F = float32
return aliasSize[F]()
}
func aliasSize64() uintptr {
type F = float64
return aliasSize[F]()
}
type aliasMethodResult[F float32 | float64] struct {
Value F
}
type aliasMethodValue[F float32 | float64] struct{}
func (aliasMethodValue[F]) Get() aliasMethodResult[F] {
return aliasMethodResult[F]{}
}
func main() {
var af, bf Point[float32]
Add(af, bf)
var ai, bi Point[int]
Add(ai, bi)
checkSize(aliasSize32())
checkSize(aliasSize64())
}
func checkSize(uintptr)
func checkBool(bool)
func aliasMethod32(x any) {
type F = float32
_, ok := x.(interface {
Get() aliasMethodResult[F]
})
checkBool(ok)
}
+21 -70
View File
@@ -14,62 +14,32 @@ entry:
ret void
}
; Function Attrs: nounwind
define hidden i32 @main.aliasSize32(ptr %context) unnamed_addr #1 {
entry:
%0 = call i32 @"main.aliasSize[basic:float32]"(ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[basic:float32]"(ptr %context) unnamed_addr #1 {
entry:
ret i32 4
}
; Function Attrs: nounwind
define hidden i32 @main.aliasSize64(ptr %context) unnamed_addr #1 {
entry:
%0 = call i32 @"main.aliasSize[basic:float64]"(ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"main.aliasSize[basic:float64]"(ptr %context) unnamed_addr #1 {
entry:
ret i32 8
}
; Function Attrs: nounwind
define hidden void @main.main(ptr %context) unnamed_addr #1 {
entry:
%0 = call %"main.Point[float32]" @"main.Add[basic:float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
%1 = call %"main.Point[int]" @"main.Add[basic:int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
%2 = call i32 @main.aliasSize32(ptr undef)
call void @main.checkSize(i32 %2, ptr undef) #4
%3 = call i32 @main.aliasSize64(ptr undef)
call void @main.checkSize(i32 %3, ptr undef) #4
%0 = call %"main.Point[float32]" @"main.Add[float32]"(float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, float 0.000000e+00, ptr undef)
%1 = call %"main.Point[int]" @"main.Add[int]"(i32 0, i32 0, i32 0, i32 0, ptr undef)
ret void
}
; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[basic:float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
define linkonce_odr hidden %"main.Point[float32]" @"main.Add[float32]"(float %a.X, float %a.Y, float %b.X, float %b.Y, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
%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.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) #4
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #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.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) #4
call void @main.checkSize(i32 8, ptr undef) #4
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #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
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3
br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry
@@ -116,23 +86,23 @@ declare void @main.checkSize(i32, ptr) #0
declare void @runtime.nilPanic(ptr) #0
; Function Attrs: nounwind
define linkonce_odr hidden %"main.Point[int]" @"main.Add[basic:int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
define linkonce_odr hidden %"main.Point[int]" @"main.Add[int]"(i32 %a.X, i32 %a.Y, i32 %b.X, i32 %b.Y, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%a = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #4
%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.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) #4
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #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.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) #4
call void @main.checkSize(i32 8, ptr undef) #4
%complit = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #4
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #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
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #3
br i1 false, label %deref.throw, label %deref.next
deref.next: ; preds = %entry
@@ -171,26 +141,7 @@ deref.throw: ; preds = %store.next, %deref.
unreachable
}
declare void @main.checkBool(i1, ptr) #0
; Function Attrs: nounwind
define hidden void @main.aliasMethod32(ptr %x.typecode, ptr %x.value, ptr %context) unnamed_addr #1 {
entry:
%0 = call i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr %x.typecode) #4
br i1 %0, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
call void @main.checkBool(i1 %0, ptr undef) #4
ret void
typeassert.ok: ; preds = %entry
br label %typeassert.next
}
declare i1 @"interface:{Get:func:{}{named:main.aliasMethodResult[basic:float32]}}.$typeassert"(ptr) #3
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 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Get:func:{}{named:main.aliasMethodResult[basic:float32]}" }
attributes #4 = { nounwind }
attributes #3 = { nounwind }
-24
View File
@@ -1,24 +0,0 @@
package main
// genericMethod has both a regular method and a "generic method": a method
// with its own type parameter, independent of any type parameter on the
// receiver. This is a Go 1.27 feature (see math/rand/v2.Rand.N for a
// real-world example). Boxing such a value into an interface must not try to
// encode the generic method's type-parameterized signature into a type code.
type genericMethod struct{}
func (t genericMethod) Regular(n int) int { return n }
func (t genericMethod) GenericParam[X int | int64](n X) X { return n }
// onlyGenericMethod's only method is generic, so its runtime type must have
// an empty method set (hasMethodSet must become false, not just numMethods).
type onlyGenericMethod struct{}
func (t onlyGenericMethod) GenericParam[X int | int64](n X) X { return n }
func useGenericMethods() (any, any) {
var g genericMethod
var o onlyGenericMethod
return any(g), any(o)
}
-67
View File
@@ -1,67 +0,0 @@
; ModuleID = 'go1.27.go'
source_filename = "go1.27.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime.structField = type { ptr, ptr }
%runtime._interface = type { ptr, ptr }
@"reflect/types.signature:Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
@"reflect/types.type:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] } { ptr @"named:main.genericMethod$methodset", i8 122, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, { i32, [1 x ptr] } }, ptr @"reflect/types.type:pointer:named:main.genericMethod", i32 0, i32 1), ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] }, [19 x i8] c"main.genericMethod\00" }, align 4
@"reflect/types.type.pkgpath:main" = linkonce_odr unnamed_addr constant [5 x i8] c"main\00", align 1
@"reflect/types.type:pointer:named:main.genericMethod" = linkonce_odr constant { ptr, i8, i16, ptr, { i32, [1 x ptr] } } { ptr @"pointer:named:main.genericMethod$methodset", i8 -43, i16 -32767, ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Regular:func:{basic:int}{basic:int}"] } }, align 4
@"reflect/methods.Regular:func:{basic:int}{basic:int}" = linkonce_odr constant i8 0, align 1
@"main$string" = internal unnamed_addr constant [18 x i8] c"main.genericMethod", align 1
@"main$string.1" = internal unnamed_addr constant [7 x i8] c"Regular", align 1
@"pointer:named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(*main.genericMethod).Regular" } }
@"reflect/types.type:struct:{}" = linkonce_odr constant { i8, i16, ptr, ptr, i32, i16, [0 x %runtime.structField] } { i8 90, i16 0, ptr @"reflect/types.type:pointer:struct:{}", ptr @"reflect/types.type.pkgpath.empty", i32 0, i16 0, [0 x %runtime.structField] zeroinitializer }, align 4
@"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1
@"reflect/types.type:pointer:struct:{}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:struct:{}" }, align 4
@"named:main.genericMethod$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Regular:func:{basic:int}{basic:int}"], { ptr } { ptr @"(main.genericMethod).Regular$invoke" } }
@"reflect/types.type:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [23 x i8] } { i8 122, i16 0, ptr @"reflect/types.type:pointer:named:main.onlyGenericMethod", ptr @"reflect/types.type:struct:{}", ptr @"reflect/types.type.pkgpath:main", [23 x i8] c"main.onlyGenericMethod\00" }, align 4
@"reflect/types.type:pointer:named:main.onlyGenericMethod" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:main.onlyGenericMethod" }, align 4
declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden i32 @"(main.genericMethod).Regular"(i32 %n, ptr %context) unnamed_addr #1 {
entry:
ret i32 %n
}
; Function Attrs: nounwind
define hidden { %runtime._interface, %runtime._interface } @main.useGenericMethods(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull getelementptr inbounds nuw (i8, ptr @"reflect/types.type:named:main.genericMethod", i32 4), ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:named:main.onlyGenericMethod", ptr nonnull %stackalloc, ptr undef) #2
call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #2
ret { %runtime._interface, %runtime._interface } { %runtime._interface { ptr getelementptr ({ ptr, i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [19 x i8] }, ptr @"reflect/types.type:named:main.genericMethod", i32 0, i32 1), ptr null }, %runtime._interface { ptr @"reflect/types.type:named:main.onlyGenericMethod", ptr null } }
}
; Function Attrs: nounwind
define linkonce_odr hidden i32 @"(*main.genericMethod).Regular"(ptr %t, i32 %n, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr %t, ptr nonnull %stackalloc, ptr undef) #2
%0 = call i32 @"(main.genericMethod).Regular"(i32 %n, ptr undef)
ret i32 %0
}
; Function Attrs: nounwind
define linkonce_odr i32 @"(main.genericMethod).Regular$invoke"(ptr %0, i32 %1, ptr %2) unnamed_addr #1 {
entry:
%ret = call i32 @"(main.genericMethod).Regular"(i32 %1, ptr %2)
ret i32 %ret
}
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 }
+4 -4
View File
@@ -60,7 +60,7 @@ define hidden void @main.closureFunctionGoroutine(ptr %context) unnamed_addr #0
entry:
%n = call align 4 dereferenceable(4) ptr @runtime.alloc(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #11
store i32 3, ptr %n, align 4
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr %n, ptr %1, align 4
@@ -102,7 +102,7 @@ declare void @runtime.printunlock(ptr) #1
; Function Attrs: nounwind
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #0 {
entry:
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr %fn.context, ptr %1, align 4
@@ -157,7 +157,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #1
; Function Attrs: nounwind
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #0 {
entry:
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
store ptr @"main$string", ptr %1, align 4
@@ -195,6 +195,6 @@ attributes #5 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb
attributes #6 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper" }
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
attributes #9 = { "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
attributes #10 = { nounwind "target-features"="+armv7-m,+hwdiv,+soft-float,+thumb-mode,-aes,-bf16,-cdecp0,-cdecp1,-cdecp2,-cdecp3,-cdecp4,-cdecp5,-cdecp6,-cdecp7,-crc,-crypto,-d32,-dotprod,-dsp,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-hwdiv-arm,-i8mm,-lob,-mve,-mve.fp,-neon,-pacbti,-ras,-sb,-sha2,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
attributes #11 = { nounwind }
+4 -4
View File
@@ -66,7 +66,7 @@ entry:
store i32 3, ptr %n, align 4
call void @runtime.trackPointer(ptr nonnull %n, ptr nonnull %stackalloc, ptr undef) #11
call void @runtime.trackPointer(ptr nonnull @"main.closureFunctionGoroutine$1", ptr nonnull %stackalloc, ptr undef) #11
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr null, ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -110,7 +110,7 @@ declare void @runtime.printunlock(ptr) #0
define hidden void @main.funcGoroutine(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store i32 5, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -167,7 +167,7 @@ declare void @runtime.chanClose(ptr dereferenceable_or_null(36), ptr) #0
define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), ptr undef) #11
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr null, ptr undef) #11
call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #11
store ptr %itf.value, ptr %0, align 4
%1 = getelementptr inbounds nuw i8, ptr %0, i32 4
@@ -206,6 +206,6 @@ attributes #5 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+cal
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper" }
attributes #7 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #8 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic:string}{}" }
attributes #9 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Print(string)" "tinygo-methods"="reflect/methods.Print(string)" }
attributes #10 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="interface:{Print:func:{basic:string}{}}.Print$invoke" }
attributes #11 = { nounwind }
+4 -4
View File
@@ -131,8 +131,8 @@ declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"
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 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}" }
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo:func:{basic:int}{basic:uint8}" "tinygo-methods"="reflect/methods.String:func:{}{basic:string}; main.$methods.foo:func:{basic:int}{basic:uint8}" }
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error:func:{}{basic:string}" "tinygo-methods"="reflect/methods.Error:func:{}{basic:string}" }
attributes #2 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.Error() string" }
attributes #3 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-methods"="reflect/methods.String() string" }
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.foo(int) uint8" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) uint8" }
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" }
attributes #6 = { nounwind }
-20
View File
@@ -1,20 +0,0 @@
package main
type largeOptimizedValue [1025]byte
type mixedLargeOptimizedValue struct {
value [1025]byte
any any
}
func makeLargeOptimizedValue() largeOptimizedValue {
return largeOptimizedValue{}
}
func readLargeOptimizedValue(value largeOptimizedValue) byte {
return value[len(value)-1]
}
func readMixedLargeOptimizedValue(value mixedLargeOptimizedValue) byte {
return value.value[len(value.value)-1]
}
-120
View File
@@ -1,120 +0,0 @@
package main
type largeValue [1025]byte
type largeStruct struct {
value [1025]byte
ptr *byte
}
type largeInterface interface {
makeLargeValue() largeValue
readLargeValue(largeValue) byte
}
type largeReceiver largeValue
func makeLargeValue(value byte) largeValue {
var result largeValue
result[len(result)-1] = value
return result
}
func makeZeroLargeValue() largeValue {
return largeValue{}
}
func passZeroLargeValue() byte {
return readLargeValue(largeValue{})
}
func readLargeValue(value largeValue) byte {
return value[len(value)-1]
}
func useLargeValue() byte {
return readLargeValue(makeLargeValue(42))
}
func useLargeFunctionValue(fn func(largeValue) byte) byte {
return fn(makeLargeValue(42))
}
func (receiver largeReceiver) makeLargeValue() largeValue {
return largeValue(receiver)
}
func (receiver largeReceiver) readLargeValue(value largeValue) byte {
return value[len(value)-1]
}
func useLargeInterface(value largeInterface) byte {
return value.readLargeValue(value.makeLargeValue())
}
func deferLargeValue(value largeValue) {
defer readLargeValue(value)
}
func goLargeValue(value largeValue) {
go readLargeValue(value)
}
func makeLargeResults(value byte) (largeValue, byte) {
return makeLargeValue(value), value
}
func makeTwoLargeResults(value byte) (largeValue, largeValue) {
return makeLargeValue(value), makeLargeValue(value + 1)
}
func makeMixedLargeResults(value byte) (largeValue, byte, largeValue) {
return makeLargeValue(value), value + 1, makeLargeValue(value + 2)
}
func chooseLargeValue(flag bool) largeValue {
value := makeLargeValue(1)
if flag {
value = makeLargeValue(42)
}
return value
}
func makePointerLargeValue(value *byte) largeStruct {
return largeStruct{ptr: value}
}
func assertLargeValue(value any) byte {
large, ok := value.(largeValue)
if !ok {
return 0
}
return large[len(large)-1]
}
func useLargeMap(key, value largeValue) byte {
values := map[largeValue]largeValue{key: value}
result, ok := values[key]
if !ok {
return 0
}
return result[len(result)-1]
}
func useLargeChannel(ch chan largeValue, value largeValue) byte {
ch <- value
result, ok := <-ch
if !ok {
return 0
}
return result[len(result)-1]
}
func selectLargeChannel(ch chan largeValue, value largeValue) byte {
select {
case ch <- value:
return 0
case result := <-ch:
return result[len(result)-1]
}
}
-498
View File
@@ -1,498 +0,0 @@
; ModuleID = 'large.go'
source_filename = "large.go"
target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20"
target triple = "wasm32-unknown-wasi"
%runtime._string = type { ptr, i32 }
%runtime.channelOp = type { ptr, ptr, i32, ptr }
%runtime.chanSelectState = type { ptr, ptr }
@"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002" = linkonce_odr unnamed_addr constant { i32, [33 x i8] } { i32 258, [33 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\02" }
@"reflect/types.typeid:named:main.largeValue" = external constant i8
@llvm.used = appending global [15 x ptr] [ptr @"(main.largeReceiver).makeLargeValue", ptr @"(main.largeReceiver).readLargeValue", ptr @main.makeLargeValue, ptr @main.makeZeroLargeValue, ptr @main.readLargeValue, ptr @main.deferLargeValue, ptr @main.goLargeValue, ptr @main.makeLargeResults, ptr @main.makeTwoLargeResults, ptr @main.makeMixedLargeResults, ptr @main.chooseLargeValue, ptr @main.makePointerLargeValue, ptr @main.useLargeMap, ptr @main.useLargeChannel, ptr @main.selectLargeChannel]
@"main$string" = internal unnamed_addr constant [31 x i8] c"blocking select matched no case", align 1
@"main$pack" = internal unnamed_addr constant { %runtime._string } { %runtime._string { ptr @"main$string", i32 31 } }
@"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4
@"reflect/types.type:pointer:basic:string" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:string" }, align 4
declare void @runtime.trackPointer(ptr readonly captures(none), ptr, ptr) #0
; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 {
entry:
ret void
}
; Function Attrs: nounwind
define hidden void @"(main.largeReceiver).makeLargeValue"(ptr dereferenceable_or_null(1025) %return, ptr readonly dereferenceable_or_null(1025) %receiver, ptr %context) unnamed_addr #1 {
entry:
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %receiver, i32 1025, i1 false)
ret void
}
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite)
declare void @llvm.memcpy.p0.p0.i32(ptr noalias writeonly captures(none), ptr noalias readonly captures(none), i32, i1 immarg) #2
; Function Attrs: nounwind
define hidden i8 @"(main.largeReceiver).readLargeValue"(ptr readonly dereferenceable_or_null(1025) %receiver, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
%1 = load i8, ptr %0, align 1
ret i8 %1
}
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #3
; Function Attrs: nounwind
define hidden void @main.makeLargeValue(ptr dereferenceable_or_null(1025) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%0 = getelementptr inbounds nuw i8, ptr %result, i32 1024
store i8 %value, ptr %0, align 1
%1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeZeroLargeValue(ptr dereferenceable_or_null(1025) %return, ptr %context) unnamed_addr #1 {
entry:
store [1025 x i8] zeroinitializer, ptr %return, align 1
ret void
}
; Function Attrs: nounwind
define hidden i8 @main.passZeroLargeValue(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%"main.largeValue{}:main.largeValue" = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %"main.largeValue{}:main.largeValue", ptr nonnull %stackalloc, ptr undef) #9
store [1025 x i8] zeroinitializer, ptr %"main.largeValue{}:main.largeValue", align 1
%0 = call i8 @main.readLargeValue(ptr nonnull %"main.largeValue{}:main.largeValue", ptr undef)
ret i8 %0
}
; Function Attrs: nounwind
define hidden i8 @main.readLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%value1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %value1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %value1, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %value1, i32 1024
%1 = load i8, ptr %0, align 1
ret i8 %1
}
; Function Attrs: nounwind
define hidden i8 @main.useLargeValue(ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
%0 = call i8 @main.readLargeValue(ptr nonnull %call.result, ptr undef)
ret i8 %0
}
; Function Attrs: nounwind
define hidden i8 @main.useLargeFunctionValue(ptr %fn.context, ptr %fn.funcptr, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 42, ptr undef)
%0 = icmp eq ptr %fn.funcptr, null
br i1 %0, label %fpcall.throw, label %fpcall.next
fpcall.next: ; preds = %entry
%1 = call i8 %fn.funcptr(ptr nonnull %call.result, ptr %fn.context) #9
ret i8 %1
fpcall.throw: ; preds = %entry
call void @runtime.nilPanic(ptr undef) #9
unreachable
}
declare void @runtime.nilPanic(ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.useLargeInterface(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr nonnull %call.result, ptr %value.value, ptr %value.typecode, ptr undef) #9
%0 = call i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr %value.value, ptr nonnull %call.result, ptr %value.typecode, ptr undef) #9
ret i8 %0
}
declare void @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.makeLargeValue$invoke"(ptr, ptr, ptr, ptr) #4
declare i8 @"interface:{main.makeLargeValue:func:{}{named:main.largeValue},main.readLargeValue:func:{named:main.largeValue}{basic:uint8}}.readLargeValue$invoke"(ptr, ptr, ptr, ptr) #5
; Function Attrs: nounwind
define hidden void @main.deferLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%defer.alloca = alloca { i32, ptr, ptr }, align 8
%deferPtr = alloca ptr, align 4
store ptr null, ptr %deferPtr, align 4
%stackalloc = alloca i8, align 1
call void @runtime.trackPointer(ptr nonnull %defer.alloca, ptr nonnull %stackalloc, ptr undef) #9
store i32 0, ptr %defer.alloca, align 4
%defer.alloca.repack1 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 4
store ptr null, ptr %defer.alloca.repack1, align 4
%defer.alloca.repack3 = getelementptr inbounds nuw i8, ptr %defer.alloca, i32 8
store ptr %value, ptr %defer.alloca.repack3, align 4
store ptr %defer.alloca, ptr %deferPtr, align 4
br label %rundefers.block
rundefers.after: ; preds = %rundefers.end
ret void
rundefers.block: ; preds = %entry
br label %rundefers.loophead
rundefers.loophead: ; preds = %rundefers.callback0, %rundefers.block
%0 = load ptr, ptr %deferPtr, align 4
%stackIsNil = icmp eq ptr %0, null
br i1 %stackIsNil, label %rundefers.end, label %rundefers.loop
rundefers.loop: ; preds = %rundefers.loophead
%stack.next.gep = getelementptr inbounds nuw i8, ptr %0, i32 4
%stack.next = load ptr, ptr %stack.next.gep, align 4
store ptr %stack.next, ptr %deferPtr, align 4
%callback = load i32, ptr %0, align 4
switch i32 %callback, label %rundefers.default [
i32 0, label %rundefers.callback0
]
rundefers.callback0: ; preds = %rundefers.loop
%gep = getelementptr inbounds nuw i8, ptr %0, i32 8
%param = load ptr, ptr %gep, align 4
%1 = call i8 @main.readLargeValue(ptr %param, ptr undef)
br label %rundefers.loophead
rundefers.default: ; preds = %rundefers.loop
unreachable
rundefers.end: ; preds = %rundefers.loophead
br label %rundefers.after
recover: ; No predecessors!
ret void
}
; Function Attrs: nounwind
define hidden void @main.goLargeValue(ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%go.param = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %go.param, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %go.param, ptr noundef nonnull align 1 dereferenceable(1025) %value, i32 1025, i1 false)
call void @"internal/task.start"(i32 ptrtoint (ptr @"main.readLargeValue$gowrapper" to i32), ptr nonnull %go.param, i32 65536, ptr undef) #9
ret void
}
declare void @runtime.deadlock(ptr) #0
; Function Attrs: nounwind
define linkonce_odr void @"main.readLargeValue$gowrapper"(ptr %0) unnamed_addr #6 {
entry:
%1 = call i8 @main.readLargeValue(ptr %0, ptr undef)
call void @runtime.deadlock(ptr undef) #9
unreachable
}
declare void @"internal/task.start"(i32, ptr, i32, ptr) #0
; Function Attrs: nounwind
define hidden void @main.makeLargeResults(ptr dereferenceable_or_null(1026) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%0 = getelementptr inbounds nuw i8, ptr %return, i32 1025
store i8 %value, ptr %0, align 1
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeTwoLargeResults(ptr dereferenceable_or_null(2050) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
%0 = add i8 %value, 1
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %0, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%1 = getelementptr inbounds nuw i8, ptr %return, i32 1025
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %1, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makeMixedLargeResults(ptr dereferenceable_or_null(2051) %return, i8 %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 %value, ptr undef)
%0 = add i8 %value, 1
%1 = add i8 %value, 2
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 %1, ptr undef)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %call.result, i32 1025, i1 false)
%2 = getelementptr inbounds nuw i8, ptr %return, i32 1025
store i8 %0, ptr %2, align 1
%3 = getelementptr inbounds nuw i8, ptr %return, i32 1026
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %3, ptr noundef nonnull align 1 dereferenceable(1025) %call.result1, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.chooseLargeValue(ptr dereferenceable_or_null(1025) %return, i1 %flag, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%call.result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result, i8 1, ptr undef)
br i1 %flag, label %if.then, label %if.done
if.then: ; preds = %entry
%call.result1 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %call.result1, ptr nonnull %stackalloc, ptr undef) #9
call void @main.makeLargeValue(ptr nonnull %call.result1, i8 42, ptr undef)
br label %if.done
if.done: ; preds = %if.then, %entry
%0 = phi ptr [ %call.result, %entry ], [ %call.result1, %if.then ]
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %return, ptr noundef nonnull align 1 dereferenceable(1025) %0, i32 1025, i1 false)
ret void
}
; Function Attrs: nounwind
define hidden void @main.makePointerLargeValue(ptr dereferenceable_or_null(1032) %return, ptr dereferenceable_or_null(1) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%complit = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %complit, ptr nonnull %stackalloc, ptr undef) #9
br i1 false, label %store.throw, label %store.next
store.next: ; preds = %entry
%0 = getelementptr inbounds nuw i8, ptr %complit, i32 1028
store ptr %value, ptr %0, align 4
%1 = call align 4 dereferenceable(1032) ptr @runtime.alloc(i32 1032, ptr nonnull @"runtime/gc.layout:258-000000000000000000000000000000000000000000000000000000000000000002", ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %1, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 4 dereferenceable(1032) %1, ptr noundef nonnull align 4 dereferenceable(1032) %complit, i32 1032, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1032) %return, ptr noundef nonnull align 4 dereferenceable(1032) %1, i32 1032, i1 false)
ret void
store.throw: ; preds = %entry
unreachable
}
; Function Attrs: nounwind
define hidden i8 @main.assertLargeValue(ptr %value.typecode, ptr %value.value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%large = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %large, ptr nonnull %stackalloc, ptr undef) #9
%typecode = call i1 @runtime.typeAssert(ptr %value.typecode, ptr nonnull @"reflect/types.typeid:named:main.largeValue", ptr undef) #9
%typeassert.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %typeassert.result, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memset.p0.i32(ptr noundef nonnull align 1 dereferenceable(1026) %typeassert.result, i8 0, i32 1026, i1 false)
br i1 %typecode, label %typeassert.ok, label %typeassert.next
typeassert.next: ; preds = %typeassert.ok, %entry
%0 = getelementptr inbounds nuw i8, ptr %typeassert.result, i32 1025
store i1 %typecode, ptr %0, align 1
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %large, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
br i1 %typecode, label %if.done, label %if.then
typeassert.ok: ; preds = %entry
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %typeassert.result, ptr noundef nonnull align 1 dereferenceable(1025) %value.value, i32 1025, i1 false)
br label %typeassert.next
if.done: ; preds = %typeassert.next
%1 = getelementptr inbounds nuw i8, ptr %large, i32 1024
%2 = load i8, ptr %1, align 1
ret i8 %2
if.then: ; preds = %typeassert.next
ret i8 0
}
declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #7
; Function Attrs: nounwind
define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%stackalloc = alloca i8, align 1
%0 = call ptr @runtime.hashmapMakeGeneric(i32 1025, i32 1025, i32 1, ptr null, ptr nonnull @runtime.hash32, ptr null, ptr nonnull @runtime.memequal, ptr undef) #9
call void @runtime.trackPointer(ptr %0, ptr nonnull %stackalloc, ptr undef) #9
call void @runtime.hashmapBinarySet(ptr %0, ptr %key, ptr %value, ptr undef) #9
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%hashmap.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %hashmap.result, ptr nonnull %stackalloc, ptr undef) #9
%1 = call i1 @runtime.hashmapBinaryGet(ptr %0, ptr %key, ptr nonnull %hashmap.result, i32 1025, ptr undef) #9
%2 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
store i1 %1, ptr %2, align 1
%t3 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t3, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t3, ptr noundef nonnull align 1 dereferenceable(1025) %hashmap.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t3, i32 1025, i1 false)
%3 = getelementptr inbounds nuw i8, ptr %hashmap.result, i32 1025
%t4 = load i1, ptr %3, align 1
br i1 %t4, label %if.done, label %if.then
if.done: ; preds = %entry
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%5 = load i8, ptr %4, align 1
ret i8 %5
if.then: ; preds = %entry
ret i8 0
}
declare i32 @runtime.hash32(ptr, i32, i32, ptr) #0
declare i1 @runtime.memequal(ptr, ptr, i32, ptr) #0
declare ptr @runtime.hashmapMakeGeneric(i32, i32, i32, ptr, ptr, ptr, ptr, ptr) #0
declare void @runtime.hashmapBinarySet(ptr dereferenceable_or_null(48), ptr, ptr, ptr) #0
declare i1 @runtime.hashmapBinaryGet(ptr dereferenceable_or_null(48), ptr, ptr, i32, ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.useLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%chan.op1 = alloca %runtime.channelOp, align 8
%chan.op = alloca %runtime.channelOp, align 8
%stackalloc = alloca i8, align 1
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr %value, ptr nonnull %chan.op, ptr undef) #9
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%chan.result = call align 1 dereferenceable(1026) ptr @runtime.alloc(i32 1026, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %chan.result, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.lifetime.start.p0(ptr nonnull %chan.op1)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.result, ptr nonnull %chan.op1, ptr undef) #9
%1 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
store i1 %0, ptr %1, align 1
call void @llvm.lifetime.end.p0(ptr nonnull %chan.op1)
%t2 = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %t2, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %t2, ptr noundef nonnull align 1 dereferenceable(1025) %chan.result, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %t2, i32 1025, i1 false)
%2 = getelementptr inbounds nuw i8, ptr %chan.result, i32 1025
%t3 = load i1, ptr %2, align 1
br i1 %t3, label %if.done, label %if.then
if.done: ; preds = %entry
%3 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%4 = load i8, ptr %3, align 1
ret i8 %4
if.then: ; preds = %entry
ret i8 0
}
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(ptr captures(none)) #8
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(ptr captures(none)) #8
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
; Function Attrs: nounwind
define hidden i8 @main.selectLargeChannel(ptr dereferenceable_or_null(36) %ch, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
entry:
%select.block.alloca = alloca [2 x %runtime.channelOp], align 8
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.recvbuf.alloca = alloca [1025 x i8], align 1
%stackalloc = alloca i8, align 1
call void @llvm.lifetime.start.p0(ptr nonnull %select.recvbuf.alloca)
call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
store ptr %ch, ptr %select.states.alloca, align 4
%select.states.alloca.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
store ptr %value, ptr %select.states.alloca.repack3, align 4
%0 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 8
store ptr %ch, ptr %0, align 4
%.repack5 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
store ptr null, ptr %.repack5, align 4
call void @llvm.lifetime.start.p0(ptr nonnull %select.block.alloca)
%select.result = call { i32, i1 } @runtime.chanSelect(ptr nonnull %select.recvbuf.alloca, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr nonnull %select.block.alloca, i32 2, i32 2, ptr undef) #9
call void @llvm.lifetime.end.p0(ptr nonnull %select.block.alloca)
call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
call void @runtime.trackPointer(ptr nonnull %select.recvbuf.alloca, ptr nonnull %stackalloc, ptr undef) #9
%1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0
br i1 %2, label %select.body, label %select.next
select.body: ; preds = %entry
ret i8 0
select.next: ; preds = %entry
%3 = icmp eq i32 %1, 1
br i1 %3, label %select.body1, label %select.next2
select.body1: ; preds = %select.next
%result = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %result, ptr nonnull %stackalloc, ptr undef) #9
%select.received = call align 1 dereferenceable(1025) ptr @runtime.alloc(i32 1025, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #9
call void @runtime.trackPointer(ptr nonnull %select.received, ptr nonnull %stackalloc, ptr undef) #9
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %select.received, ptr noundef nonnull align 1 dereferenceable(1025) %select.recvbuf.alloca, i32 1025, i1 false)
call void @llvm.memcpy.p0.p0.i32(ptr noundef nonnull align 1 dereferenceable(1025) %result, ptr noundef nonnull align 1 dereferenceable(1025) %select.received, i32 1025, i1 false)
%4 = getelementptr inbounds nuw i8, ptr %result, i32 1024
%5 = load i8, ptr %4, align 1
ret i8 %5
select.next2: ; preds = %select.next
call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull %stackalloc, ptr undef) #9
call void @runtime.trackPointer(ptr nonnull @"main$pack", ptr nonnull %stackalloc, ptr undef) #9
call void @runtime._panic(ptr nonnull @"reflect/types.type:basic:string", ptr nonnull @"main$pack", ptr undef) #9
unreachable
}
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #0
declare void @runtime._panic(ptr, ptr, ptr) #0
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 = { nocallback nofree nounwind willreturn memory(argmem: readwrite) }
attributes #3 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
attributes #4 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-indirect-result"="true" "tinygo-invoke"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
attributes #5 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-invoke"="main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" "tinygo-methods"="main.$methods.makeLargeValue:func:{}{named:main.largeValue}; main.$methods.readLargeValue:func:{named:main.largeValue}{basic:uint8}" }
attributes #6 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "tinygo-gowrapper"="main.readLargeValue" }
attributes #7 = { nocallback nofree nounwind willreturn memory(argmem: write) }
attributes #8 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) }
attributes #9 = { nounwind }
-34
View File
@@ -35,12 +35,6 @@ func withLinkageName1() {
//go:linkname withLinkageName2 somepkg.someFunction2
func withLinkageName2()
// Import a function from a different package using go:linknamestd (the standard
// library variant of go:linkname introduced in Go 1.27).
//
//go:linknamestd withLinkageNameStd somepkg.someFunctionStd
func withLinkageNameStd()
// Function has an 'inline hint', similar to the inline keyword in C.
//
//go:inline
@@ -126,31 +120,3 @@ 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() {
}
+2 -24
View File
@@ -33,8 +33,6 @@ entry:
declare void @somepkg.someFunction2(ptr) #0
declare void @somepkg.someFunctionStd(ptr) #0
; Function Attrs: inlinehint nounwind
define hidden void @main.inlineFunc(ptr %context) unnamed_addr #3 {
entry:
@@ -50,12 +48,12 @@ entry:
; Function Attrs: nounwind
define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 {
entry:
call void @"main.noinlineGenericFunc[basic:int8]"(ptr undef)
call void @"main.noinlineGenericFunc[int8]"(ptr undef)
ret void
}
; Function Attrs: noinline nounwind
define linkonce_odr hidden void @"main.noinlineGenericFunc[basic:int8]"(ptr %context) unnamed_addr #4 {
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #4 {
entry:
ret void
}
@@ -104,26 +102,6 @@ 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" }
+2
View File
@@ -59,6 +59,7 @@ func TestCorpus(t *testing.T) {
}
for _, repo := range repos {
repo := repo
name := repo.Repo
if repo.Tags != "" {
name += "(" + strings.ReplaceAll(repo.Tags, " ", "-") + ")"
@@ -131,6 +132,7 @@ func TestCorpus(t *testing.T) {
}
for _, dir := range repo.Subdirs {
dir := dir
t.Run(dir.Pkg, func(t *testing.T) {
t.Parallel()
+5 -2
View File
@@ -116,7 +116,10 @@ func Diff(oldName string, old []byte, newName string, new []byte) []byte {
// End chunk with common lines for context.
if len(ctext) > 0 {
n := min(end.x-start.x, C)
n := end.x - start.x
if n > C {
n = C
}
for _, s := range x[start.x : start.x+n] {
ctext = append(ctext, " "+s)
count.x++
@@ -231,7 +234,7 @@ func tgs(x, y []string) []pair {
for i := range T {
T[i] = n + 1
}
for i := range n {
for i := 0; i < n; i++ {
k := sort.Search(n, func(k int) bool {
return T[k] >= J[i]
})
+1 -1
View File
@@ -136,7 +136,7 @@ func readErrorMessages(t *testing.T, file string) string {
}
var errors []string
for line := range strings.SplitSeq(string(data), "\n") {
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "// ERROR: ") {
errors = append(errors, strings.TrimRight(line[len("// ERROR: "):], "\r\n"))
}
+10 -9
View File
@@ -1,6 +1,6 @@
module github.com/tinygo-org/tinygo
go 1.25.0
go 1.24.0
require (
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
@@ -15,21 +15,22 @@ require (
github.com/mgechev/revive v1.3.9
github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3
github.com/tetratelabs/wazero v1.9.0
go.bug.st/serial v1.8.0
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.56.0
golang.org/x/sys v0.47.0
golang.org/x/tools v0.47.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.8.0
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5
tinygo.org/x/espflasher v0.6.1
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
)
require (
github.com/BurntSushi/toml v1.4.0 // indirect
github.com/chavacava/garif v0.1.0 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/creack/goselect v0.1.2 // indirect
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
github.com/fatih/color v1.17.0 // indirect
github.com/fatih/structtag v1.2.0 // indirect
@@ -47,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.37.0 // indirect
golang.org/x/text v0.38.0 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/text v0.34.0 // indirect
)
+20 -18
View File
@@ -8,6 +8,8 @@ github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc
github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -87,30 +89,30 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
go.bug.st/serial v1.8.0 h1:ZtnmN8aYXtPlTghwSvDWPHKBHL9TM6oFDa+KpSn4SQE=
go.bug.st/serial v1.8.0/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
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.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
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.8.0 h1:iZionLEnSD9svkqcTI4ZBj/247B0dEme1lQ0DDNRFL4=
tinygo.org/x/espflasher v0.8.0/go.mod h1:YLkCtOCz6gdrbueTi6uZbGPSYduTYt62JdNGmiGxgc0=
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5 h1:0PKRhM1INWAi7PdIng1BHMPTDaRcOmv1UymdTgZ76Jo=
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5/go.mod h1:GFbusT2VTA4I+l4j80b17KFK+6whv69Wtny5U+T8RR0=
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=
-2
View File
@@ -151,8 +151,6 @@ 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.
+6 -18
View File
@@ -140,30 +140,18 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
panic("unknown number of operands")
}
case llvm.Switch:
// Compile to an array of (value, label) pairs, of which the
// A switch is an array of (value, label) pairs, of which the
// first one indicates the to-switch value and the default
// label.
//
// Successor 0 is always the default destination; successors
// 1..N-1 are the individual cases. This must be read via
// GetSwitchCaseValue/Successor rather than raw operands,
// because LLVM 22 stopped exposing switch case values as
// regular instruction operands (only the condition and
// destination-block operands remain).
inst.operands = append(inst.operands,
r.getValue(llvmInst.Operand(0)),
literalValue{uint32(blockIndices[llvmInst.Successor(0).AsValue()])},
)
for i := 1; i < llvmInst.SuccessorsCount(); i++ {
inst.operands = append(inst.operands,
r.getValue(llvmInst.GetSwitchCaseValue(i)),
literalValue{uint32(blockIndices[llvmInst.Successor(i).AsValue()])},
)
numOperands := llvmInst.OperandsCount()
for i := 0; i < numOperands; i += 2 {
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
inst.operands = append(inst.operands, literalValue{uint32(blockIndices[llvmInst.Operand(i+1)])})
}
case llvm.PHI:
inst.name = llvmInst.Name()
incomingCount := inst.llvmInst.IncomingCount()
for i := range incomingCount {
for i := 0; i < incomingCount; i++ {
incomingBB := inst.llvmInst.IncomingBlock(i)
incomingValue := inst.llvmInst.IncomingValue(i)
inst.operands = append(inst.operands,
+1 -26
View File
@@ -2,7 +2,6 @@ package interp
import (
"os"
"regexp"
"strings"
"testing"
"time"
@@ -23,6 +22,7 @@ func TestInterp(t *testing.T) {
"alloc",
"slicedata",
} {
name := name // make local to this closure
t.Run(name, func(t *testing.T) {
t.Parallel()
runTest(t, "testdata/"+name)
@@ -93,16 +93,6 @@ func runTest(t *testing.T, pathPrefix string) {
// equal. That means, only relevant lines are compared (excluding comments
// etc.).
func fuzzyEqualIR(s1, s2 string) bool {
// Golden files are written using the pre-LLVM21 'nocapture' spelling,
// which LLVM printed before any co-occurring attribute such as
// 'readonly' (e.g. "ptr nocapture readonly"). LLVM 21+ prints the
// equivalent 'captures(none)' instead, and after such attributes (e.g.
// "ptr readonly captures(none)"). Normalize both name and position back
// to the old spelling to keep a single golden file working across LLVM
// versions.
s1 = normalizeCapturesAttr(s1)
s2 = normalizeCapturesAttr(s2)
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
if len(lines1) != len(lines2) {
@@ -118,21 +108,6 @@ func fuzzyEqualIR(s1, s2 string) bool {
return true
}
// capturesNoneAttrRe matches a co-occurring attribute directly followed by
// 'captures(none)', which is how LLVM 21+ orders these two attributes when
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
// around).
var capturesNoneAttrRe = regexp.MustCompile(`\b(readonly|readnone|writeonly|nonnull)\s+captures\(none\)`)
// normalizeCapturesAttr rewrites LLVM 21+'s 'captures(none)' attribute back
// to the pre-LLVM21 'nocapture' spelling and position, so golden IR files
// written against LLVM <21 keep matching.
func normalizeCapturesAttr(s string) string {
s = capturesNoneAttrRe.ReplaceAllString(s, "nocapture $1")
s = strings.ReplaceAll(s, "captures(none)", "nocapture")
return s
}
// filterIrrelevantIRLines removes lines from the input slice of strings that
// are not relevant in comparing IR. For example, empty lines and comments are
// stripped out.
+4 -5
View File
@@ -5,7 +5,6 @@ import (
"fmt"
"math"
"os"
"slices"
"strconv"
"strings"
"time"
@@ -471,7 +470,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 := range numMethods {
for i := 0; i < numMethods; i++ {
methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "")
methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "")
if methodSignature == signature {
@@ -908,7 +907,7 @@ func (r *runner) interpretICmp(lhs, rhs value, predicate llvm.IntPredicate) bool
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
numOperands := inst.llvmInst.OperandsCount()
operands := make([]llvm.Value, numOperands)
for i := range numOperands {
for i := 0; i < numOperands; i++ {
operand := inst.llvmInst.Operand(i)
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
var err error
@@ -986,9 +985,9 @@ func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, me
mem.instructions = append(mem.instructions, agg)
}
result = operands[1]
for i, indice := range slices.Backward(indices) {
for i := len(indices) - 1; i >= 0; i-- {
agg := aggregates[i]
result = r.builder.CreateInsertValue(agg, result, int(indice), inst.name+".insertvalue"+strconv.Itoa(i))
result = r.builder.CreateInsertValue(agg, result, int(indices[i]), inst.name+".insertvalue"+strconv.Itoa(i))
if i != 0 { // don't add last result to mem.instructions as it will be done at the end already
mem.instructions = append(mem.instructions, result)
}
+18 -18
View File
@@ -18,10 +18,8 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
"math/big"
"slices"
"strconv"
"strings"
@@ -82,7 +80,9 @@ func (mv *memoryView) extend(sub memoryView) {
if mv.objects == nil && len(sub.objects) != 0 {
mv.objects = make(map[uint32]object)
}
maps.Copy(mv.objects, sub.objects)
for key, value := range sub.objects {
mv.objects[key] = value
}
mv.instructions = append(mv.instructions, sub.instructions...)
}
@@ -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 _, llvmInst := range slices.Backward(mv.instructions) {
for i := len(mv.instructions) - 1; i >= 0; i-- {
llvmInst := mv.instructions[i]
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 := range numOperands {
for i := 0; i < numOperands; i++ {
// 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 := range numElements {
for i := 0; i < numElements; i++ {
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 := range numElements {
for i := 0; i < numElements; i++ {
element := mv.r.builder.CreateExtractValue(llvmValue, i, "")
err := mv.markExternal(element, mark)
if err != nil {
@@ -366,7 +366,7 @@ func (mv *memoryView) store(v value, p pointerValue) bool {
buffer := obj.buffer.asRawValue(mv.r)
obj.buffer = buffer
v := v.asRawValue(mv.r)
for i := range valueLen {
for i := uint32(0); i < valueLen; i++ {
buffer.buf[p.offset()+i] = v.buf[i]
}
}
@@ -392,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 any
value interface{}
}
// Make a literalValue given the number of bits.
@@ -931,10 +931,10 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
return llvm.Value{}, err
}
}
// Always use ConstNamedStruct to preserve the exact type identity.
// ConstStruct creates a literal struct type which may differ from an
// anonymous identified struct even when structurally identical.
return llvm.ConstNamedStruct(llvmType, fields), nil
if llvmType.StructName() != "" {
return llvm.ConstNamedStruct(llvmType, fields), nil
}
return llvmType.Context().ConstStruct(fields, false), nil
case llvm.ArrayTypeKind:
numElements := llvmType.ArrayLength()
childType := llvmType.ElementType()
@@ -1015,7 +1015,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
if err != nil {
panic(err)
}
for i := range ptrSize {
for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptr.pointer
}
} else if !llvmValue.IsAConstantExpr().IsNil() {
@@ -1056,7 +1056,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
panic(err)
}
ptrValue.pointer += totalOffset
for i := range ptrSize {
for i := uint32(0); i < ptrSize; i++ {
v.buf[i] = ptrValue.pointer
}
case llvm.ICmp:
@@ -1113,7 +1113,7 @@ func (v *rawValue) set(llvmValue llvm.Value, r *runner) {
}
case llvm.StructTypeKind:
numElements := llvmType.StructElementTypesCount()
for i := range numElements {
for i := 0; i < numElements; i++ {
offset := r.targetData.ElementOffset(llvmType, i)
field := rawValue{
buf: v.buf[offset:],
@@ -1124,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 := range numElements {
for i := 0; i < numElements; i++ {
offset := i * int(childTypeSize)
field := rawValue{
buf: v.buf[offset:],
-6
View File
@@ -22,7 +22,6 @@ 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"
@@ -507,11 +506,6 @@ 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
+25 -21
View File
@@ -786,15 +786,15 @@ func Debug(debugger, pkgName string, ocdOutput bool, options *compileopts.Option
}
defer func() {
daemon.Process.Signal(os.Interrupt)
var stopped atomic.Uint32
var stopped uint32
go func() {
time.Sleep(time.Millisecond * 100)
if stopped.Load() == 0 {
if atomic.LoadUint32(&stopped) == 0 {
daemon.Process.Kill()
}
}()
daemon.Wait()
stopped.Store(1)
atomic.StoreUint32(&stopped, 1)
}()
}
@@ -1046,7 +1046,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c
func touchSerialPortAt1200bps(port string) (err error) {
retryCount := 3
for range retryCount {
for i := 0; i < retryCount; i++ {
// Open port
p, e := serial.Open(port, &serial.Mode{BaudRate: 1200})
if e != nil {
@@ -1122,19 +1122,13 @@ const (
jtagReset = "jtag"
)
var progressFunc = func(current, total int) {
pct := float64(current) / float64(total) * 100
bar := int(pct / 2)
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
if current >= total {
fmt.Println()
}
}
func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Options) error {
opts := espflasher.DefaultOptions()
opts.Compress = true
opts.Logger = &espflasher.StdoutLogger{W: os.Stdout}
if options.BaudRate != 0 {
opts.FlashBaudRate = options.BaudRate
}
if resetMode == jtagReset {
opts.ResetMode = espflasher.ResetUSBJTAG
@@ -1158,12 +1152,21 @@ func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Op
return err
}
if err := flasher.EraseFlash(progressFunc); err != nil {
if err := flasher.EraseFlash(); err != nil {
return fmt.Errorf("erase failed: %v", err)
}
progress := func(current, total int) {
pct := float64(current) / float64(total) * 100
bar := int(pct / 2)
fmt.Printf("\r[%-50s] %6.1f%%", strings.Repeat("#", bar)+strings.Repeat(".", 50-bar), pct)
if current >= total {
fmt.Println()
}
}
// Flash with progress reporting
err = flasher.FlashImage(data, offset, progressFunc)
err = flasher.FlashImage(data, offset, progress)
if err != nil {
return err
}
@@ -1241,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.SplitSeq(string(tab), "\n") {
for _, line := range strings.Split(string(tab), "\n") {
fields := strings.Fields(line)
if len(fields) <= 2 {
continue
@@ -1270,7 +1273,7 @@ func findFATMounts(options *compileopts.Options) ([]mountPoint, error) {
}
// Extract data to convert to a []mountPoint slice.
for line := range strings.SplitSeq(out.String(), "\n") {
for _, line := range strings.Split(out.String(), "\n") {
words := strings.Fields(line)
if len(words) < 3 {
continue
@@ -1682,18 +1685,18 @@ func (m globalValuesFlag) String() string {
}
func (m globalValuesFlag) Set(value string) error {
before, after, ok := strings.Cut(value, "=")
if !ok {
equalsIndex := strings.IndexByte(value, '=')
if equalsIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
pathAndName := before
pathAndName := value[:equalsIndex]
pointIndex := strings.LastIndexByte(pathAndName, '.')
if pointIndex < 0 {
return errors.New("expected format pkgpath.Var=value")
}
path := pathAndName[:pointIndex]
name := pathAndName[pointIndex+1:]
stringValue := after
stringValue := value[equalsIndex+1:]
if m[path] == nil {
m[path] = make(map[string]string)
}
@@ -2051,6 +2054,7 @@ 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)
+12 -148
View File
@@ -59,7 +59,6 @@ func TestBuild(t *testing.T) {
"cgo/",
"channel.go",
"embed/",
"finalizer.go",
"float.go",
"gc.go",
"generics.go",
@@ -246,21 +245,6 @@ func TestBuild(t *testing.T) {
}
}
// TestTimerStopResetRace checks that stopping or resetting a timer while its
// callback is running behaves correctly. It reaches into the runtime timer
// hooks via //go:linkname and relies on the threads scheduler, which is only
// used on these hosts.
func TestTimerStopResetRace(t *testing.T) {
t.Parallel()
switch runtime.GOOS {
case "darwin", "linux":
default:
t.Skipf("host GOOS %s does not use the threads scheduler", runtime.GOOS)
}
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options)
@@ -274,17 +258,8 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
isWASI := strings.HasPrefix(options.Target, "wasi")
isWebAssembly := isWASI || strings.HasPrefix(options.Target, "wasm") || (options.Target == "" && strings.HasPrefix(options.GOARCH, "wasm"))
isBaremetal := options.Target == "simavr" || options.Target == "cortex-m-qemu" || options.Target == "riscv-qemu"
_, goMinor, err := goenv.GetGorootVersion()
if err != nil {
t.Fatal("could not get version:", goMinor)
}
for _, name := range tests {
if name == "goroutines.go" && (spec.Scheduler == "threads" || spec.Scheduler == "cores") {
// This test intentionally checks concurrent scheduling by comparing
// output order, so only run it with non-threaded schedulers.
continue
}
if options.GOOS == "linux" && (options.GOARCH == "arm" || options.GOARCH == "386") {
switch name {
case "timers.go":
@@ -306,11 +281,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue
}
}
if options.Target == "cortex-m-qemu" && goMinor >= 27 && name == "json.go" {
// Go 1.27 jsonv2 exceeds the LM3S6965's 256KiB flash. json.go
// is still covered by larger targets such as riscv-qemu.
continue
}
if options.Target == "simavr" {
// Not all tests are currently supported on AVR.
// Skip the ones that aren't.
@@ -364,17 +334,6 @@ func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
continue
}
}
if name == "finalizer.go" && options.Target != "wasm" {
// runtime.SetFinalizer is implemented for the block GC, but the
// test asserts deterministic collection of a dropped object, which
// only holds on the GOOS=js wasm target. The host default GC is
// boehm (SetFinalizer is a no-op there); conservative stack scanning
// on the emulated targets can pin the object; and the wasip2
// component entry lays out the stack differently, so collection is
// not deterministic on those. The feature still works on all of
// them, it just can't be golden-tested for firing.
continue
}
name := name // redefine to avoid race condition
t.Run(name, func(t *testing.T) {
@@ -424,7 +383,7 @@ func emuCheck(t *testing.T, options compileopts.Options) {
t.Fatal("failed to load target spec:", err)
}
if spec.Emulator != "" {
emulatorCommand, _, _ := strings.Cut(spec.Emulator, " ")
emulatorCommand := strings.SplitN(spec.Emulator, " ", 2)[0]
_, err := exec.LookPath(emulatorCommand)
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
@@ -499,12 +458,6 @@ 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 {
@@ -514,18 +467,12 @@ 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.SplitSeq(strings.TrimRight(w.String(), "\n"), "\n") {
for _, line := range strings.Split(strings.TrimRight(w.String(), "\n"), "\n") {
t.Log(line)
}
if stdout.Len() != 0 {
@@ -537,7 +484,11 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
actual := stdout.Bytes()
if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual)
// Strip simavr log formatting.
actual = bytes.Replace(actual, []byte{0x1b, '[', '3', '2', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{0x1b, '[', '0', 'm'}, nil, -1)
actual = bytes.Replace(actual, []byte{'.', '.', '\n'}, []byte{'\n'}, -1)
actual = bytes.Replace(actual, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'}, -1)
}
if name == "testing.go" {
// Strip actual time.
@@ -564,28 +515,6 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
}
}
func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{'.', '.', '\n'}, []byte{'\n'})
output = bytes.ReplaceAll(output, []byte{'\n', '.', '\n'}, []byte{'\n', '\n'})
var cleaned []byte
for _, line := range bytes.SplitAfter(output, []byte{'\n'}) {
trimmedLine := bytes.TrimRight(line, "\r\n")
if simavrLoadTextLogPattern.Match(trimmedLine) || simavrLoadBytesLogPattern.Match(trimmedLine) {
continue
}
cleaned = append(cleaned, line...)
}
return cleaned
}
var (
simavrLoadTextLogPattern = regexp.MustCompile(`^Loaded [0-9]+ \.[A-Za-z0-9_]+( at address 0x[0-9a-fA-F]+)?$`)
simavrLoadBytesLogPattern = regexp.MustCompile(`^Loaded [0-9]+ bytes of [A-Za-z]+ data at (0x)?[0-9a-fA-F]+$`)
)
// Test WebAssembly files for certain properties.
func TestWebAssembly(t *testing.T) {
t.Parallel()
@@ -601,6 +530,7 @@ func TestWebAssembly(t *testing.T) {
{name: "panic-default", target: "wasip1", imports: []string{"wasi_snapshot_preview1.fd_write", "wasi_snapshot_preview1.random_get"}},
{name: "panic-trap", target: "wasm-unknown", panicStrategy: "trap", imports: []string{}},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tmpdir := t.TempDir()
@@ -722,6 +652,7 @@ func TestWasmExport(t *testing.T) {
}
for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@@ -891,6 +822,7 @@ 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.
@@ -938,6 +870,7 @@ 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)
@@ -980,76 +913,6 @@ func checkOutputData(t *testing.T, expectedOutput, actual []byte) {
}
}
func TestGoexitCrash(t *testing.T) {
t.Parallel()
options := optionsFromTarget("", sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
want string
}{
{"main", "fatal error: all goroutines are asleep - deadlock!"},
{"deadlock", "fatal error: all goroutines are asleep - deadlock!"},
{"exit", "fatal error: all goroutines are asleep - deadlock!"},
{"main-other", "fatal error: all goroutines are asleep - deadlock!"},
{"in-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"panic", "panic: panic after Goexit"},
{"recovered-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"recover-before-panic", "fatal error: all goroutines are asleep - deadlock!"},
{"recover-before-panic-loop", "fatal error: 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 TestRuntimeFatal(t *testing.T) {
t.Parallel()
options := optionsFromTarget("", sema)
config, err := builder.NewConfig(&options)
if err != nil {
t.Fatal(err)
}
output := &bytes.Buffer{}
_, err = buildAndRun("testdata/runtimefatal.go", config, output, nil, 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 want := "fatal error: sync: unlock of unlocked Mutex"; !strings.Contains(output.String(), want) {
t.Fatalf("output does not contain %q:\n%s", want, output.String())
}
if strings.Contains(output.String(), "recovered:") {
t.Fatalf("fatal runtime error was recovered:\n%s", output.String())
}
}
func TestTest(t *testing.T) {
t.Parallel()
@@ -1082,6 +945,7 @@ func TestTest(t *testing.T) {
)
}
for _, targ := range targs {
targ := targ
t.Run(targ.name, func(t *testing.T) {
t.Parallel()
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1 || stm32u0)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
//go:build nrf || (stm32 && !(stm32f103 || stm32l0x1)) || (sam && atsamd51) || (sam && atsame5x) || esp32c3 || esp32s3 || tkey || (tinygo.riscv32 && virt)
// If you update the above build constraint, you'll probably also need to update
// src/runtime/rand_hwrng.go.
-107
View File
@@ -1,107 +0,0 @@
// TINYGO: cipher suite IDs and the exported CipherSuites/InsecureCipherSuites
// descriptors, copied verbatim from the Go official implementation. TinyGo has
// no software handshake, so only these declarations (no selection machinery)
// are provided to satisfy callers such as google.golang.org/grpc/credentials
// and github.com/pion/dtls that reference the cipher-suite API surface.
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package tls
// A list of cipher suite IDs that are, or have been, implemented by this
// package.
//
// See https://www.iana.org/assignments/tls-parameters/tls-parameters.xml
const (
// TLS 1.0 - 1.2 cipher suites.
TLS_RSA_WITH_RC4_128_SHA uint16 = 0x0005
TLS_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0x000a
TLS_RSA_WITH_AES_128_CBC_SHA uint16 = 0x002f
TLS_RSA_WITH_AES_256_CBC_SHA uint16 = 0x0035
TLS_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0x003c
TLS_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0x009c
TLS_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0x009d
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA uint16 = 0xc007
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA uint16 = 0xc009
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA uint16 = 0xc00a
TLS_ECDHE_RSA_WITH_RC4_128_SHA uint16 = 0xc011
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA uint16 = 0xc012
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA uint16 = 0xc013
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA uint16 = 0xc014
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc023
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 uint16 = 0xc027
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02f
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 uint16 = 0xc02b
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc030
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 uint16 = 0xc02c
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca8
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 uint16 = 0xcca9
// TLS 1.3 cipher suites.
TLS_AES_128_GCM_SHA256 uint16 = 0x1301
TLS_AES_256_GCM_SHA384 uint16 = 0x1302
TLS_CHACHA20_POLY1305_SHA256 uint16 = 0x1303
// TLS_FALLBACK_SCSV isn't a standard cipher suite but an indicator
// that the client is doing version fallback. See RFC 7507.
TLS_FALLBACK_SCSV uint16 = 0x5600
// Legacy names for the corresponding cipher suites with the correct _SHA256
// suffix, retained for backward compatibility.
TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305 = TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
)
var (
supportedUpToTLS12 = []uint16{VersionTLS10, VersionTLS11, VersionTLS12}
supportedOnlyTLS12 = []uint16{VersionTLS12}
supportedOnlyTLS13 = []uint16{VersionTLS13}
)
// CipherSuites returns a list of cipher suites currently implemented by this
// package, excluding those with security issues, which are returned by
// InsecureCipherSuites.
func CipherSuites() []*CipherSuite {
return []*CipherSuite{
{TLS_AES_128_GCM_SHA256, "TLS_AES_128_GCM_SHA256", supportedOnlyTLS13, false},
{TLS_AES_256_GCM_SHA384, "TLS_AES_256_GCM_SHA384", supportedOnlyTLS13, false},
{TLS_CHACHA20_POLY1305_SHA256, "TLS_CHACHA20_POLY1305_SHA256", supportedOnlyTLS13, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, false},
{TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
{TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", supportedOnlyTLS12, false},
}
}
// InsecureCipherSuites returns a list of cipher suites currently implemented by
// this package and which have security issues.
//
// Most applications should not use the cipher suites in this list, and should
// only use those returned by CipherSuites.
func InsecureCipherSuites() []*CipherSuite {
// This list includes legacy RSA kex, RC4, CBC_SHA256, and 3DES cipher
// suites. See cipherSuitesPreferenceOrder for details.
return []*CipherSuite{
{TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA", supportedUpToTLS12, true},
{TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_128_GCM_SHA256, "TLS_RSA_WITH_AES_128_GCM_SHA256", supportedOnlyTLS12, true},
{TLS_RSA_WITH_AES_256_GCM_SHA384, "TLS_RSA_WITH_AES_256_GCM_SHA384", supportedOnlyTLS12, true},
{TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_RC4_128_SHA, "TLS_ECDHE_RSA_WITH_RC4_128_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", supportedUpToTLS12, true},
{TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
{TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", supportedOnlyTLS12, true},
}
}
+2 -121
View File
@@ -55,16 +55,6 @@ 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.
@@ -78,42 +68,14 @@ type ConnectionState struct {
//
// Minimum (empty) fields for fortio.org/log http logging and others
// to compile and run.
Version uint16
ServerName string
PeerCertificates []*x509.Certificate
CipherSuite uint16
NegotiatedProtocol string
NegotiatedProtocolIsMutual bool // Deprecated: this value is always true.
PeerCertificates []*x509.Certificate
CipherSuite uint16
}
// 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
@@ -138,45 +100,6 @@ 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 {
@@ -536,48 +459,6 @@ 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
+3 -42
View File
@@ -20,52 +20,13 @@ 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) *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}
func Client(conn net.Conn, config *Config) *net.TLSConn {
panic("tls.Client() not implemented")
return nil
}
// A listener implements a network listener (net.Listener) for TLS connections.
-74
View File
@@ -1,74 +0,0 @@
//go:build amd64
package amd64
const (
CPUIDTimeStampCounter = 0x15
CPUIDProcessorFrequency = 0x16
)
type CPUExtendedFamily uint16
const (
CPUFamilyIntelCore CPUExtendedFamily = 6
)
//export asmPause
func AsmPause()
//export asmReadRdtsc
func AsmReadRdtsc() uint64
//export asmCpuid
func AsmCpuid(index uint32, registerEax *uint32, registerEbx *uint32, registerEcx *uint32) int
var maxCpuidIndex uint32
var stdVendorName0 uint32
var stdCpuid1Eax uint32
func init() {
AsmCpuid(0, &maxCpuidIndex, &stdVendorName0, nil)
AsmCpuid(1, &stdCpuid1Eax, nil, nil)
}
func getExtendedCPUFamily() CPUExtendedFamily {
family := CPUExtendedFamily((stdCpuid1Eax >> 8) & 0x0f)
family += CPUExtendedFamily((stdCpuid1Eax >> 20) & 0xff)
return family
}
func isIntel() bool {
return stdVendorName0 == 0x756e6547
}
func isIntelFamilyCore() bool {
return isIntel() && getExtendedCPUFamily() == CPUFamilyIntelCore
}
func InternalGetPerformanceCounterFrequency() uint64 {
if maxCpuidIndex >= CPUIDTimeStampCounter {
return cpuidCoreClockCalculateTSCFrequency()
}
return 0
}
func cpuidCoreClockCalculateTSCFrequency() uint64 {
var eax uint32
var ebx uint32
var ecx uint32
AsmCpuid(CPUIDTimeStampCounter, &eax, &ebx, &ecx)
if eax == 0 || ebx == 0 {
return 0
}
coreCrystalFrequency := uint64(ecx)
if coreCrystalFrequency == 0 {
if !isIntelFamilyCore() {
return 0
}
coreCrystalFrequency = 24000000
}
return ((coreCrystalFrequency * uint64(ebx)) + (uint64(eax) / 2)) / uint64(eax)
}
-39
View File
@@ -1,39 +0,0 @@
.section .text
.global asmPause
asmPause:
pause
ret
.global asmReadRdtsc
asmReadRdtsc:
rdtsc
shlq $0x20, %rdx
orq %rdx, %rax
ret
.global asmCpuid
asmCpuid:
pushq %rbx
mov %ecx, %eax
pushq %rax
pushq %rdx
cpuid
test %r9, %r9
jz .SkipEcx
mov %ecx, (%r9)
.SkipEcx:
popq %rcx
jrcxz .SkipEax
mov %eax, (%rcx)
.SkipEax:
mov %r8, %rcx
jrcxz .SkipEbx
mov %ebx, (%rcx)
.SkipEbx:
popq %rax
popq %rbx
ret
-143
View File
@@ -1,143 +0,0 @@
//go:build cortexm
package arm
import (
"runtime/volatile"
"unsafe"
)
/*
The struct below has been created using gen-device-svd
on cortex_m/armv7em.yaml from stm32-rs.
The MPU type has been included as some stm32 svd files
do not contain MPU definitions.
*/
const MPU_BASE = SCS_BASE + 0x0D90
// Memory Protection Unit
type MPU_Type struct {
TYPE volatile.Register32 // 0x0
CTRL volatile.Register32 // 0x4
RNR volatile.Register32 // 0x8
RBAR volatile.Register32 // 0xC
RASR volatile.Register32 // 0x10
RBAR_A1 volatile.Register32 // 0x14
RASR_A1 volatile.Register32 // 0x18
RBAR_A2 volatile.Register32 // 0x1C
RASR_A2 volatile.Register32 // 0x20
RBAR_A3 volatile.Register32 // 0x24
RASR_A3 volatile.Register32 // 0x28
}
var MPU = (*MPU_Type)(unsafe.Pointer(uintptr(MPU_BASE)))
// Constants for MPU: Memory protection unit
const (
// TYPER: MPU type register
// Position of SEPARATE field.
MPU_TYPER_SEPARATE_Pos = 0x0
// Bit mask of SEPARATE field.
MPU_TYPER_SEPARATE_Msk = 0x1
// Bit SEPARATE.
MPU_TYPER_SEPARATE = 0x1
// Position of DREGION field.
MPU_TYPER_DREGION_Pos = 0x8
// Bit mask of DREGION field.
MPU_TYPER_DREGION_Msk = 0xff00
// Position of IREGION field.
MPU_TYPER_IREGION_Pos = 0x10
// Bit mask of IREGION field.
MPU_TYPER_IREGION_Msk = 0xff0000
// CTRL: MPU control register
// Position of ENABLE field.
MPU_CTRL_ENABLE_Pos = 0x0
// Bit mask of ENABLE field.
MPU_CTRL_ENABLE_Msk = 0x1
// Bit ENABLE.
MPU_CTRL_ENABLE = 0x1
// Position of HFNMIENA field.
MPU_CTRL_HFNMIENA_Pos = 0x1
// Bit mask of HFNMIENA field.
MPU_CTRL_HFNMIENA_Msk = 0x2
// Bit HFNMIENA.
MPU_CTRL_HFNMIENA = 0x2
// Position of PRIVDEFENA field.
MPU_CTRL_PRIVDEFENA_Pos = 0x2
// Bit mask of PRIVDEFENA field.
MPU_CTRL_PRIVDEFENA_Msk = 0x4
// Bit PRIVDEFENA.
MPU_CTRL_PRIVDEFENA = 0x4
// RNR: MPU region number register
// Position of REGION field.
MPU_RNR_REGION_Pos = 0x0
// Bit mask of REGION field.
MPU_RNR_REGION_Msk = 0xff
// RBAR: MPU region base address register
// Position of REGION field.
MPU_RBAR_REGION_Pos = 0x0
// Bit mask of REGION field.
MPU_RBAR_REGION_Msk = 0xf
// Position of VALID field.
MPU_RBAR_VALID_Pos = 0x4
// Bit mask of VALID field.
MPU_RBAR_VALID_Msk = 0x10
// Bit VALID.
MPU_RBAR_VALID = 0x10
// Position of ADDR field.
MPU_RBAR_ADDR_Pos = 0x5
// Bit mask of ADDR field.
MPU_RBAR_ADDR_Msk = 0xffffffe0
// RASR: MPU region attribute and size register
// Position of ENABLE field.
MPU_RASR_ENABLE_Pos = 0x0
// Bit mask of ENABLE field.
MPU_RASR_ENABLE_Msk = 0x1
// Bit ENABLE.
MPU_RASR_ENABLE = 0x1
// Position of SIZE field.
MPU_RASR_SIZE_Pos = 0x1
// Bit mask of SIZE field.
MPU_RASR_SIZE_Msk = 0x3e
// Position of SRD field.
MPU_RASR_SRD_Pos = 0x8
// Bit mask of SRD field.
MPU_RASR_SRD_Msk = 0xff00
// Position of B field.
MPU_RASR_B_Pos = 0x10
// Bit mask of B field.
MPU_RASR_B_Msk = 0x10000
// Bit B.
MPU_RASR_B = 0x10000
// Position of C field.
MPU_RASR_C_Pos = 0x11
// Bit mask of C field.
MPU_RASR_C_Msk = 0x20000
// Bit C.
MPU_RASR_C = 0x20000
// Position of S field.
MPU_RASR_S_Pos = 0x12
// Bit mask of S field.
MPU_RASR_S_Msk = 0x40000
// Bit S.
MPU_RASR_S = 0x40000
// Position of TEX field.
MPU_RASR_TEX_Pos = 0x13
// Bit mask of TEX field.
MPU_RASR_TEX_Msk = 0x380000
// Position of AP field.
MPU_RASR_AP_Pos = 0x18
// Bit mask of AP field.
MPU_RASR_AP_Msk = 0x7000000
// Position of XN field.
MPU_RASR_XN_Pos = 0x1c
// Bit mask of XN field.
MPU_RASR_XN_Msk = 0x10000000
// Bit XN.
MPU_RASR_XN = 0x10000000
)
+8 -232
View File
@@ -10,49 +10,6 @@
.section .text.call_start_cpu0
1:
.long _stack_top
.Lmain_addr:
.long main
.Lrom_mmu_init:
.long 0x400095a4 // mmu_init(int cpu_no)
.Lrom_cache_flash_mmu_set:
.long 0x400095e0 // cache_flash_mmu_set(cpu, pid, vaddr, paddr, pgsz, pgcnt)
.Lrom_Cache_Read_Enable:
.long 0x40009a84 // Cache_Read_Enable(int cpu_no)
.Lrom_Cache_Read_Disable:
.long 0x40009ab8 // Cache_Read_Disable(int cpu_no)
.Lrom_Cache_Flush:
.long 0x40009a14 // Cache_Flush(int cpu_no)
.Lrodata_start:
.long _rodata_start
.Lrodata_end:
.long _rodata_end
.Ltext_start:
.long _text_start
.Ltext_end:
.long _text_end
.Ldport_pro_cache_ctrl1:
.long 0x3FF00044 // DPORT_PRO_CACHE_CTRL1_REG
.Ldrom_paddr_ptr:
.long _drom_flash_addr // pointer to builder-patched DROM flash offset
.Ldrom_vaddr:
.long 0x3F400000 // DROM virtual base address
.Lirom_vaddr:
.long 0x400D0000 // IROM virtual base address
.Lmmu_table_base:
.long 0x3FF10000 // PRO CPU Flash MMU table
.Lrtc_wdt_protect:
.long 0x3FF480A4 // RTC_CNTL_WDTWPROTECT_REG
.Lrtc_wdt_key:
.long 0x50D83AA1 // WDT write-protect key
.Lrtc_wdt_config0:
.long 0x3FF4808C // RTC_CNTL_WDTCONFIG0_REG
.Ltimg0_wdt_protect:
.long 0x3FF5F064 // TIMG0_WDTWPROTECT_REG
.Ltimg0_wdt_config0:
.long 0x3FF5F048 // TIMG0_WDTCONFIG0_REG
.Lvector_table:
.long _vector_table
.global call_start_cpu0
call_start_cpu0:
// We need to set the stack pointer to a different value. This is somewhat
@@ -60,14 +17,10 @@ call_start_cpu0:
// version of the following code:
// https://github.com/espressif/esp-idf/blob/c77c4ccf/components/xtensa/include/xt_instr_macros.h#L47
// Disable WOE (bit 18 of PS).
// Avoid large movi constants to prevent auto-generated .literal section
// entries, which cause l32r offset miscalculation in LLVM 22 / lld.
// Disable WOE.
rsr.ps a2
movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE_MASK (0x40000)
and a3, a2, a3 // a3 = a2 & WOE_MASK (isolate WOE bit)
xor a2, a2, a3 // clear WOE bit
movi a3, ~(PS_WOE_MASK)
and a2, a2, a3
wsr.ps a2
rsync
@@ -84,8 +37,7 @@ call_start_cpu0:
// Re-enable WOE.
rsr.ps a2
movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
movi a3, PS_WOE
or a2, a2, a3
wsr.ps a2
rsync
@@ -95,187 +47,11 @@ call_start_cpu0:
wsr.cpenable a2
rsync
// Disable the RTC and TIMG0 watchdogs before configuring the flash cache.
// The ROM bootloader leaves them running; a fault during cache setup would
// otherwise reset the chip. The Go runtime re-disables them once it starts.
l32r a2, .Lrtc_wdt_protect
l32r a3, .Lrtc_wdt_key
s32i a3, a2, 0 // unlock WDT write-protect
memw
l32r a2, .Lrtc_wdt_config0
movi a3, 0
s32i a3, a2, 0 // disable WDT (write 0 to config0)
memw
// Jump to the runtime start function written in Go.
call4 main
// Disable TG0 WDT (Timer Group 0 Main Watchdog).
// TIMG0_WDTWPROTECT_REG = 0x3FF5F064, TIMG0_WDTCONFIG0_REG = 0x3FF5F048
l32r a2, .Ltimg0_wdt_protect
l32r a3, .Lrtc_wdt_key // same unlock key 0x50D83AA1
s32i a3, a2, 0
memw
l32r a2, .Ltimg0_wdt_config0
movi a3, 0
s32i a3, a2, 0
memw
// Set VECBASE to our vector table. Must happen before any callx4 so that
// register-window overflow exceptions route to our handlers.
l32r a2, .Lvector_table
wsr.vecbase a2
rsync
// Clear PS.EXCM so window overflow exceptions work properly.
rsr.ps a2
movi a3, ~0x1F
and a2, a2, a3
movi a3, 0x20 // PS.UM = 1
or a2, a2, a3
wsr.ps a2
rsync
// ---- Configure flash cache and MMU ----
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Read_Disable
callx4 a4
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Flush
callx4 a4
movi a6, 0
mov a5, a1
l32r a4, .Lrom_mmu_init
callx4 a4
l32r a2, .Lrodata_end
l32r a3, .Lrodata_start
sub a2, a2, a3
beqz a2, .Lskip_drom
addi a2, a2, -1
srli a2, a2, 16
addi a2, a2, 1
movi a6, 0
movi a7, 0
l32r a8, .Ldrom_vaddr
l32r a9, .Ldrom_paddr_ptr
l32i a9, a9, 0
movi a10, 64
mov a11, a2
mov a5, a1
l32r a4, .Lrom_cache_flash_mmu_set
callx4 a4
.Lskip_drom:
l32r a2, .Ltext_end
l32r a3, .Ltext_start
sub a2, a2, a3
beqz a2, .Lskip_irom
addi a2, a2, -1
srli a2, a2, 16
addi a2, a2, 1
l32r a9, .Lrodata_end
l32r a3, .Lrodata_start
sub a9, a9, a3
l32r a3, .Ldrom_paddr_ptr
l32i a3, a3, 0
beqz a9, .Lirom_paddr_ready
addi a9, a9, -1
srli a9, a9, 16
addi a9, a9, 1
slli a9, a9, 16
add a3, a3, a9
.Lirom_paddr_ready:
movi a6, 0
movi a7, 0
l32r a8, .Lirom_vaddr
mov a9, a3
movi a10, 64
mov a11, a2
mov a5, a1
l32r a4, .Lrom_cache_flash_mmu_set
callx4 a4
.Lskip_irom:
l32r a2, .Ldport_pro_cache_ctrl1
l32i a3, a2, 0
movi a4, ~0x11
and a3, a3, a4
s32i a3, a2, 0
memw
movi a6, 0
mov a5, a1
l32r a4, .Lrom_Cache_Read_Enable
callx4 a4
isync
// ---- Jump to main (in IROM/flash, now accessible) ----
mov a5, a1
l32r a4, .Lmain_addr
callx4 a4
// If main returns, loop forever.
1: j 1b
// -----------------------------------------------------------------------
// tinygo_scanCurrentStack Spill all Xtensa register windows to the
// stack, then call tinygo_scanstack(sp) so the conservative GC can
// discover live heap pointers that are currently in physical registers.
//
// On RISC-V / ARM the equivalent function pushes callee-saved registers
// before the call. On Xtensa windowed ABI the same effect is achieved
// by forcing hardware window-overflow for every occupied pane: each
// overflow saves the four registers in that pane to the stack frame
// pointed to by the pane's a1 (sp). After all panes are flushed, a
// scan from the current sp to stackTop covers every live value.
//
// Without this spill the conservative GC misses heap pointers held only
// in physical registers, frees live objects, and later crashes jumping
// through a freed/garbage function pointer (e.g. a goroutine trampoline).
// -----------------------------------------------------------------------
.section .text.tinygo_scanCurrentStack
.global tinygo_scanCurrentStack
tinygo_scanCurrentStack:
entry a1, 48
// Disable interrupts while flushing register windows.
rsr a4, PS
s32i a4, a1, 0 // save PS for later restore
rsil a4, 3 // XCHAL_EXCM_LEVEL
// Flush all register windows using recursive call4.
// For NAREG=64 (16 panes), 15 recursive levels cover all panes
// except the current one (which is kept active).
movi a6, 15
call4 .Lscan_spill
// Restore interrupts.
l32i a4, a1, 0
wsr.ps a4
rsync
// Pass current sp to tinygo_scanstack.
// call4 maps caller's a5callee's a1 (stack ptr for callee's entry)
// and caller's a6callee's a2 (first argument = sp).
mov a5, a1 // callee's a1 = valid stack pointer
mov a6, a1 // callee's a2 = sp argument
call4 tinygo_scanstack
retw
.balign 4
.Lscan_spill:
entry a1, 16
beqz a2, .Lscan_spill_done
addi a2, a2, -1
mov a6, a2
call4 .Lscan_spill
.Lscan_spill_done:
retw
// TODO: save callee saved registers on the stack
j tinygo_scanstack
+6 -14
View File
@@ -102,14 +102,10 @@ call_start_cpu0:
// ---- 1. Windowed-ABI register file setup ----
// Disable WOE (bit 18 of PS).
// Avoid large movi constants to prevent auto-generated .literal section
// entries, which cause l32r offset miscalculation in LLVM 22 / lld.
// Disable WOE so we can safely manipulate WINDOWSTART.
rsr.ps a2
movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
and a3, a2, a3 // isolate WOE bit
xor a2, a2, a3 // clear WOE bit
movi a3, ~(PS_WOE)
and a2, a2, a3
wsr.ps a2
rsync
@@ -126,8 +122,7 @@ call_start_cpu0:
// Re-enable WOE.
rsr.ps a2
movi a3, 1
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
movi a3, PS_WOE
or a2, a2, a3
wsr.ps a2
rsync
@@ -218,9 +213,7 @@ call_start_cpu0:
// and cannot service flash accesses.
// 4a. Configure ICache mode: 16KB, 8-way, 32-byte line
// Use movi+slli to avoid auto-literal generation (lld l32r bug).
movi a6, 1
slli a6, a6, 14 // a6 = 0x4000 = 16KB
movi a6, 0x4000 // cache_size = 16KB
movi a7, 8 // ways = 8
movi a8, 32 // line_size = 32
mov a5, a1
@@ -233,8 +226,7 @@ call_start_cpu0:
callx4 a4
// 4c. Configure DCache mode: 32KB, 8-way, 32-byte line
movi a6, 1
slli a6, a6, 15 // a6 = 0x8000 = 32KB
movi a6, 0x8000 // cache_size = 32KB
movi a7, 8 // ways = 8
movi a8, 32 // line_size = 32
mov a5, a1
-17
View File
@@ -1,17 +0,0 @@
//go:build i386 || amd64
package uefi
import "device/amd64"
func Ticks() uint64 {
return amd64.AsmReadRdtsc()
}
func CpuPause() {
amd64.AsmPause()
}
func getTSCFrequency() uint64 {
return amd64.InternalGetPerformanceCounterFrequency()
}
-201
View File
@@ -1,201 +0,0 @@
.section .text
.global uefiCall0
uefiCall0:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
callq *%rcx
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall1
uefiCall1:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall2
uefiCall2:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall3
uefiCall3:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall4
uefiCall4:
pushq %rbp
movq %rsp, %rbp
subq $0x20, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall5
uefiCall5:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall6
uefiCall6:
pushq %rbp
movq %rsp, %rbp
subq $0x30, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall7
uefiCall7:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall8
uefiCall8:
pushq %rbp
movq %rsp, %rbp
subq $0x40, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall9
uefiCall9:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global uefiCall10
uefiCall10:
pushq %rbp
movq %rsp, %rbp
subq $0x50, %rsp
movq %rcx, %rax
movq %rdx, %rcx
movq %r8, %rdx
movq %r9, %r8
movq 0x30(%rbp), %r9
movq 0x38(%rbp), %r10
movq %r10, 0x20(%rsp)
movq 0x40(%rbp), %r10
movq %r10, 0x28(%rsp)
movq 0x48(%rbp), %r10
movq %r10, 0x30(%rsp)
movq 0x50(%rbp), %r10
movq %r10, 0x38(%rsp)
movq 0x58(%rbp), %r10
movq %r10, 0x40(%rsp)
movq 0x60(%rbp), %r10
movq %r10, 0x48(%rsp)
callq *%rax
movq %rbp, %rsp
popq %rbp
ret
.global ___chkstk_ms
___chkstk_ms:
ret
-45
View File
@@ -1,45 +0,0 @@
package uefi
//go:nosplit
//export uefiCall0
func UefiCall0(fn uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall1
func UefiCall1(fn uintptr, a uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall2
func UefiCall2(fn uintptr, a uintptr, b uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall3
func UefiCall3(fn uintptr, a uintptr, b uintptr, c uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall4
func UefiCall4(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall5
func UefiCall5(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall6
func UefiCall6(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall7
func UefiCall7(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall8
func UefiCall8(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall9
func UefiCall9(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr) EFI_STATUS
//go:nosplit
//go:export uefiCall10
func UefiCall10(fn uintptr, a uintptr, b uintptr, c uintptr, d uintptr, e uintptr, f uintptr, g uintptr, h uintptr, i uintptr, j uintptr) EFI_STATUS
-77
View File
@@ -1,77 +0,0 @@
package uefi
import (
"unicode/utf16"
"unsafe"
)
// StringToCHAR16 converts a Go string to a UTF-16 code unit slice.
func StringToCHAR16(s string) []CHAR16 {
if s == "" {
return nil
}
encoded := utf16.Encode([]rune(s))
out := make([]CHAR16, len(encoded))
for i, r := range encoded {
out[i] = CHAR16(r)
}
return out
}
// StringToCHAR16Z converts a Go string to a NUL-terminated UTF-16 code unit slice.
func StringToCHAR16Z(s string) []CHAR16 {
out := StringToCHAR16(s)
return append(out, 0)
}
// BytesToCHAR16 converts UTF-8 text bytes to a UTF-16 code unit slice.
func BytesToCHAR16(b []byte) []CHAR16 {
return StringToCHAR16(string(b))
}
// BytesToCHAR16Z converts UTF-8 text bytes to a NUL-terminated UTF-16 code unit slice.
func BytesToCHAR16Z(b []byte) []CHAR16 {
return StringToCHAR16Z(string(b))
}
// CHAR16ToString converts a UTF-16 code unit slice to a Go string.
func CHAR16ToString(input []CHAR16) string {
if len(input) == 0 {
return ""
}
units := make([]uint16, len(input))
for i, c := range input {
units[i] = uint16(c)
}
return string(utf16.Decode(units))
}
// CHAR16ToBytes converts a UTF-16 code unit slice to UTF-8 text bytes.
func CHAR16ToBytes(input []CHAR16) []byte {
return []byte(CHAR16ToString(input))
}
// CHAR16PtrToString converts a NUL-terminated UTF-16 string pointer to a Go string.
func CHAR16PtrToString(input *CHAR16) string {
if input == nil {
return ""
}
ptr := uintptr(unsafe.Pointer(input))
length := 0
for *(*CHAR16)(unsafe.Pointer(ptr)) != 0 {
length++
ptr += 2
}
return CHAR16PtrLenToString(input, length)
}
// CHAR16PtrLenToString converts a UTF-16 string pointer with a known code unit count to a Go string.
func CHAR16PtrLenToString(input *CHAR16, length int) string {
if input == nil || length <= 0 {
return ""
}
return CHAR16ToString(unsafe.Slice(input, length))
}
-39
View File
@@ -1,39 +0,0 @@
//go:build uefi
package uefi
import "sync"
var calibrateMutex sync.Mutex
var calculatedFrequency uint64
func TicksFrequency() uint64 {
frequency := getTSCFrequency()
if frequency > 0 {
return frequency
}
calibrateMutex.Lock()
defer calibrateMutex.Unlock()
if calculatedFrequency > 0 {
return calculatedFrequency
}
var event EFI_EVENT
var index UINTN
if BS().CreateEvent(EVT_TIMER, TPL_CALLBACK, nil, nil, &event) != EFI_SUCCESS {
return 0
}
defer BS().CloseEvent(event)
start := Ticks()
if BS().SetTimer(event, TimerPeriodic, 250*10000) != EFI_SUCCESS {
return 0
}
if BS().WaitForEvent(1, &event, &index) != EFI_SUCCESS {
return 0
}
calculatedFrequency = (Ticks() - start) * 4
return calculatedFrequency
}

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