mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 22:58:41 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b0298c6a69 | |||
| 0fe4c619eb | |||
| 16fc1ea2bb | |||
| 9105cce340 | |||
| 7756941f39 | |||
| 259842dc2e | |||
| 528476f2b3 | |||
| 0c68e1215e | |||
| 9c2eafce6f | |||
| 3797e89600 | |||
| 40ed956d6c | |||
| 2a49216152 | |||
| 1e3baff966 | |||
| 4fcf03414c | |||
| 888d0c0f97 | |||
| fc17c3565e | |||
| 6aa966af0a | |||
| aee0581a32 | |||
| 2dea20b769 | |||
| 0c17b918fb | |||
| 8727b696a5 | |||
| 94736ac0e5 | |||
| 9e9be09e9a | |||
| 9e7d89d4d5 | |||
| 3b7c9e24f5 | |||
| bc35d087c2 | |||
| deaf532d61 | |||
| 5ec2632461 | |||
| 39a105dab9 | |||
| aa914bea5c | |||
| 3071e339cd | |||
| b536dd6f79 | |||
| 3ad913acb8 | |||
| 9e9f1d0b96 | |||
| cd4e479dd5 | |||
| cf5bed8227 | |||
| babdfc9e10 | |||
| 2602d4c25d | |||
| 0a9b3f91bc | |||
| 3b137c3053 | |||
| 5f02d6b659 | |||
| b8adb803c4 | |||
| 36d3b83e9c | |||
| a2a638c198 | |||
| 0ffa3eb748 | |||
| 922f583dd4 | |||
| 91a56b28a2 | |||
| 9c864f0d79 | |||
| 6203b624c5 | |||
| ea003da13f | |||
| 7994d2e912 | |||
| f1c39e8356 | |||
| 801bd484ab | |||
| be7b6b316e | |||
| ec184e90c1 | |||
| 07c9cdcbc4 | |||
| bd1d11d166 | |||
| 919c998355 | |||
| a3375fb2c4 | |||
| 09bda77b78 | |||
| 047ed57005 | |||
| c7923d1c64 | |||
| 73db9776b1 | |||
| 6d49f0177b |
@@ -40,13 +40,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-${{ matrix.os }}-v1
|
||||
key: llvm-source-22-${{ 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-20-${{ matrix.os }}-v2
|
||||
key: llvm-build-22-${{ matrix.os }}-v1
|
||||
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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Build TinyGo (LLVM ${{ matrix.version }})
|
||||
run: go install -tags=llvm${{ matrix.version }}
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
env:
|
||||
# Oldest versions currently supported by TinyGo
|
||||
LLVM: "15"
|
||||
Go: "1.24" # when updating this, also update minorMin in builder/config.go
|
||||
Go: "1.25" # 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-20-linux-compat
|
||||
key: llvm-source-22-linux-compat
|
||||
path: llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
|
||||
+13
-13
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
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.26-alpine
|
||||
image: golang:1.27rc2-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-20-linux-alpine-v2
|
||||
key: llvm-source-22-linux-alpine-v1
|
||||
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-20-linux-alpine-v2
|
||||
key: llvm-build-22-linux-alpine-v1
|
||||
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-v2
|
||||
key: binaryen-linux-alpine-v3
|
||||
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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Install wasmtime
|
||||
uses: bytecodealliance/actions/wasmtime/setup@v1
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -220,7 +220,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-linux-asserts-v1
|
||||
key: llvm-source-22-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-20-linux-asserts-v1
|
||||
key: llvm-build-22-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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Restore LLVM source cache
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-linux-v1
|
||||
key: llvm-source-22-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-20-linux-${{ matrix.goarch }}-v1
|
||||
key: llvm-build-22-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 }}-v4
|
||||
key: binaryen-linux-${{ matrix.goarch }}-v5
|
||||
path: build/wasm-opt
|
||||
- name: Build Binaryen
|
||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-linux-nix-v1
|
||||
key: llvm-source-22-linux-nix-v1
|
||||
path: |
|
||||
llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
|
||||
@@ -19,6 +19,10 @@ 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:
|
||||
@@ -30,7 +34,7 @@ jobs:
|
||||
uses: actions/cache@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-sizediff-v1
|
||||
key: llvm-source-22-sizediff-v1
|
||||
path: |
|
||||
llvm-project/compiler-rt
|
||||
- name: Download LLVM source
|
||||
|
||||
@@ -34,13 +34,13 @@ jobs:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Restore cached LLVM source
|
||||
uses: actions/cache/restore@v5
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-20-windows-v3
|
||||
key: llvm-source-22-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-20-windows-v5
|
||||
key: llvm-build-22-windows-v1
|
||||
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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
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.26.4'
|
||||
go-version: '1.27.0-rc.2'
|
||||
cache: true
|
||||
- name: Download TinyGo build
|
||||
uses: actions/download-artifact@v8
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
# tinygo-llvm stage obtains the llvm source for TinyGo
|
||||
FROM golang:1.26 AS tinygo-llvm
|
||||
FROM golang:1.27rc2 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.26 AS tinygo-compiler
|
||||
FROM golang:1.27rc2 AS tinygo-compiler
|
||||
|
||||
# Copy tinygo build.
|
||||
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
|
||||
|
||||
+31
-12
@@ -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 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 dtlto 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
|
||||
@@ -172,7 +172,7 @@ endif
|
||||
MD5SUM ?= md5sum
|
||||
|
||||
# Libraries that should be linked in for the statically linked Clang.
|
||||
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_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_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
||||
|
||||
# Libraries that should be linked in for the statically linked LLD.
|
||||
@@ -180,7 +180,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 = LLVMInterpreter LLVMMCA LLVMRISCVTargetMCA LLVMX86TargetMCA
|
||||
EXTRA_LIB_NAMES = LLVMDTLTO 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 +272,7 @@ gen-device-renesas: build/gen-device-svd
|
||||
GO111MODULE=off $(GO) fmt ./src/device/renesas
|
||||
|
||||
$(LLVM_PROJECTDIR)/llvm:
|
||||
git clone -b tinygo_20.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
git clone -b tinygo_22.x --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
llvm-source: $(LLVM_PROJECTDIR)/llvm ## Get LLVM sources
|
||||
|
||||
# Configure LLVM.
|
||||
@@ -319,9 +319,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 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 llvm22 osusergo" .
|
||||
test: check-nodejs-version
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm osusergo" $(GOTESTPKGS)
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags "byollvm llvm22 osusergo" $(GOTESTPKGS)
|
||||
|
||||
# Standard library packages that pass tests on darwin, linux, wasi, and windows, but take over a minute in wasi
|
||||
TEST_PACKAGES_SLOW = \
|
||||
@@ -479,6 +479,11 @@ 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.$$$$))
|
||||
@@ -539,12 +544,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) ./tests/runtime_wasi
|
||||
$(TINYGO) test -target=wasip1 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./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) ./tests/runtime_wasi
|
||||
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi
|
||||
|
||||
tinygo-test-wasip2-sum-slow:
|
||||
TINYGO=$(TINYGO) \
|
||||
@@ -574,13 +579,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 -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 llvm22" -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 -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 llvm22" -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 -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 llvm22" -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 -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 llvm22" -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasip2
|
||||
|
||||
.PHONY: testchdir
|
||||
testchdir:
|
||||
@@ -652,6 +657,8 @@ 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
|
||||
@@ -802,6 +809,8 @@ 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
|
||||
@@ -840,8 +849,14 @@ 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
|
||||
@@ -883,6 +898,8 @@ 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
|
||||
@@ -967,6 +984,8 @@ 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
|
||||
|
||||
+61
-3
@@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
@@ -36,6 +37,7 @@ func TestClangAttributes(t *testing.T) {
|
||||
"nintendoswitch",
|
||||
"riscv-qemu",
|
||||
"tkey",
|
||||
"uefi-amd64",
|
||||
"wasip1",
|
||||
"wasip2",
|
||||
"wasm",
|
||||
@@ -128,8 +130,10 @@ func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
||||
defer mod.Dispose()
|
||||
|
||||
// Check whether the LLVM target matches.
|
||||
if mod.Target() != config.Triple() {
|
||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Check the "target-cpu" and "target-features" string attribute of the add
|
||||
@@ -153,11 +157,65 @@ 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.
|
||||
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
+29
-3
@@ -204,7 +204,18 @@ var avrBuiltins = []string{
|
||||
|
||||
// Builtins needed specifically for windows/386.
|
||||
var windowsI386Builtins = []string{
|
||||
"i386/chkstk.S", // also _alloca
|
||||
"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",
|
||||
}
|
||||
|
||||
// libCompilerRT is a library with symbols required by programs compiled with
|
||||
@@ -233,13 +244,28 @@ var libCompilerRT = Library{
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
case "avr":
|
||||
builtins = append(builtins, avrBuiltins...)
|
||||
case "x86_64", "aarch64", "riscv64": // any 64-bit arch
|
||||
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":
|
||||
builtins = append(builtins, genericBuiltins128...)
|
||||
case "i386":
|
||||
if strings.Split(target, "-")[2] == "windows" {
|
||||
if isWindowsTriple(target) {
|
||||
builtins = append(builtins, windowsI386Builtins...)
|
||||
}
|
||||
}
|
||||
return builtins, nil
|
||||
},
|
||||
}
|
||||
|
||||
func isWindowsTriple(target string) bool {
|
||||
parts := strings.Split(target, "-")
|
||||
return len(parts) > 2 && parts[2] == "windows"
|
||||
}
|
||||
|
||||
+17
-17
@@ -23,7 +23,7 @@
|
||||
#include "clang/Basic/Diagnostic.h"
|
||||
#include "clang/Basic/DiagnosticOptions.h"
|
||||
#include "clang/Driver/DriverDiagnostic.h"
|
||||
#include "clang/Driver/Options.h"
|
||||
#include "clang/Options/Options.h"
|
||||
#include "clang/Frontend/FrontendDiagnostic.h"
|
||||
#include "clang/Frontend/TextDiagnosticPrinter.h"
|
||||
#include "clang/Frontend/Utils.h"
|
||||
@@ -35,6 +35,7 @@
|
||||
#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"
|
||||
@@ -67,8 +68,7 @@
|
||||
#include <optional>
|
||||
#include <system_error>
|
||||
using namespace clang;
|
||||
using namespace clang::driver;
|
||||
using namespace clang::driver::options;
|
||||
using namespace clang::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::normalize(Args.getLastArgValue(OPT_triple));
|
||||
Opts.Triple = llvm::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.empty())
|
||||
Opts.Triple = llvm::sys::getDefaultTargetTriple();
|
||||
if (Opts.Triple.getTriple().empty())
|
||||
Opts.Triple = llvm::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;
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
|
||||
|
||||
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(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
|
||||
MCContext Ctx(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) {
|
||||
MCInstPrinter *IP = TheTarget->createMCInstPrinter(
|
||||
llvm::Triple(Opts.Triple), Opts.OutputAsmVariant, *MAI, *MCII, *MRI);
|
||||
std::unique_ptr<MCInstPrinter> IP(TheTarget->createMCInstPrinter(
|
||||
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), IP,
|
||||
Str.reset(TheTarget->createAsmStreamer(Ctx, std::move(FOut), std::move(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;
|
||||
Failed = Diags.Report(diag::err_target_unknown_triple) << Opts.Triple.str();
|
||||
|
||||
// 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.
|
||||
IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts = new DiagnosticOptions();
|
||||
DiagnosticOptions DiagOpts;
|
||||
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(driver::options::CC1AsOption));
|
||||
llvm::opt::Visibility(clang::options::CC1AsOption));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@ struct AssemblerInvocation {
|
||||
/// @{
|
||||
|
||||
/// The name of the target triple to assemble for.
|
||||
std::string Triple;
|
||||
llvm::Triple Triple;
|
||||
|
||||
/// If given, the name of the target CPU to determine which instructions
|
||||
/// are legal.
|
||||
@@ -142,7 +142,7 @@ struct AssemblerInvocation {
|
||||
|
||||
public:
|
||||
AssemblerInvocation() {
|
||||
Triple = "";
|
||||
Triple = llvm::Triple();
|
||||
NoInitialTextSection = 0;
|
||||
InputFile = "-";
|
||||
OutputPath = "-";
|
||||
|
||||
+4
-4
@@ -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
|
||||
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);
|
||||
clang::DiagnosticOptions DiagOpts;
|
||||
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(*llvm::vfs::getRealFileSystem());
|
||||
Clang->createDiagnostics();
|
||||
if (!Clang->hasDiagnostics()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
+2
-2
@@ -25,8 +25,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
}
|
||||
|
||||
// Version range supported by TinyGo.
|
||||
const minorMin = 24 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 26
|
||||
const minorMin = 25 // when updating the min version, also update .github/workflows/compat.yml
|
||||
const minorMax = 27
|
||||
|
||||
// Check that we support this Go toolchain version.
|
||||
gorootMajor, gorootMinor, err := goenv.GetGorootVersion()
|
||||
|
||||
+132
-9
@@ -65,15 +65,6 @@ 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.
|
||||
@@ -88,6 +79,86 @@ 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
|
||||
@@ -191,6 +262,58 @@ 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 {
|
||||
|
||||
+1
-1
@@ -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="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
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)
|
||||
resourceDir := goenv.ClangResourceDir(false)
|
||||
if resourceDir != "" {
|
||||
args = append(args, "-resource-dir="+resourceDir)
|
||||
|
||||
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
|
||||
// This is a small number of very diverse targets that we want to test.
|
||||
tests := []sizeTest{
|
||||
// microcontrollers
|
||||
{"hifive1b", "examples/echo", 3771, 309, 0, 2260},
|
||||
{"microbit", "examples/serial", 2832, 368, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 8065, 1663, 132, 7488},
|
||||
{"hifive1b", "examples/echo", 4313, 323, 0, 2260},
|
||||
{"microbit", "examples/serial", 2838, 382, 8, 2256},
|
||||
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488},
|
||||
|
||||
// TODO: also check wasm. Right now this is difficult, because
|
||||
// wasm binaries are run through wasm-opt and therefore the
|
||||
|
||||
+10
-4
@@ -330,10 +330,8 @@ func (f *cgoFile) createASTNode(name string, c clangCursor) (ast.Node, any) {
|
||||
NamePos: pos,
|
||||
Name: typeName,
|
||||
},
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
}
|
||||
if underlyingType.kind != C.CXType_Enum {
|
||||
typeSpec.Assign = pos
|
||||
Assign: pos,
|
||||
Type: f.makeASTType(underlyingType, pos),
|
||||
}
|
||||
return typeSpec, nil
|
||||
case C.CXCursor_VarDecl:
|
||||
@@ -806,6 +804,14 @@ 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,4 +1,4 @@
|
||||
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19
|
||||
//go:build !byollvm && !llvm15 && !llvm16 && !llvm17 && !llvm18 && !llvm19 && !llvm21 && !llvm22
|
||||
|
||||
package cgo
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
//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"
|
||||
@@ -0,0 +1,15 @@
|
||||
//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"
|
||||
@@ -61,13 +61,15 @@ 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 == "" {
|
||||
return c.Options.LLVMFeatures
|
||||
features = c.Options.LLVMFeatures
|
||||
} else if c.Options.LLVMFeatures == "" {
|
||||
features = c.Target.Features
|
||||
} else {
|
||||
features = c.Target.Features + "," + c.Options.LLVMFeatures
|
||||
}
|
||||
if c.Options.LLVMFeatures == "" {
|
||||
return c.Target.Features
|
||||
}
|
||||
return c.Target.Features + "," + c.Options.LLVMFeatures
|
||||
return patchFeatures(features)
|
||||
}
|
||||
|
||||
// ABI returns the -mabi= flag for this target (like -mabi=lp64). A zero-length
|
||||
@@ -347,7 +349,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="+c.Triple())
|
||||
cflags = append(cflags, "--target="+ClangTriple(c.Triple()))
|
||||
// Set the -mcpu (or similar) flag.
|
||||
if c.Target.CPU != "" {
|
||||
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
//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
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//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)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//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
|
||||
}
|
||||
@@ -475,10 +475,12 @@ 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",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
//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)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
//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
|
||||
}
|
||||
@@ -35,6 +35,9 @@ 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
|
||||
@@ -102,6 +105,18 @@ 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)
|
||||
@@ -115,6 +130,38 @@ func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType
|
||||
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
|
||||
|
||||
+22
-38
@@ -30,17 +30,15 @@ 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 valueAlloca, valueAllocaSize llvm.Value
|
||||
var storage valueStorage
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(b.dataPtrType)
|
||||
storage.ptr = llvm.ConstNull(b.dataPtrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
storage = b.getValueStorage(instr.X, "chan.value")
|
||||
}
|
||||
|
||||
// Allocate buffer for the channel operation.
|
||||
@@ -48,15 +46,13 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
channelOpAlloca, channelOpAllocaSize := b.createTemporaryAlloca(channelOp, "chan.op")
|
||||
|
||||
// Do the send.
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, valueAlloca, channelOpAlloca}, "")
|
||||
b.createRuntimeInvoke("chanSend", []llvm.Value{ch, storage.ptr, 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)
|
||||
if !isZeroSize {
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
b.endValueStorage(storage)
|
||||
}
|
||||
|
||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
@@ -66,37 +62,17 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
ch := b.getValue(unop.X, getPos(unop))
|
||||
|
||||
// Allocate memory to receive into.
|
||||
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")
|
||||
}
|
||||
result := b.createRuntimeValueResult(valueType, unop.CommaOk, true, "chan")
|
||||
|
||||
// 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, valueAlloca, channelOpAlloca}, "")
|
||||
var received llvm.Value
|
||||
if isZeroSize {
|
||||
received = llvm.ConstNull(valueType)
|
||||
} else {
|
||||
received = b.CreateLoad(valueType, valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(valueAlloca, valueAllocaSize)
|
||||
}
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, result.valuePtr, channelOpAlloca}, "")
|
||||
received := result.finish(b, commaOk, "chan.received")
|
||||
b.emitLifetimeEnd(channelOpAlloca, channelOpAllocaSize)
|
||||
|
||||
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
|
||||
}
|
||||
return received
|
||||
}
|
||||
|
||||
// createChanClose closes the given channel.
|
||||
@@ -170,9 +146,7 @@ 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.
|
||||
sendValue := b.getValue(state.Send, state.Pos)
|
||||
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
||||
b.CreateStore(sendValue, alloca)
|
||||
alloca := b.getSelectSendStorage(state.Send)
|
||||
selectState = b.CreateInsertValue(selectState, alloca, 1, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
@@ -280,7 +254,17 @@ 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)]
|
||||
typ := b.getLLVMType(expr.Type())
|
||||
return b.CreateLoad(typ, recvbuf, "")
|
||||
return b.loadFromStorage(recvbuf, expr.Type(), "select.received")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
+319
-55
@@ -155,6 +155,8 @@ 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
|
||||
@@ -193,6 +195,7 @@ 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),
|
||||
}
|
||||
}
|
||||
@@ -422,19 +425,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:
|
||||
case types.Int32, types.Uint32, types.UntypedRune:
|
||||
return c.ctx.Int32Type()
|
||||
case types.Int, types.Uint:
|
||||
case types.Int, types.Uint, types.UntypedInt:
|
||||
return c.intType
|
||||
case types.Int64, types.Uint64:
|
||||
return c.ctx.Int64Type()
|
||||
case types.Float32:
|
||||
return c.ctx.FloatType()
|
||||
case types.Float64:
|
||||
case types.Float64, types.UntypedFloat:
|
||||
return c.ctx.DoubleType()
|
||||
case types.Complex64:
|
||||
return c.ctx.StructType([]llvm.Type{c.ctx.FloatType(), c.ctx.FloatType()}, false)
|
||||
case types.Complex128:
|
||||
case types.Complex128, types.UntypedComplex:
|
||||
return c.ctx.StructType([]llvm.Type{c.ctx.DoubleType(), c.ctx.DoubleType()}, false)
|
||||
case types.String, types.UntypedString:
|
||||
return c.getLLVMRuntimeType("_string")
|
||||
@@ -1282,10 +1285,28 @@ 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 b.expandFormalParamType(llvmType, param.Name(), param.Type()) {
|
||||
for _, info := range paramInfos {
|
||||
param := b.llvmFn.Param(llvmParamIndex)
|
||||
param.SetName(info.name)
|
||||
fields = append(fields, param)
|
||||
@@ -1423,7 +1444,7 @@ func (b *builder) createFunction() {
|
||||
for _, phi := range b.phis {
|
||||
block := phi.ssa.Block()
|
||||
for i, edge := range phi.ssa.Edges {
|
||||
llvmVal := b.getValue(edge, getPos(phi.ssa))
|
||||
llvmVal := b.getCallArgument(edge, false)
|
||||
llvmBlock := b.blockInfo[block.Preds[i].Index].exit
|
||||
phi.llvm.AddIncoming([]llvm.Value{llvmVal}, []llvm.BasicBlock{llvmBlock})
|
||||
}
|
||||
@@ -1432,6 +1453,9 @@ 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)
|
||||
@@ -1523,10 +1547,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
b.diagnostics = append(b.diagnostics, err)
|
||||
b.locals[instr] = llvm.Undef(b.getLLVMType(instr.Type()))
|
||||
} else {
|
||||
b.locals[instr] = value
|
||||
if len(*instr.Referrers()) != 0 && b.NeedsStackObjects {
|
||||
b.trackExpr(instr, value)
|
||||
}
|
||||
b.setValue(instr, value)
|
||||
}
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
@@ -1546,10 +1567,8 @@ 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, key, value, instr.Pos())
|
||||
b.createMapUpdate(mapType.Key(), m, instr.Key, instr.Value, instr.Pos())
|
||||
case *ssa.Panic:
|
||||
value := b.getValue(instr.X, getPos(instr))
|
||||
b.createRuntimeInvoke("_panic", []llvm.Value{value}, "")
|
||||
@@ -1558,19 +1577,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
|
||||
if b.hasDeferFrame() {
|
||||
b.createRuntimeCall("destroyDeferFrame", []llvm.Value{b.deferFrame}, "")
|
||||
}
|
||||
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)
|
||||
}
|
||||
b.createReturn(instr.Results, getPos(instr))
|
||||
case *ssa.RunDefers:
|
||||
// Note where we're going to put the rundefers block
|
||||
run := b.insertBasicBlock("rundefers.block")
|
||||
@@ -1584,18 +1591,246 @@ 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")
|
||||
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
|
||||
llvmType := b.getLLVMType(instr.Val.Type())
|
||||
if b.targetData.TypeAllocSize(llvmType) == 0 {
|
||||
// nothing to store
|
||||
return
|
||||
}
|
||||
b.CreateStore(llvmVal, llvmAddr)
|
||||
b.storeValue(llvmAddr, instr.Val)
|
||||
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) {
|
||||
@@ -1981,7 +2216,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 := fn.RelString(nil)
|
||||
name := b.getFunctionInfo(fn).linkName
|
||||
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)
|
||||
@@ -2030,14 +2265,10 @@ 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)
|
||||
@@ -2067,19 +2298,18 @@ 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)
|
||||
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)
|
||||
invokeTypecode = b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||
invokeReceiver = b.CreateExtractValue(itf, 1, "invoke.func.value")
|
||||
callee = b.getInvokeFunction(instr)
|
||||
calleeType = callee.GlobalValueType()
|
||||
context = llvm.Undef(b.dataPtrType)
|
||||
@@ -2093,7 +2323,23 @@ 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)
|
||||
@@ -2132,6 +2378,9 @@ 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 {
|
||||
@@ -2214,8 +2463,15 @@ 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.
|
||||
x := b.getValue(expr.X, getPos(expr))
|
||||
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))
|
||||
if x.Type() == llvmType {
|
||||
// Different Go type but same LLVM type (for example, named int).
|
||||
// This is the common case.
|
||||
@@ -2245,9 +2501,15 @@ 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
|
||||
@@ -2270,11 +2532,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?")
|
||||
@@ -2308,13 +2570,12 @@ 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 := collection.Type()
|
||||
alloca, allocaSize := b.createTemporaryAlloca(arrayType, "index.alloca")
|
||||
b.CreateStore(collection, alloca)
|
||||
arrayType := b.getLLVMType(expr.X.Type())
|
||||
storage := b.getValueStorage(expr.X, "index.alloca")
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
ptr := b.CreateInBoundsGEP(arrayType, alloca, []llvm.Value{zero, index}, "index.gep")
|
||||
result := b.CreateLoad(arrayType.ElementType(), ptr, "index.load")
|
||||
b.emitLifetimeEnd(alloca, allocaSize)
|
||||
ptr := b.CreateInBoundsGEP(arrayType, storage.ptr, []llvm.Value{zero, index}, "index.gep")
|
||||
result := b.loadFromStorage(ptr, expr.Type(), "index.load")
|
||||
b.endValueStorage(storage)
|
||||
return result, nil
|
||||
default:
|
||||
panic("unknown *ssa.Index type")
|
||||
@@ -2373,17 +2634,21 @@ 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, index, expr.CommaOk, expr.Pos())
|
||||
return b.createMapLookup(expr.X.Type().Underlying().(*types.Map).Key(), valueType, value, expr.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:
|
||||
@@ -2417,8 +2682,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
}
|
||||
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
|
||||
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
|
||||
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
|
||||
slicePtr := b.createAlloc(sliceSize, layoutValue, elemAlign, "makeslice.buf")
|
||||
|
||||
// Extend or truncate if necessary. This is safe as we've already done
|
||||
// the bounds check.
|
||||
@@ -2451,7 +2715,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
|
||||
return b.createMapIteratorNext(rangeVal, llvmRangeVal, it), nil
|
||||
}
|
||||
case *ssa.Phi:
|
||||
phi := b.CreatePHI(b.getLLVMType(expr.Type()), "")
|
||||
phiType := b.storedParamType(b.getLLVMType(expr.Type()), false)
|
||||
phi := b.CreatePHI(phiType, "")
|
||||
b.phis = append(b.phis, phiNode{expr, phi})
|
||||
return phi, nil
|
||||
case *ssa.Range:
|
||||
@@ -3454,8 +3719,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
|
||||
return fn, nil
|
||||
} else {
|
||||
b.createNilCheck(unop.X, x, "deref")
|
||||
load := b.CreateLoad(valueType, x, "")
|
||||
return load, nil
|
||||
return b.loadFromStorage(x, unop.Type(), ""), nil
|
||||
}
|
||||
case token.XOR: // ^x, toggle all bits in integer
|
||||
return b.CreateXor(x, llvm.ConstInt(x.Type(), ^uint64(0), false), ""), nil
|
||||
|
||||
+172
-16
@@ -4,6 +4,7 @@ import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -51,6 +52,7 @@ func TestCompiler(t *testing.T) {
|
||||
{"gc.go", "", ""},
|
||||
{"zeromap.go", "", ""},
|
||||
{"generics.go", "", ""},
|
||||
{"large.go", "", ""},
|
||||
}
|
||||
if goMinor >= 20 {
|
||||
tests = append(tests, testCase{"go1.20.go", "", ""})
|
||||
@@ -58,6 +60,9 @@ 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
|
||||
@@ -122,30 +127,138 @@ func TestCompiler(t *testing.T) {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
|
||||
if !fuzzyEqualIR(mod.String(), string(expected)) {
|
||||
t.Errorf("output does not match expected output:\n%s", mod.String())
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
for i, line1 := range lines1 {
|
||||
line2 := lines2[i]
|
||||
if line1 != line2 {
|
||||
return false
|
||||
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
|
||||
}
|
||||
defer mod.Dispose()
|
||||
|
||||
passOptions := llvm.NewPassBuilderOptions()
|
||||
defer passOptions.Dispose()
|
||||
if err := mod.RunPasses("default<O2>", llvm.TargetMachine{}, passOptions); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return true
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
@@ -214,6 +327,49 @@ 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)
|
||||
|
||||
+88
-85
@@ -32,7 +32,7 @@ func (b *builder) supportsRecover() bool {
|
||||
// proposal of WebAssembly:
|
||||
// https://github.com/WebAssembly/exception-handling
|
||||
return false
|
||||
case "riscv64", "xtensa":
|
||||
case "xtensa":
|
||||
// TODO: add support for these architectures
|
||||
return false
|
||||
default:
|
||||
@@ -217,12 +217,20 @@ 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":
|
||||
asmString = `
|
||||
case "riscv32", "riscv64":
|
||||
if b.archFamily() == "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().
|
||||
@@ -359,6 +367,44 @@ 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) {
|
||||
@@ -366,31 +412,28 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// make a linked list.
|
||||
next := b.CreateLoad(b.dataPtrType, b.deferPtr, "defer.next")
|
||||
|
||||
var values []llvm.Value
|
||||
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
||||
var values llvmValueList
|
||||
lowerArgument := func(value ssa.Value) llvm.Value {
|
||||
return b.getCallArgument(value, false)
|
||||
}
|
||||
if instr.Call.IsInvoke() {
|
||||
// Method call on an interface.
|
||||
|
||||
// Get callback type number.
|
||||
methodName := instr.Call.Method.FullName()
|
||||
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
|
||||
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
|
||||
key := b.getInvokeFunctionName(&instr.Call)
|
||||
if _, ok := b.deferInvokeFuncs[key]; !ok {
|
||||
b.deferInvokeFuncs[key] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
||||
}
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[key]), 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 = []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())
|
||||
}
|
||||
values = newLLVMValueList(callback, next, typecode, receiverValue)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
|
||||
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
||||
// Regular function call.
|
||||
@@ -402,12 +445,11 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
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 = newLLVMValueList(callback, next)
|
||||
exported := b.getFunctionInfo(callee).exported
|
||||
values.appendSSAValues(instr.Call.Args, func(value ssa.Value) llvm.Value {
|
||||
return b.getCallArgument(value, exported)
|
||||
})
|
||||
|
||||
} else if makeClosure, ok := instr.Call.Value.(*ssa.MakeClosure); ok {
|
||||
// Immediately applied function literal with free variables.
|
||||
@@ -430,14 +472,9 @@ 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 = []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())
|
||||
values = newLLVMValueList(callback, next)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
values.append(context)
|
||||
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
var argTypes []types.Type
|
||||
@@ -460,11 +497,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range argValues {
|
||||
values = append(values, param)
|
||||
valueTypes = append(valueTypes, param.Type())
|
||||
}
|
||||
values = newLLVMValueList(callback, next)
|
||||
values.append(argValues...)
|
||||
|
||||
} else {
|
||||
funcValue := b.getValue(instr.Call.Value, getPos(instr))
|
||||
@@ -479,20 +513,15 @@ 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 = []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())
|
||||
}
|
||||
values = newLLVMValueList(callback, next, funcValue)
|
||||
values.appendSSAValues(instr.Call.Args, lowerArgument)
|
||||
}
|
||||
|
||||
// Make a struct out of the collected values to put in the deferred call
|
||||
// struct.
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
deferredCallType := b.ctx.StructType(values.types, false)
|
||||
deferredCall := llvm.ConstNull(deferredCallType)
|
||||
for i, value := range values {
|
||||
for i, value := range values.values {
|
||||
deferredCall = b.CreateInsertValue(deferredCall, value, i, "")
|
||||
}
|
||||
|
||||
@@ -508,8 +537,9 @@ 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)
|
||||
nilPtr := llvm.ConstNull(b.dataPtrType)
|
||||
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
|
||||
layoutValue := b.createObjectLayout(deferredCallType, instr.Pos())
|
||||
align := b.targetData.ABITypeAlignment(deferredCallType)
|
||||
alloca = b.createAlloc(sizeValue, layoutValue, align, "defer.alloc.call")
|
||||
}
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
@@ -593,19 +623,11 @@ func (b *builder) createRunDefers() {
|
||||
valueTypes = append(valueTypes, b.dataPtrType, b.dataPtrType)
|
||||
}
|
||||
|
||||
for _, arg := range callback.Args {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
||||
}
|
||||
valueTypes = b.appendStoredValueTypes(valueTypes, callback.Args, false)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
}
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
|
||||
var fnPtr llvm.Value
|
||||
var fnType llvm.Type
|
||||
@@ -634,6 +656,7 @@ 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, "")
|
||||
|
||||
@@ -642,27 +665,21 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{b.uintptrType, b.dataPtrType}
|
||||
for _, param := range getParams(callback.Signature) {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||
}
|
||||
exported := b.getFunctionInfo(callback).exported
|
||||
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(callback.Signature), exported)
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
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)
|
||||
}
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
|
||||
// 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 !b.getFunctionInfo(callback).exported {
|
||||
if !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)
|
||||
@@ -672,24 +689,16 @@ 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}
|
||||
params := fn.Signature.Params()
|
||||
for v := range params.Variables() {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(v.Type()))
|
||||
}
|
||||
valueTypes = b.appendStoredParamTypes(valueTypes, getParams(fn.Signature), false)
|
||||
valueTypes = append(valueTypes, b.dataPtrType) // closure
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
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)
|
||||
}
|
||||
forwardParams := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
|
||||
// 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]
|
||||
@@ -706,13 +715,7 @@ func (b *builder) createRunDefers() {
|
||||
deferredCallType := b.ctx.StructType(valueTypes, false)
|
||||
|
||||
// Extract the params from the struct.
|
||||
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)
|
||||
}
|
||||
argValues := b.loadDeferredCallParams(deferredCallType, deferData)
|
||||
|
||||
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
||||
if err != nil {
|
||||
|
||||
+95
-20
@@ -10,6 +10,91 @@ 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 {
|
||||
@@ -48,28 +133,18 @@ 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 {
|
||||
// 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)
|
||||
}
|
||||
returnType, indirectResult := c.hasIndirectResult(typ)
|
||||
|
||||
// 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" {
|
||||
@@ -112,7 +187,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)
|
||||
context := b.emitPointerPack(boundVars, expr.Pos())
|
||||
|
||||
// Create the closure.
|
||||
_, fn := b.getFunction(f)
|
||||
|
||||
+3
-3
@@ -29,10 +29,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 {
|
||||
// TODO: make sure all callsites set the correct alignment.
|
||||
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
if align == 0 || align&(align-1) != 0 {
|
||||
panic("invalid alignment")
|
||||
}
|
||||
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
|
||||
|
||||
return call
|
||||
}
|
||||
|
||||
+26
-12
@@ -47,20 +47,16 @@ 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.
|
||||
@@ -73,10 +69,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))
|
||||
@@ -84,23 +80,32 @@ func (b *builder) createGo(instr *ssa.Go) {
|
||||
itfValue := b.CreateExtractValue(itf, 1, "")
|
||||
funcPtr = b.getInvokeFunction(&instr.Call)
|
||||
funcType = funcPtr.GlobalValueType()
|
||||
params = append([]llvm.Value{itfValue}, params...) // start with receiver
|
||||
params = append(params, itfTypeCode) // end with typecode
|
||||
params = append(params, itfValue)
|
||||
context = itfTypeCode
|
||||
} 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
|
||||
}
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
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())
|
||||
var stackSize llvm.Value
|
||||
callee := b.createGoroutineStartWrapper(funcType, funcPtr, prefix, hasContext, false, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
@@ -122,6 +127,15 @@ 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.
|
||||
|
||||
+111
-134
@@ -84,7 +84,11 @@ 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})
|
||||
itfValue := b.emitPointerPack([]llvm.Value{val}, pos)
|
||||
return b.createMakeInterfaceFromPointer(itfValue, typ)
|
||||
}
|
||||
|
||||
func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value {
|
||||
itfType := b.getTypeCode(typ)
|
||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||
itf = b.CreateInsertValue(itf, itfType, 0, "")
|
||||
@@ -98,9 +102,16 @@ func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.
|
||||
// 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 != "" {
|
||||
@@ -125,6 +136,16 @@ 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
|
||||
@@ -134,22 +155,26 @@ 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() {
|
||||
numMethods++
|
||||
}
|
||||
}
|
||||
if isInterface {
|
||||
hasMethodSet = false
|
||||
}
|
||||
|
||||
// Short-circuit all the global pointer logic here for pointers to pointers.
|
||||
if typ, ok := typ.(*types.Pointer); ok {
|
||||
@@ -194,7 +219,11 @@ 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() {
|
||||
methods = append(methods, method.Obj().(*types.Func))
|
||||
fn := method.Obj().(*types.Func)
|
||||
if isGenericMethod(fn) {
|
||||
continue
|
||||
}
|
||||
methods = append(methods, fn)
|
||||
}
|
||||
methodSetType := types.NewStruct([]*types.Var{
|
||||
types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]),
|
||||
@@ -599,8 +628,21 @@ 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() {
|
||||
// Package-scope or builtin: the printed name is unique.
|
||||
return "named:" + t.String(), false
|
||||
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
|
||||
}
|
||||
if tn.Parent() != nil {
|
||||
// Ordinary function-local type. Use the un-//line-adjusted
|
||||
@@ -707,8 +749,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 RelString as prefix. RelString encodes the type
|
||||
// same source position, so each is named with the enclosing instance's
|
||||
// canonical function name as prefix. The function name 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.
|
||||
@@ -717,9 +759,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 (RelString and the raw token.Pos used as a
|
||||
// sort key), so any package compiling the same instance produces the
|
||||
// same identifier.
|
||||
// intrinsic SSA properties (the canonical function name and raw token.Pos),
|
||||
// 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
|
||||
@@ -807,9 +849,8 @@ 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 fn.RelString as the
|
||||
// owning function plus a per-function counter assigned in source
|
||||
// order.
|
||||
// nil) in c.localTypeNames. Each is named with the canonical function
|
||||
// name 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
|
||||
@@ -920,7 +961,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 := fn.RelString(nil)
|
||||
enclosing := c.canonicalFunctionName(fn)
|
||||
for i, named := range found {
|
||||
c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i))
|
||||
}
|
||||
@@ -929,7 +970,8 @@ 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 {
|
||||
globalName := typ.String() + "$methodset"
|
||||
typeName, _ := c.getTypeCodeName(typ)
|
||||
globalName := typeName + "$methodset"
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
ms := c.program.MethodSets.MethodSet(typ)
|
||||
@@ -937,6 +979,9 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
// Create method set.
|
||||
var signatures, wrappers []llvm.Value
|
||||
for method := range ms.Methods() {
|
||||
if isGenericMethod(method.Obj().(*types.Func)) {
|
||||
continue
|
||||
}
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
signatures = append(signatures, signatureGlobal)
|
||||
fn := c.program.MethodValue(method)
|
||||
@@ -951,7 +996,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
|
||||
// Construct global value.
|
||||
globalValue := c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false),
|
||||
llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false),
|
||||
llvm.ConstArray(c.dataPtrType, signatures),
|
||||
c.ctx.ConstStruct(wrappers, false),
|
||||
}, false)
|
||||
@@ -967,14 +1012,15 @@ 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 {
|
||||
signature := methodSignature(method)
|
||||
var globalName string
|
||||
name := method.Name()
|
||||
var prefix string
|
||||
if token.IsExported(method.Name()) {
|
||||
globalName = "reflect/methods." + signature
|
||||
prefix = "reflect/methods."
|
||||
} else {
|
||||
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
|
||||
prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods."
|
||||
}
|
||||
return globalName
|
||||
signature, _ := c.getTypeCodeName(method.Type())
|
||||
return prefix + name + ":" + signature
|
||||
}
|
||||
|
||||
// getMethodSignature returns a global variable which is a reference to an
|
||||
@@ -1053,6 +1099,21 @@ 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
|
||||
@@ -1179,8 +1240,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
|
||||
fnName := s + "." + instr.Method.Name() + "$invoke"
|
||||
fnName := c.getInvokeFunctionName(instr)
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
sig := instr.Method.Type().(*types.Signature)
|
||||
@@ -1193,12 +1253,20 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
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
|
||||
@@ -1233,6 +1301,7 @@ 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)
|
||||
}
|
||||
@@ -1247,7 +1316,13 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||
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):]...)
|
||||
wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||
c.addStandardAttributes(wrapper)
|
||||
@@ -1273,8 +1348,15 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType
|
||||
block := b.ctx.AddBasicBlock(wrapper, "entry")
|
||||
b.SetInsertPointAtEnd(block)
|
||||
|
||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
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:]...)
|
||||
if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateCall(llvmFnType, llvmFn, params, "")
|
||||
b.CreateRetVoid()
|
||||
@@ -1285,108 +1367,3 @@ 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 {
|
||||
var s strings.Builder
|
||||
if sig.Params().Len() == 0 {
|
||||
s.WriteString("()")
|
||||
} else {
|
||||
s.WriteString("(")
|
||||
i := 0
|
||||
for v := range sig.Params().Variables() {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
s.WriteString(typestring(v.Type()))
|
||||
i++
|
||||
}
|
||||
s.WriteString(")")
|
||||
}
|
||||
if sig.Results().Len() == 0 {
|
||||
// keep as-is
|
||||
} else if sig.Results().Len() == 1 {
|
||||
s.WriteString(" " + typestring(sig.Results().At(0).Type()))
|
||||
} else {
|
||||
s.WriteString(" (")
|
||||
i := 0
|
||||
for v := range sig.Results().Variables() {
|
||||
if i > 0 {
|
||||
s.WriteString(", ")
|
||||
}
|
||||
s.WriteString(typestring(v.Type()))
|
||||
i++
|
||||
}
|
||||
s.WriteString(")")
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -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) llvm.Value {
|
||||
func (b *builder) emitPointerPack(values []llvm.Value, pos token.Pos) llvm.Value {
|
||||
valueTypes := make([]llvm.Type, len(values))
|
||||
for i, value := range values {
|
||||
valueTypes[i] = value.Type()
|
||||
@@ -128,8 +128,9 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
layoutValue := b.createObjectLayout(packedType, pos)
|
||||
align := b.targetData.ABITypeAlignment(packedType)
|
||||
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
|
||||
packedAlloc := b.createAlloc(sizeValue, layoutValue, align, "")
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(packedAlloc)
|
||||
}
|
||||
|
||||
@@ -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, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(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, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(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, []llvm.Value{size, alloca}, "")
|
||||
builder.CreateCall(fnType, fn, lifetimeCallArgs(size, alloca), "")
|
||||
return alloca
|
||||
}
|
||||
|
||||
@@ -74,7 +74,27 @@ 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, []llvm.Value{size, ptr}, "")
|
||||
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)
|
||||
}
|
||||
|
||||
// getLifetimeStartFunc returns the llvm.lifetime.start intrinsic and creates it
|
||||
@@ -84,7 +104,7 @@ func getLifetimeStartFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -98,7 +118,7 @@ func getLifetimeEndFunc(mod llvm.Module) (llvm.Type, llvm.Value) {
|
||||
fn := mod.NamedFunction(fnName)
|
||||
ctx := mod.Context()
|
||||
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||
fnType := llvm.FunctionType(ctx.VoidType(), []llvm.Type{ctx.Int64Type(), ptrType}, false)
|
||||
fnType := lifetimeFuncType(ctx, ptrType)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(mod, fnName, fnType)
|
||||
}
|
||||
@@ -218,6 +238,36 @@ 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 {
|
||||
|
||||
+17
-30
@@ -72,19 +72,20 @@ 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, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
func (b *builder) createMapLookup(keyType, valueType types.Type, m llvm.Value, key ssa.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.
|
||||
mapValueAlloca, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
result := b.createRuntimeValueResult(llvmValueType, commaOk, false, "hashmap")
|
||||
mapValueAlloca := result.valuePtr
|
||||
|
||||
// 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 := mapValueAllocaSize
|
||||
mapValueSize := result.valueSize
|
||||
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
||||
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
|
||||
}
|
||||
@@ -94,60 +95,46 @@ func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Val
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, mapValueAlloca, mapValueSize}
|
||||
params := []llvm.Value{m, b.getValue(key, getPos(key)), mapValueAlloca, mapValueSize}
|
||||
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type: either binary-comparable or with
|
||||
// compiler-generated hash/equal.
|
||||
mapKeyAlloca, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, mapKeyAlloca)
|
||||
params := []llvm.Value{m, mapKeyAlloca, mapValueAlloca, mapValueSize}
|
||||
mapKey := b.getValueStorage(key, "hashmap.key")
|
||||
params := []llvm.Value{m, mapKey.ptr, mapValueAlloca, mapValueSize}
|
||||
fnName := "hashmapBinaryGet"
|
||||
if !hashmapIsBinaryKey(keyType) {
|
||||
fnName = "hashmapGenericGet"
|
||||
}
|
||||
commaOkValue = b.createRuntimeCall(fnName, params, "")
|
||||
b.emitLifetimeEnd(mapKeyAlloca, mapKeySize)
|
||||
b.endValueStorage(mapKey)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// The value is set to the zero value if the key doesn't exist.
|
||||
return result.finish(b, commaOkValue, ""), nil
|
||||
}
|
||||
|
||||
// createMapUpdate updates a map key to a given value, by creating an
|
||||
// appropriate runtime call.
|
||||
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)
|
||||
func (b *builder) createMapUpdate(keyType types.Type, m llvm.Value, key, value ssa.Value, pos token.Pos) {
|
||||
storedValue := b.getValueStorage(value, "hashmap.value")
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valueAlloca}
|
||||
params := []llvm.Value{m, b.getValue(key, getPos(key)), storedValue.ptr}
|
||||
b.createRuntimeInvoke("hashmapStringSet", params, "")
|
||||
} else {
|
||||
// Key stored at actual type.
|
||||
keyAlloca, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, keyAlloca)
|
||||
keyStorage := b.getValueStorage(key, "hashmap.key")
|
||||
fnName := "hashmapBinarySet"
|
||||
if !hashmapIsBinaryKey(keyType) {
|
||||
fnName = "hashmapGenericSet"
|
||||
}
|
||||
params := []llvm.Value{m, keyAlloca, valueAlloca}
|
||||
params := []llvm.Value{m, keyStorage.ptr, storedValue.ptr}
|
||||
b.createRuntimeInvoke(fnName, params, "")
|
||||
b.emitLifetimeEnd(keyAlloca, keySize)
|
||||
b.endValueStorage(keyStorage)
|
||||
}
|
||||
b.emitLifetimeEnd(valueAlloca, valueSize)
|
||||
b.endValueStorage(storedValue)
|
||||
}
|
||||
|
||||
// createMapDelete deletes a key from a map by calling the appropriate runtime
|
||||
|
||||
+48
-51
@@ -79,24 +79,27 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
||||
return llvmFn.GlobalValueType(), llvmFn
|
||||
}
|
||||
|
||||
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 v := range fn.Signature.Results().Variables() {
|
||||
results = append(results, c.getLLVMType(v.Type()))
|
||||
}
|
||||
retType = c.ctx.StructType(results, false)
|
||||
retType, indirectResult := c.hasIndirectResult(fn.Signature)
|
||||
if info.exported {
|
||||
indirectResult = 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())
|
||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
if info.exported {
|
||||
paramInfos = append(paramInfos, c.expandDirectFormalParamType(paramType, param.Name(), param.Type())...)
|
||||
} else {
|
||||
paramInfos = append(paramInfos, c.expandFormalParamType(paramType, param.Name(), param.Type())...)
|
||||
}
|
||||
}
|
||||
|
||||
// Add an extra parameter as the function context. This context is used in
|
||||
@@ -106,12 +109,20 @@ 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¶mIsIndirect != 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();
|
||||
@@ -141,7 +152,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("nocapture"), 0)
|
||||
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0)
|
||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
||||
}
|
||||
if paramInfo.flags¶mIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
|
||||
@@ -160,9 +171,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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 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.
|
||||
@@ -184,44 +195,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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.stringFromBytes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.stringFromRunes":
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
case "runtime.hashmapDelete":
|
||||
// The key (param 2) is read-only and never captured.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
case "runtime.hashmapGenericGet":
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(3, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
case "runtime.hashmapGenericDelete":
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 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("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(llvmutil.NoCaptureAttrName()), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "__mulsi3", "__divmodsi4", "__udivmodsi4":
|
||||
if strings.Split(c.Triple, "-")[0] == "avr" {
|
||||
@@ -256,7 +267,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("nocapture")
|
||||
nocaptureKind := llvm.AttributeKindID(llvmutil.NoCaptureAttrName())
|
||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||
for i, typ := range paramTypes {
|
||||
if typ.TypeKind() == llvm.PointerTypeKind {
|
||||
@@ -320,13 +331,7 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
}
|
||||
info := functionInfo{
|
||||
// Pick the default linkName.
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
|
||||
// RelString is not unique for local type arguments, so add a suffix
|
||||
// when needed.
|
||||
if suffix := c.localTypeArgsSuffix(f); suffix != "" {
|
||||
info.linkName += suffix
|
||||
linkName: c.canonicalFunctionName(f),
|
||||
}
|
||||
|
||||
// Check for a few runtime functions that are treated specially.
|
||||
@@ -353,24 +358,16 @@ func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
return info
|
||||
}
|
||||
|
||||
func (c *compilerContext) localTypeArgsSuffix(f *ssa.Function) string {
|
||||
func (c *compilerContext) canonicalFunctionName(f *ssa.Function) string {
|
||||
typeArgs := f.TypeArgs()
|
||||
if len(typeArgs) == 0 {
|
||||
return ""
|
||||
return f.RelString(nil)
|
||||
}
|
||||
var hasLocal bool
|
||||
parts := make([]string, len(typeArgs))
|
||||
for i, ta := range typeArgs {
|
||||
name, isLocal := c.getTypeCodeName(ta)
|
||||
if isLocal {
|
||||
hasLocal = true
|
||||
}
|
||||
parts[i] = name
|
||||
parts[i], _ = c.getTypeCodeName(ta)
|
||||
}
|
||||
if !hasLocal {
|
||||
return ""
|
||||
}
|
||||
return "$localtype:" + strings.Join(parts, ",")
|
||||
return f.Origin().RelString(nil) + "[" + strings.Join(parts, ",") + "]"
|
||||
}
|
||||
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
@@ -515,7 +512,7 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
case "//go:linkname":
|
||||
case "//go:linkname", "//go:linknamestd":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
|
||||
Vendored
+5
@@ -66,6 +66,11 @@ 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
|
||||
}
|
||||
|
||||
Vendored
+7
@@ -176,6 +176,13 @@ 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
@@ -278,7 +278,7 @@ entry:
|
||||
|
||||
for.body: ; preds = %for.body, %entry
|
||||
%defer.next = load ptr, ptr %deferPtr, align 4
|
||||
%defer.alloc.call = call dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
|
||||
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), 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 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr null, ptr undef) #4
|
||||
%defer.alloc.call = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 135 to ptr), 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
|
||||
|
||||
Vendored
+1
-1
@@ -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 null, ptr undef) #3
|
||||
%0 = call align 8 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 3 to ptr), 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
|
||||
|
||||
Vendored
+37
@@ -19,12 +19,49 @@ 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)
|
||||
}
|
||||
|
||||
Vendored
+70
-21
@@ -14,32 +14,62 @@ 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[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)
|
||||
%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
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
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 {
|
||||
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 {
|
||||
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) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%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
|
||||
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) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%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
|
||||
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) #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
|
||||
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
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
@@ -86,23 +116,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[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[basic: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) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %a, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%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
|
||||
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) #3
|
||||
call void @runtime.trackPointer(ptr nonnull %b, ptr nonnull %stackalloc, ptr undef) #3
|
||||
%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
|
||||
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) #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
|
||||
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
|
||||
br i1 false, label %deref.throw, label %deref.next
|
||||
|
||||
deref.next: ; preds = %entry
|
||||
@@ -141,7 +171,26 @@ 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 = { nounwind }
|
||||
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 }
|
||||
|
||||
Vendored
+24
@@ -0,0 +1,24 @@
|
||||
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)
|
||||
}
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
; 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
@@ -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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), 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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), 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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), 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(string)" "tinygo-methods"="reflect/methods.Print(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:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic: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
@@ -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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 133 to ptr), 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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(12) ptr @runtime.alloc(i32 12, ptr nonnull inttoptr (i32 391 to ptr), 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 null, ptr undef) #11
|
||||
%0 = call align 4 dereferenceable(16) ptr @runtime.alloc(i32 16, ptr nonnull inttoptr (i32 713 to ptr), 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(string)" "tinygo-methods"="reflect/methods.Print(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:func:{basic:string}{}" "tinygo-methods"="reflect/methods.Print:func:{basic: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 }
|
||||
|
||||
Vendored
+4
-4
@@ -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() 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 #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 #6 = { nounwind }
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
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]
|
||||
}
|
||||
Vendored
+120
@@ -0,0 +1,120 @@
|
||||
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]
|
||||
}
|
||||
}
|
||||
Vendored
+498
@@ -0,0 +1,498 @@
|
||||
; 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 }
|
||||
Vendored
+6
@@ -35,6 +35,12 @@ 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
|
||||
|
||||
Vendored
+4
-2
@@ -33,6 +33,8 @@ 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:
|
||||
@@ -48,12 +50,12 @@ entry:
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.useGeneric(ptr %context) unnamed_addr #1 {
|
||||
entry:
|
||||
call void @"main.noinlineGenericFunc[int8]"(ptr undef)
|
||||
call void @"main.noinlineGenericFunc[basic:int8]"(ptr undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: noinline nounwind
|
||||
define linkonce_odr hidden void @"main.noinlineGenericFunc[int8]"(ptr %context) unnamed_addr #4 {
|
||||
define linkonce_odr hidden void @"main.noinlineGenericFunc[basic:int8]"(ptr %context) unnamed_addr #4 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/tinygo-org/tinygo
|
||||
|
||||
go 1.24.0
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/aykevl/go-wasm v0.0.2-0.20250317121156-42b86c494139
|
||||
@@ -15,22 +15,21 @@ 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.6.4
|
||||
go.bug.st/serial v1.8.0
|
||||
go.bytecodealliance.org v0.6.2
|
||||
go.bytecodealliance.org/cm v0.2.2
|
||||
golang.org/x/net v0.50.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/tools v0.42.0
|
||||
golang.org/x/net v0.56.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/tools v0.47.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
tinygo.org/x/espflasher v0.6.1
|
||||
tinygo.org/x/go-llvm v0.0.0-20260422095634-06c6725fe5e6
|
||||
tinygo.org/x/espflasher v0.7.1
|
||||
tinygo.org/x/go-llvm v0.0.0-20260721072906-185673ef46a5
|
||||
)
|
||||
|
||||
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
|
||||
@@ -48,6 +47,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.33.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
)
|
||||
|
||||
@@ -8,8 +8,6 @@ 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=
|
||||
@@ -89,30 +87,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.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||
go.bug.st/serial v1.8.0 h1:ZtnmN8aYXtPlTghwSvDWPHKBHL9TM6oFDa+KpSn4SQE=
|
||||
go.bug.st/serial v1.8.0/go.mod h1:d0MmS16Qt9b1m06yoYRNUXhRRTJV5Qg2S5EKqQtnayQ=
|
||||
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.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/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/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.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=
|
||||
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=
|
||||
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=
|
||||
@@ -120,7 +118,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
tinygo.org/x/espflasher v0.6.1 h1:9jfyAP9jGjxF63FQUY2Bml9TFb5fCZYxVgbgR2IjUGs=
|
||||
tinygo.org/x/espflasher v0.6.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=
|
||||
tinygo.org/x/espflasher v0.7.1 h1:oJ+tt58QltP+c5+iHGRdCNNnVaXZ9yYXyWK/F747BVY=
|
||||
tinygo.org/x/espflasher v0.7.1/go.mod h1:a3l/4jOSMf9vhaPtEmGbCwtrC5oiZiNKx1i8jUkyyRI=
|
||||
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=
|
||||
|
||||
+17
-5
@@ -140,13 +140,25 @@ func (r *runner) compileFunction(llvmFn llvm.Value) *function {
|
||||
panic("unknown number of operands")
|
||||
}
|
||||
case llvm.Switch:
|
||||
// A switch is an array of (value, label) pairs, of which the
|
||||
// Compile to an array of (value, label) pairs, of which the
|
||||
// first one indicates the to-switch value and the default
|
||||
// label.
|
||||
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)])})
|
||||
//
|
||||
// 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()])},
|
||||
)
|
||||
}
|
||||
case llvm.PHI:
|
||||
inst.name = llvmInst.Name()
|
||||
|
||||
@@ -2,6 +2,7 @@ package interp
|
||||
|
||||
import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -92,6 +93,16 @@ 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) {
|
||||
@@ -107,6 +118,21 @@ 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
-4
@@ -931,10 +931,10 @@ func (v rawValue) toLLVMValue(llvmType llvm.Type, mem *memoryView) (llvm.Value,
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
}
|
||||
if llvmType.StructName() != "" {
|
||||
return llvm.ConstNamedStruct(llvmType, fields), nil
|
||||
}
|
||||
return llvmType.Context().ConstStruct(fields, false), nil
|
||||
// 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
|
||||
case llvm.ArrayTypeKind:
|
||||
numElements := llvmType.ArrayLength()
|
||||
childType := llvmType.ElementType()
|
||||
|
||||
@@ -1122,6 +1122,15 @@ 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
|
||||
@@ -1152,21 +1161,12 @@ func flashBinUsingEsp32(port, resetMode, tmppath string, options *compileopts.Op
|
||||
return err
|
||||
}
|
||||
|
||||
if err := flasher.EraseFlash(); err != nil {
|
||||
if err := flasher.EraseFlash(progressFunc); 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, progress)
|
||||
err = flasher.FlashImage(data, offset, progressFunc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+62
-8
@@ -59,6 +59,7 @@ func TestBuild(t *testing.T) {
|
||||
"cgo/",
|
||||
"channel.go",
|
||||
"embed/",
|
||||
"finalizer.go",
|
||||
"float.go",
|
||||
"gc.go",
|
||||
"generics.go",
|
||||
@@ -273,8 +274,17 @@ 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":
|
||||
@@ -296,6 +306,11 @@ 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.
|
||||
@@ -349,6 +364,17 @@ 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) {
|
||||
@@ -967,15 +993,15 @@ func TestGoexitCrash(t *testing.T) {
|
||||
name string
|
||||
want string
|
||||
}{
|
||||
{"main", "all goroutines are asleep - deadlock!"},
|
||||
{"deadlock", "all goroutines are asleep - deadlock!"},
|
||||
{"exit", "all goroutines are asleep - deadlock!"},
|
||||
{"main-other", "all goroutines are asleep - deadlock!"},
|
||||
{"in-panic", "all goroutines are asleep - deadlock!"},
|
||||
{"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", "all goroutines are asleep - deadlock!"},
|
||||
{"recover-before-panic", "all goroutines are asleep - deadlock!"},
|
||||
{"recover-before-panic-loop", "all goroutines are asleep - deadlock!"},
|
||||
{"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{}
|
||||
@@ -996,6 +1022,34 @@ func TestGoexitCrash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
//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
|
||||
)
|
||||
+232
-8
@@ -10,6 +10,49 @@
|
||||
.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
|
||||
@@ -17,10 +60,14 @@ 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.
|
||||
// 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.
|
||||
rsr.ps a2
|
||||
movi a3, ~(PS_WOE_MASK)
|
||||
and a2, a2, a3
|
||||
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
|
||||
wsr.ps a2
|
||||
rsync
|
||||
|
||||
@@ -37,7 +84,8 @@ call_start_cpu0:
|
||||
|
||||
// Re-enable WOE.
|
||||
rsr.ps a2
|
||||
movi a3, PS_WOE
|
||||
movi a3, 1
|
||||
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
|
||||
or a2, a2, a3
|
||||
wsr.ps a2
|
||||
rsync
|
||||
@@ -47,11 +95,187 @@ call_start_cpu0:
|
||||
wsr.cpenable a2
|
||||
rsync
|
||||
|
||||
// Jump to the runtime start function written in Go.
|
||||
call4 main
|
||||
// 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
|
||||
|
||||
// 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:
|
||||
// TODO: save callee saved registers on the stack
|
||||
j tinygo_scanstack
|
||||
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 a5→callee's a1 (stack ptr for callee's entry)
|
||||
// and caller's a6→callee'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
|
||||
|
||||
@@ -102,10 +102,14 @@ call_start_cpu0:
|
||||
|
||||
// ---- 1. Windowed-ABI register file setup ----
|
||||
|
||||
// Disable WOE so we can safely manipulate WINDOWSTART.
|
||||
// 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.
|
||||
rsr.ps a2
|
||||
movi a3, ~(PS_WOE)
|
||||
and a2, a2, a3
|
||||
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
|
||||
wsr.ps a2
|
||||
rsync
|
||||
|
||||
@@ -122,7 +126,8 @@ call_start_cpu0:
|
||||
|
||||
// Re-enable WOE.
|
||||
rsr.ps a2
|
||||
movi a3, PS_WOE
|
||||
movi a3, 1
|
||||
slli a3, a3, 18 // a3 = PS_WOE (0x40000)
|
||||
or a2, a2, a3
|
||||
wsr.ps a2
|
||||
rsync
|
||||
@@ -213,7 +218,9 @@ call_start_cpu0:
|
||||
// and cannot service flash accesses.
|
||||
|
||||
// 4a. Configure ICache mode: 16KB, 8-way, 32-byte line
|
||||
movi a6, 0x4000 // cache_size = 16KB
|
||||
// Use movi+slli to avoid auto-literal generation (lld l32r bug).
|
||||
movi a6, 1
|
||||
slli a6, a6, 14 // a6 = 0x4000 = 16KB
|
||||
movi a7, 8 // ways = 8
|
||||
movi a8, 32 // line_size = 32
|
||||
mov a5, a1
|
||||
@@ -226,7 +233,8 @@ call_start_cpu0:
|
||||
callx4 a4
|
||||
|
||||
// 4c. Configure DCache mode: 32KB, 8-way, 32-byte line
|
||||
movi a6, 0x8000 // cache_size = 32KB
|
||||
movi a6, 1
|
||||
slli a6, a6, 15 // a6 = 0x8000 = 32KB
|
||||
movi a7, 8 // ways = 8
|
||||
movi a8, 32 // line_size = 32
|
||||
mov a5, a1
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package uefi
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//go:linkname gosched runtime.Gosched
|
||||
func gosched()
|
||||
|
||||
// WaitForEvent blocks while yielding to the TinyGo scheduler so other
|
||||
// goroutines can continue to run.
|
||||
func WaitForEvent(event EFI_EVENT) EFI_STATUS {
|
||||
for {
|
||||
status := BS().CheckEvent(event)
|
||||
if status == EFI_SUCCESS {
|
||||
return EFI_SUCCESS
|
||||
}
|
||||
if status != EFI_NOT_READY {
|
||||
return status
|
||||
}
|
||||
gosched()
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,10 @@ type EFI_RUNTIME_SERVICES struct {
|
||||
queryVariableInfo uintptr
|
||||
}
|
||||
|
||||
func (p *EFI_RUNTIME_SERVICES) GetTime(time *EFI_TIME, capabilities *EFI_TIME_CAPABILITIES) EFI_STATUS {
|
||||
return UefiCall2(p.getTime, uintptr(unsafe.Pointer(time)), uintptr(unsafe.Pointer(capabilities)))
|
||||
}
|
||||
|
||||
type EFI_BOOT_SERVICES struct {
|
||||
Hdr EFI_TABLE_HEADER
|
||||
raiseTPL uintptr
|
||||
@@ -82,6 +86,10 @@ func (p *EFI_BOOT_SERVICES) WaitForEvent(numberOfEvents UINTN, event *EFI_EVENT,
|
||||
return UefiCall3(p.waitForEvent, uintptr(numberOfEvents), uintptr(unsafe.Pointer(event)), uintptr(unsafe.Pointer(index)))
|
||||
}
|
||||
|
||||
func (p *EFI_BOOT_SERVICES) SignalEvent(event EFI_EVENT) EFI_STATUS {
|
||||
return UefiCall1(p.signalEvent, uintptr(event))
|
||||
}
|
||||
|
||||
func (p *EFI_BOOT_SERVICES) CloseEvent(event EFI_EVENT) EFI_STATUS {
|
||||
return UefiCall1(p.closeEvent, uintptr(event))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package uefi
|
||||
|
||||
type EFI_TIME struct {
|
||||
Year uint16
|
||||
Month byte
|
||||
Day byte
|
||||
Hour byte
|
||||
Minute byte
|
||||
Second byte
|
||||
Pad1 byte
|
||||
Nanosecond uint32
|
||||
TimeZone int16
|
||||
Daylight byte
|
||||
Pad2 byte
|
||||
}
|
||||
|
||||
type EFI_TIME_CAPABILITIES struct {
|
||||
Resolution uint32
|
||||
Accuracy uint32
|
||||
SetsToZero BOOLEAN
|
||||
}
|
||||
|
||||
func GetTime() (EFI_TIME, EFI_STATUS) {
|
||||
var time EFI_TIME
|
||||
status := ST().RuntimeServices.GetTime(&time, nil)
|
||||
return time, status
|
||||
}
|
||||
|
||||
func (t *EFI_TIME) GetEpoch() (sec int64, nsec int32) {
|
||||
if t.TimeZone != 0x07FF { // EFI_UNSPECIFIED_TIMEZONE
|
||||
sec -= int64(t.TimeZone) * 60
|
||||
}
|
||||
year := int(t.Year)
|
||||
month := int(t.Month)
|
||||
|
||||
d := daysSinceEpoch(year)
|
||||
d += uint64(daysBefore[month-1])
|
||||
if isLeap(year) && month > 2 {
|
||||
d++
|
||||
}
|
||||
d += uint64(t.Day - 1)
|
||||
|
||||
abs := d * secondsPerDay
|
||||
abs += uint64(uint64(t.Hour)*uint64(secondsPerHour) + uint64(t.Minute)*uint64(secondsPerMinute) + uint64(t.Second))
|
||||
|
||||
sec = int64(abs) + (absoluteToInternal + internalToUnix)
|
||||
nsec = int32(t.Nanosecond)
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
secondsPerMinute = 60
|
||||
secondsPerHour = 60 * secondsPerMinute
|
||||
secondsPerDay = 24 * secondsPerHour
|
||||
daysPer400Years = 365*400 + 97
|
||||
daysPer100Years = 365*100 + 24
|
||||
daysPer4Years = 365*4 + 1
|
||||
|
||||
absoluteZeroYear = -292277022399
|
||||
internalYear = 1
|
||||
|
||||
absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
|
||||
unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
|
||||
internalToUnix int64 = -unixToInternal
|
||||
)
|
||||
|
||||
var daysBefore = [...]int32{
|
||||
0,
|
||||
31,
|
||||
31 + 28,
|
||||
31 + 28 + 31,
|
||||
31 + 28 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
|
||||
31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
|
||||
}
|
||||
|
||||
func daysSinceEpoch(year int) uint64 {
|
||||
y := uint64(int64(year) - absoluteZeroYear)
|
||||
|
||||
n := y / 400
|
||||
y -= 400 * n
|
||||
d := daysPer400Years * n
|
||||
|
||||
n = y / 100
|
||||
y -= 100 * n
|
||||
d += daysPer100Years * n
|
||||
|
||||
n = y / 4
|
||||
y -= 4 * n
|
||||
d += daysPer4Years * n
|
||||
|
||||
d += 365 * y
|
||||
return d
|
||||
}
|
||||
|
||||
func isLeap(year int) bool {
|
||||
return year%4 == 0 && (year%100 != 0 || year%400 == 0)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build stm32h7
|
||||
|
||||
package main
|
||||
|
||||
import "machine"
|
||||
|
||||
var (
|
||||
pwm = &machine.TIM1
|
||||
pinA = machine.PA8
|
||||
pinB = machine.PA9
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
println("configuring window watchdog")
|
||||
config := machine.WindowWatchdogConfig{
|
||||
TimeoutMicros: 100000, // 100ms
|
||||
WindowPercent: 50, // 50ms to 100ms refresh window
|
||||
}
|
||||
|
||||
machine.WindowWatchdog.Configure(config)
|
||||
machine.WindowWatchdog.Start()
|
||||
|
||||
println("updating wwdg for 1 second")
|
||||
for i := 0; i < 10; i++ {
|
||||
time.Sleep(75 * time.Millisecond) // middle of the window
|
||||
machine.WindowWatchdog.Update()
|
||||
println("alive")
|
||||
}
|
||||
|
||||
println("entering tight loop (will reset)")
|
||||
for {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package task
|
||||
|
||||
import _ "unsafe"
|
||||
|
||||
//go:linkname runtimeFatal runtime.runtimeFatal
|
||||
func runtimeFatal(msg string)
|
||||
@@ -20,7 +20,7 @@ func (m *Mutex) Lock() {
|
||||
|
||||
func (m *Mutex) Unlock() {
|
||||
if !m.locked {
|
||||
panic("sync: unlock of unlocked Mutex")
|
||||
runtimeFatal("sync: unlock of unlocked Mutex")
|
||||
}
|
||||
|
||||
// Wake up a blocked task, if applicable.
|
||||
|
||||
@@ -49,7 +49,7 @@ func (m *Mutex) Lock() {
|
||||
func (m *Mutex) Unlock() {
|
||||
if old := m.futex.Swap(0); old == 0 {
|
||||
// Mutex wasn't locked before.
|
||||
panic("sync: unlock of unlocked Mutex")
|
||||
runtimeFatal("sync: unlock of unlocked Mutex")
|
||||
} else if old == 2 {
|
||||
// Mutex was a contended lock, so we need to wake the next waiter.
|
||||
m.futex.Wake()
|
||||
|
||||
@@ -15,7 +15,7 @@ func (q *Queue) Push(t *Task) {
|
||||
mask := lockAtomics()
|
||||
if asserts && t.Next != nil {
|
||||
unlockAtomics(mask)
|
||||
panic("runtime: pushing a task to a queue with a non-nil Next pointer")
|
||||
runtimeFatal("runtime: pushing a task to a queue with a non-nil Next pointer")
|
||||
}
|
||||
if q.tail != nil {
|
||||
q.tail.Next = t
|
||||
@@ -78,7 +78,7 @@ func (s *Stack) Push(t *Task) {
|
||||
mask := lockAtomics()
|
||||
if asserts && t.Next != nil {
|
||||
unlockAtomics(mask)
|
||||
panic("runtime: pushing a task to a stack with a non-nil Next pointer")
|
||||
runtimeFatal("runtime: pushing a task to a stack with a non-nil Next pointer")
|
||||
}
|
||||
s.top, t.Next = t, s.top
|
||||
unlockAtomics(mask)
|
||||
|
||||
@@ -11,9 +11,6 @@ import (
|
||||
// otherwise Go wouldn't allow the cast to a smaller integer size.
|
||||
const stackCanary = uintptr(uint64(0x670c1333b83bf575) & uint64(^uintptr(0)))
|
||||
|
||||
//go:linkname runtimePanic runtime.runtimePanic
|
||||
func runtimePanic(str string)
|
||||
|
||||
// state is a structure which holds a reference to the state of the task.
|
||||
// When the task is suspended, the stack pointers are saved here.
|
||||
type state struct {
|
||||
@@ -95,7 +92,7 @@ func Current() *Task {
|
||||
// This function may only be called when running on a goroutine stack, not when running on the system stack.
|
||||
func Pause() {
|
||||
if *currentTask.state.canaryPtr != stackCanary {
|
||||
runtimePanic("stack overflow")
|
||||
runtimeFatal("stack overflow")
|
||||
}
|
||||
|
||||
currentTask.state.unwind()
|
||||
@@ -124,7 +121,7 @@ func (t *Task) Resume() {
|
||||
currentTask = prevTask
|
||||
t.gcData.swap()
|
||||
if uintptr(t.state.asyncifysp) > uintptr(t.state.csp) {
|
||||
runtimePanic("stack overflow")
|
||||
runtimeFatal("stack overflow")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,15 @@ func exit(goexit bool) {
|
||||
if t == mainTask {
|
||||
if goexit {
|
||||
if remaining == 0 {
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
atomic.StoreUint32(&mainExitedByGoexit, 1)
|
||||
}
|
||||
} else if atomic.LoadUint32(&mainExitedByGoexit) != 0 && remaining == 0 {
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
|
||||
// TODO: explicitly free the stack after switching back to the scheduler.
|
||||
Pause()
|
||||
runtimePanic("unreachable")
|
||||
runtimeFatal("unreachable")
|
||||
}
|
||||
|
||||
@@ -6,9 +6,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:linkname runtimePanic runtime.runtimePanic
|
||||
func runtimePanic(str string)
|
||||
|
||||
// Stack canary, to detect a stack overflow. The number is a random number
|
||||
// generated by random.org. The bit fiddling dance is necessary because
|
||||
// otherwise Go wouldn't allow the cast to a smaller integer size.
|
||||
|
||||
@@ -23,10 +23,10 @@ func PauseLocked() {
|
||||
// valid. If it is not, a stack overflow has occurred.
|
||||
current := Current()
|
||||
if *current.state.canaryPtr != stackCanary {
|
||||
runtimePanic("goroutine stack overflow")
|
||||
runtimeFatal("goroutine stack overflow")
|
||||
}
|
||||
if interrupt.In() {
|
||||
runtimePanic("blocked inside interrupt")
|
||||
runtimeFatal("blocked inside interrupt")
|
||||
}
|
||||
if current.RunState == RunStateResuming {
|
||||
// Another core already marked this goroutine as ready to resume.
|
||||
|
||||
@@ -18,10 +18,10 @@ func Pause() {
|
||||
// Check whether the canary (the lowest address of the stack) is still
|
||||
// valid. If it is not, a stack overflow has occurred.
|
||||
if *currentTask.state.canaryPtr != stackCanary {
|
||||
runtimePanic("goroutine stack overflow")
|
||||
runtimeFatal("goroutine stack overflow")
|
||||
}
|
||||
if interrupt.In() {
|
||||
runtimePanic("blocked inside interrupt")
|
||||
runtimeFatal("blocked inside interrupt")
|
||||
}
|
||||
currentTask.state.pause()
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ var activeTaskLock PMutex
|
||||
var mainExitedByGoexit bool
|
||||
|
||||
func OnSystemStack() bool {
|
||||
runtimePanic("todo: task.OnSystemStack")
|
||||
runtimeFatal("todo: task.OnSystemStack")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func Init(sp uintptr) {
|
||||
func Current() *Task {
|
||||
t := (*Task)(tinygo_task_current())
|
||||
if t == nil {
|
||||
runtimePanic("unknown current task")
|
||||
runtimeFatal("unknown current task")
|
||||
}
|
||||
return t
|
||||
}
|
||||
@@ -112,7 +112,7 @@ func start(fn uintptr, args unsafe.Pointer, stackSize uintptr) {
|
||||
activeTaskLock.Lock()
|
||||
errCode := tinygo_task_start(fn, args, t, &t.state.thread, &t.state.stackTop, stackSize)
|
||||
if errCode != 0 {
|
||||
runtimePanic("could not start thread")
|
||||
runtimeFatal("could not start thread")
|
||||
}
|
||||
t.state.QueueNext = activeTasks
|
||||
activeTasks = t
|
||||
@@ -127,7 +127,7 @@ func taskExited(t *Task) {
|
||||
}
|
||||
|
||||
if exit(t) {
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func exit(t *Task) bool {
|
||||
|
||||
// Sanity check.
|
||||
if !found {
|
||||
runtimePanic("taskExited failed")
|
||||
runtimeFatal("taskExited failed")
|
||||
}
|
||||
return deadlocked
|
||||
}
|
||||
@@ -173,11 +173,11 @@ func Exit() {
|
||||
}
|
||||
activeTaskLock.Unlock()
|
||||
if noOtherTasks {
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
}
|
||||
if exit(t) {
|
||||
runtimePanic("all goroutines are asleep - deadlock!")
|
||||
runtimeFatal("all goroutines are asleep - deadlock!")
|
||||
}
|
||||
tinygo_task_exit()
|
||||
}
|
||||
@@ -325,9 +325,6 @@ func StackTop() uintptr {
|
||||
return Current().state.stackTop
|
||||
}
|
||||
|
||||
//go:linkname runtimePanic runtime.runtimePanic
|
||||
func runtimePanic(msg string)
|
||||
|
||||
// Using //go:linkname instead of //export so that we don't tell the compiler
|
||||
// that the 't' parameter won't escape (because it will).
|
||||
//
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
// Pins printed on the silkscreen
|
||||
const (
|
||||
C13 = PC13
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 12_000_000
|
||||
|
||||
const (
|
||||
NUM_DIGITAL_IO_PINS = 39
|
||||
NUM_ANALOG_IO_PINS = 7
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
//go:build m5stamp_s3a
|
||||
|
||||
// This file contains the pin mappings for the M5Stack Stamp-S3A module.
|
||||
//
|
||||
// M5Stack Stamp-S3A is an embedded module based on the Espressif ESP32-S3FN8
|
||||
// chip with 8MB flash, a programmable RGB LED, a user button, and 23 exposed
|
||||
// GPIOs.
|
||||
//
|
||||
// - https://docs.m5stack.com/en/core/Stamp-S3A
|
||||
|
||||
package machine
|
||||
|
||||
// Digital pins
|
||||
const (
|
||||
G0 = GPIO0
|
||||
G1 = GPIO1
|
||||
G2 = GPIO2
|
||||
G3 = GPIO3
|
||||
G4 = GPIO4
|
||||
G5 = GPIO5
|
||||
G6 = GPIO6
|
||||
G7 = GPIO7
|
||||
G8 = GPIO8
|
||||
G9 = GPIO9
|
||||
G10 = GPIO10
|
||||
G11 = GPIO11
|
||||
G12 = GPIO12
|
||||
G13 = GPIO13
|
||||
G14 = GPIO14
|
||||
G15 = GPIO15
|
||||
G39 = GPIO39
|
||||
G40 = GPIO40
|
||||
G41 = GPIO41
|
||||
G42 = GPIO42
|
||||
G43 = GPIO43
|
||||
G44 = GPIO44
|
||||
G46 = GPIO46
|
||||
)
|
||||
|
||||
// Button
|
||||
const (
|
||||
BUTTON = GPIO0
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
UART_RX_PIN = GPIO44
|
||||
UART_TX_PIN = GPIO43
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
SDA_PIN = GPIO13
|
||||
SCL_PIN = GPIO14
|
||||
)
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI1_SCK_PIN = GPIO36
|
||||
SPI1_MISO_PIN = NoPin
|
||||
SPI1_MOSI_PIN = GPIO35
|
||||
SPI1_CS_PIN = NoPin
|
||||
|
||||
SPI2_SCK_PIN = NoPin
|
||||
SPI2_MISO_PIN = NoPin
|
||||
SPI2_MOSI_PIN = NoPin
|
||||
SPI2_CS_PIN = NoPin
|
||||
)
|
||||
|
||||
// Display pins
|
||||
const (
|
||||
DISPLAY_RST = GPIO33
|
||||
DISPLAY_DC = GPIO34
|
||||
DISPLAY_MOSI = GPIO35
|
||||
DISPLAY_SCK = GPIO36
|
||||
DISPLAY_CS = GPIO37
|
||||
DISPLAY_BL = GPIO38
|
||||
)
|
||||
|
||||
// Onboard LEDs
|
||||
const (
|
||||
// WS2812 is the data pin for the onboard addressable RGB LED.
|
||||
WS2812 = GPIO21
|
||||
|
||||
// WS2812_POWER enables power for the onboard addressable RGB LED.
|
||||
WS2812_POWER = GPIO38
|
||||
)
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
// LED is also wired to the SD card card detect (CD) pin.
|
||||
const LED = PD12
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED_BUILTIN = LED_GREEN
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED_BUILTIN = LED_GREEN
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
//go:build nucleoh753zi
|
||||
|
||||
package machine
|
||||
|
||||
import (
|
||||
"device/stm32"
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
const hseBypass = true
|
||||
|
||||
const (
|
||||
// Arduino Pins
|
||||
A0 = PA3
|
||||
A1 = PC0
|
||||
A2 = PC3
|
||||
A3 = PB1
|
||||
A4 = PC2
|
||||
A5 = PF10
|
||||
|
||||
D0 = PG9
|
||||
D1 = PG14
|
||||
D2 = PF15
|
||||
D3 = PE13
|
||||
D4 = PF14
|
||||
D5 = PE11
|
||||
D6 = PE9
|
||||
D7 = PF13
|
||||
D8 = PF12
|
||||
D9 = PD15
|
||||
D10 = PD14
|
||||
D11 = PA7
|
||||
D12 = PA6
|
||||
D13 = PA5
|
||||
D14 = PB9
|
||||
D15 = PB8
|
||||
)
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED_BUILTIN = LED_GREEN
|
||||
LED_GREEN = PB0
|
||||
LED_YELLOW = PE1
|
||||
LED_RED = PB14
|
||||
)
|
||||
|
||||
const (
|
||||
BUTTON = BUTTON_USER
|
||||
BUTTON_USER = PC13
|
||||
)
|
||||
|
||||
// UART pins
|
||||
const (
|
||||
// PD8 and PD9 are connected to the ST-Link Virtual Com Port (VCP)
|
||||
UART_TX_PIN = PD8
|
||||
UART_RX_PIN = PD9
|
||||
UART_ALT_FN = AF7_SPI2_3_USART1_2_3_UART5_SPDIFRX
|
||||
)
|
||||
|
||||
var (
|
||||
// USART3 is the hardware serial port connected to the onboard ST-LINK
|
||||
// debugger to be exposed as virtual COM port over USB on Nucleo boards.
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{
|
||||
Buffer: NewRingBuffer(),
|
||||
Bus: stm32.USART3,
|
||||
TxAltFuncSelector: UART_ALT_FN,
|
||||
RxAltFuncSelector: UART_ALT_FN,
|
||||
}
|
||||
DefaultUART = UART1
|
||||
)
|
||||
|
||||
func init() {
|
||||
UART1.Interrupt = interrupt.New(stm32.IRQ_USART3, _UART1.handleInterrupt)
|
||||
}
|
||||
|
||||
// SPI pins
|
||||
const (
|
||||
SPI0_SCK_PIN = PA5
|
||||
SPI0_SDI_PIN = PA6
|
||||
SPI0_SDO_PIN = PA7
|
||||
)
|
||||
|
||||
var (
|
||||
SPI1 = &SPI{
|
||||
Bus: stm32.SPI1,
|
||||
AltFuncSelector: AF5_SPI1_2_3_4_5_6_I2S,
|
||||
}
|
||||
SPI0 = SPI1
|
||||
)
|
||||
|
||||
// I2C pins
|
||||
const (
|
||||
I2C0_SCL_PIN = PB8
|
||||
I2C0_SDA_PIN = PB9
|
||||
)
|
||||
|
||||
var (
|
||||
I2C1 = &I2C{
|
||||
Bus: stm32.I2C1,
|
||||
AltFuncSelector: AF4_I2C1_2_3_4_USART1,
|
||||
}
|
||||
I2C0 = I2C1
|
||||
)
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
const (
|
||||
LED = LED_BUILTIN
|
||||
LED1 = LED_GREEN
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"runtime/interrupt"
|
||||
)
|
||||
|
||||
const xtalHz = 8_000_000
|
||||
|
||||
const (
|
||||
LED1 = LED_GREEN
|
||||
LED2 = LED_ORANGE
|
||||
|
||||
@@ -621,6 +621,9 @@ func (uart *UART) Configure(config UARTConfig) error {
|
||||
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
|
||||
uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_CTRLB_TXEN | sam.SERCOM_USART_CTRLB_RXEN)
|
||||
|
||||
// Configure RX/TX inversion
|
||||
uart.setInversion(config)
|
||||
|
||||
// Enable USART1 port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_CTRLA_ENABLE)
|
||||
|
||||
@@ -1102,6 +1102,9 @@ func (uart *UART) Configure(config UARTConfig) error {
|
||||
//sercom->USART.CTRLB.reg |= SERCOM_USART_CTRLB_TXEN | SERCOM_USART_CTRLB_RXEN ;
|
||||
uart.Bus.CTRLB.SetBits(sam.SERCOM_USART_INT_CTRLB_TXEN | sam.SERCOM_USART_INT_CTRLB_RXEN)
|
||||
|
||||
// Configure RX/TX inversion
|
||||
uart.setInversion(config)
|
||||
|
||||
// Enable USART1 port.
|
||||
// sercom->USART.CTRLA.bit.ENABLE = 0x1u;
|
||||
uart.Bus.CTRLA.SetBits(sam.SERCOM_USART_INT_CTRLA_ENABLE)
|
||||
|
||||
+274
-15
@@ -5,7 +5,9 @@ package machine
|
||||
import (
|
||||
"device/esp"
|
||||
"errors"
|
||||
"runtime/interrupt"
|
||||
"runtime/volatile"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
@@ -291,6 +293,107 @@ func (p Pin) mux() *volatile.Register32 {
|
||||
}
|
||||
}
|
||||
|
||||
const maxPin = 40
|
||||
|
||||
// cpuInterruptFromPin selects an edge-triggered CPU interrupt line for GPIO.
|
||||
// CPU interrupt 10 is edge-triggered level-1 on the Xtensa LX6, which prevents
|
||||
// the ISR from re-entering continuously when other peripherals (e.g. SPI via
|
||||
// the GPIO Matrix) keep GPIO.STATUS bits asserted.
|
||||
const cpuInterruptFromPin = 10
|
||||
|
||||
type PinChange uint8
|
||||
|
||||
// Pin change interrupt constants for SetInterrupt.
|
||||
const (
|
||||
PinRising PinChange = iota + 1
|
||||
PinFalling
|
||||
PinToggle
|
||||
)
|
||||
|
||||
// SetInterrupt sets an interrupt to be executed when a particular pin changes
|
||||
// state. The pin should already be configured as an input, including a pull up
|
||||
// or down if no external pull is provided.
|
||||
//
|
||||
// You can pass a nil func to unset the pin change interrupt. If you do so,
|
||||
// the change parameter is ignored and can be set to any value (such as 0).
|
||||
// If the pin is already configured with a callback, you must first unset
|
||||
// this pins interrupt before you can set a new callback.
|
||||
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
|
||||
if p >= maxPin {
|
||||
return ErrInvalidInputPin
|
||||
}
|
||||
|
||||
if callback == nil {
|
||||
// Disable this pin interrupt
|
||||
p.pinReg().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk)
|
||||
|
||||
if pinCallbacks[p] != nil {
|
||||
pinCallbacks[p] = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if pinCallbacks[p] != nil {
|
||||
// The pin was already configured.
|
||||
// To properly re-configure a pin, unset it first and set a new
|
||||
// configuration.
|
||||
return ErrNoPinChangeChannel
|
||||
}
|
||||
pinCallbacks[p] = callback
|
||||
|
||||
onceSetupPinInterrupt.Do(func() {
|
||||
setupPinInterruptErr = setupPinInterrupt()
|
||||
})
|
||||
if setupPinInterruptErr != nil {
|
||||
return setupPinInterruptErr
|
||||
}
|
||||
|
||||
p.pinReg().Set(
|
||||
(p.pinReg().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) |
|
||||
uint32(change)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
pinCallbacks [maxPin]func(Pin)
|
||||
onceSetupPinInterrupt sync.Once
|
||||
setupPinInterruptErr error
|
||||
)
|
||||
|
||||
func setupPinInterrupt() error {
|
||||
esp.DPORT.SetPRO_GPIO_INTERRUPT_MAP_PRO_GPIO_INTERRUPT_PRO_MAP(cpuInterruptFromPin)
|
||||
return interrupt.New(cpuInterruptFromPin, handleGPIOInterrupt).Enable()
|
||||
}
|
||||
|
||||
// handleGPIOInterrupt is the GPIO pin change interrupt handler. It must be a
|
||||
// plain function (not a closure) because interrupt.New is a compiler intrinsic
|
||||
// that does not support closures.
|
||||
func handleGPIOInterrupt(interrupt.Interrupt) {
|
||||
// Read and immediately clear interrupt status bits.
|
||||
// Clearing before processing is critical for edge-triggered CPU
|
||||
// interrupts: any new GPIO events that arrive during callback
|
||||
// execution will set fresh STATUS bits, generating a new edge
|
||||
// on the CPU interrupt line so they are not lost.
|
||||
status := esp.GPIO.STATUS.Get()
|
||||
status1 := esp.GPIO.STATUS1.Get()
|
||||
esp.GPIO.STATUS_W1TC.Set(status)
|
||||
esp.GPIO.STATUS1_W1TC.Set(status1)
|
||||
|
||||
// Check status for GPIO0-31
|
||||
for i, mask := 0, uint32(1); i < 32; i, mask = i+1, mask<<1 {
|
||||
if (status&mask) != 0 && pinCallbacks[i] != nil {
|
||||
pinCallbacks[i](Pin(i))
|
||||
}
|
||||
}
|
||||
// Check status for GPIO32-39
|
||||
for i, mask := 32, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
|
||||
if (status1&mask) != 0 && pinCallbacks[i] != nil {
|
||||
pinCallbacks[i](Pin(i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var DefaultUART = UART0
|
||||
|
||||
var (
|
||||
@@ -298,53 +401,209 @@ var (
|
||||
_UART0 = UART{
|
||||
Bus: esp.UART0,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 14,
|
||||
RTSCTSSignal: 15,
|
||||
txrxSignal: 14,
|
||||
rtsctsSignal: 15,
|
||||
}
|
||||
UART1 = &_UART1
|
||||
_UART1 = UART{
|
||||
Bus: esp.UART1,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 17,
|
||||
RTSCTSSignal: 18,
|
||||
txrxSignal: 17,
|
||||
rtsctsSignal: 18,
|
||||
}
|
||||
UART2 = &_UART2
|
||||
_UART2 = UART{
|
||||
Bus: esp.UART2,
|
||||
Buffer: NewRingBuffer(),
|
||||
TXRXSignal: 198,
|
||||
RTSCTSSignal: 199,
|
||||
txrxSignal: 198,
|
||||
rtsctsSignal: 199,
|
||||
}
|
||||
|
||||
onceUart = sync.Once{}
|
||||
)
|
||||
|
||||
// CPU interrupt line used for all UART peripherals.
|
||||
//
|
||||
// On the ESP32 (Xtensa LX6) the 32 CPU interrupt lines have fixed hardware
|
||||
// roles. Lines 6, 7, 11, 14, 15, 16 and 29 are internal (Xtensa timers,
|
||||
// software and profiling/NMI) and are NOT wired to the peripheral interrupt
|
||||
// matrix, so a peripheral routed to one of them via DPORT never fires.
|
||||
// The usable level-1 peripheral lines are 2, 3, 5, 8, 9, 10 (edge), 12, 13,
|
||||
// 17 and 18. We use line 8 for UART (9 is the timer alarm, 10 is GPIO).
|
||||
const cpuInterruptFromUART = 8
|
||||
|
||||
// uartInterrupts is the set of UART interrupt flags we care about for RX.
|
||||
const uartInterrupts = esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA |
|
||||
esp.UART_INT_ENA_RXFIFO_TOUT_INT_ENA |
|
||||
esp.UART_INT_ENA_PARITY_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_FRM_ERR_INT_ENA |
|
||||
esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA |
|
||||
esp.UART_INT_ENA_GLITCH_DET_INT_ENA
|
||||
|
||||
type UART struct {
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
TXRXSignal uint32
|
||||
RTSCTSSignal uint32
|
||||
Bus *esp.UART_Type
|
||||
Buffer *RingBuffer
|
||||
|
||||
txrxSignal uint32
|
||||
rtsctsSignal uint32
|
||||
parityErrorDetected bool
|
||||
dataErrorDetected bool
|
||||
dataOverflowDetected bool
|
||||
}
|
||||
|
||||
func (uart *UART) Configure(config UARTConfig) {
|
||||
func (uart *UART) Configure(config UARTConfig) error {
|
||||
if config.BaudRate == 0 {
|
||||
config.BaudRate = 115200
|
||||
}
|
||||
|
||||
// If no pins are specified (the zero value is GPIO0, which is never a
|
||||
// sensible default for both TX and RX), pick sensible defaults per UART.
|
||||
//
|
||||
// For UART0 (the console) we deliberately leave the pins untouched: the
|
||||
// ROM bootloader has already wired GPIO1 (TX) and GPIO3 (RX) directly via
|
||||
// the IO MUX to the USB-serial bridge. Re-routing them through the GPIO
|
||||
// matrix is unnecessary and can break RX, so we keep the bootloader setup
|
||||
// which is exactly what makes the boot log and greeting appear.
|
||||
// We still fall through to configure baud rate, interrupts, and the RX
|
||||
// FIFO even when pins are already wired.
|
||||
if config.TX == 0 && config.RX == 0 {
|
||||
switch uart.Bus {
|
||||
case esp.UART0:
|
||||
config.TX = NoPin
|
||||
config.RX = NoPin
|
||||
case esp.UART1:
|
||||
config.TX = 10
|
||||
config.RX = 9
|
||||
case esp.UART2:
|
||||
config.TX = 17
|
||||
config.RX = 16
|
||||
}
|
||||
}
|
||||
|
||||
uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate)
|
||||
|
||||
if config.RX != NoPin {
|
||||
config.RX.configure(PinConfig{Mode: PinInputPullup}, uart.TXRXSignal)
|
||||
config.RX.configure(PinConfig{Mode: PinInputPullup}, uart.txrxSignal)
|
||||
if config.InvertRX {
|
||||
inFunc(uart.txrxSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos | esp.GPIO_FUNC_IN_SEL_CFG_IN_INV_SEL)
|
||||
} else {
|
||||
inFunc(uart.txrxSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX)<<esp.GPIO_FUNC_IN_SEL_CFG_IN_SEL_Pos)
|
||||
}
|
||||
}
|
||||
|
||||
if config.TX != NoPin {
|
||||
config.TX.configure(PinConfig{Mode: PinOutput}, uart.TXRXSignal)
|
||||
config.TX.configure(PinConfig{Mode: PinOutput}, uart.txrxSignal)
|
||||
if config.InvertTX {
|
||||
config.TX.outFunc().Set(uart.txrxSignal | esp.GPIO_FUNC_OUT_SEL_CFG_INV_SEL)
|
||||
} else {
|
||||
config.TX.outFunc().Set(uart.txrxSignal)
|
||||
}
|
||||
}
|
||||
|
||||
if config.RTS != NoPin {
|
||||
config.RTS.configure(PinConfig{Mode: PinOutput}, uart.RTSCTSSignal)
|
||||
config.RTS.configure(PinConfig{Mode: PinOutput}, uart.rtsctsSignal)
|
||||
}
|
||||
|
||||
if config.CTS != NoPin {
|
||||
config.CTS.configure(PinConfig{Mode: PinInputPullup}, uart.RTSCTSSignal)
|
||||
config.CTS.configure(PinConfig{Mode: PinInputPullup}, uart.rtsctsSignal)
|
||||
}
|
||||
|
||||
uart.configureInterrupt()
|
||||
uart.enableReceiver()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (uart *UART) configureInterrupt() {
|
||||
// Disable all UART interrupts while configuring.
|
||||
uart.Bus.INT_ENA.ClearBits(0x0ffff)
|
||||
|
||||
// Map this UART's peripheral interrupt to a CPU interrupt line via DPORT.
|
||||
switch uart.Bus {
|
||||
case esp.UART0:
|
||||
esp.DPORT.SetPRO_UART_INTR_MAP(cpuInterruptFromUART)
|
||||
case esp.UART1:
|
||||
esp.DPORT.SetPRO_UART1_INTR_MAP(cpuInterruptFromUART)
|
||||
case esp.UART2:
|
||||
esp.DPORT.SetPRO_UART2_INTR_MAP(cpuInterruptFromUART)
|
||||
}
|
||||
|
||||
// Register the ISR only once (shared across all UARTs on the same CPU int).
|
||||
// interrupt.New is a compiler intrinsic and requires a plain (non-capturing)
|
||||
// handler function, so we use a named package-level function.
|
||||
onceUart.Do(func() {
|
||||
_ = interrupt.New(cpuInterruptFromUART, handleUARTInterrupt).Enable()
|
||||
})
|
||||
}
|
||||
|
||||
// handleUARTInterrupt is the shared UART interrupt handler. It must be a plain
|
||||
// function (not a closure) because interrupt.New is a compiler intrinsic that
|
||||
// does not support closures.
|
||||
func handleUARTInterrupt(interrupt.Interrupt) {
|
||||
UART0.serveInterrupt()
|
||||
UART1.serveInterrupt()
|
||||
UART2.serveInterrupt()
|
||||
}
|
||||
|
||||
func (uart *UART) serveInterrupt() {
|
||||
// Check masked interrupt status.
|
||||
interruptFlag := uart.Bus.INT_ST.Get()
|
||||
if (interruptFlag & uartInterrupts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Block UART interrupts while processing.
|
||||
uart.Bus.INT_ENA.ClearBits(uartInterrupts)
|
||||
|
||||
if interruptFlag&(esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA|esp.UART_INT_ENA_RXFIFO_TOUT_INT_ENA) != 0 {
|
||||
for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 {
|
||||
// The ESP32 UART FIFO must be accessed through the AHB address
|
||||
// (base + 0x200C0000 == 0x60000000 for UART0), not the APB FIFO
|
||||
// register, due to a silicon erratum. This mirrors writeByte.
|
||||
b := (*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Get()
|
||||
if !uart.Buffer.Put(b) {
|
||||
uart.dataOverflowDetected = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if interruptFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 {
|
||||
uart.parityErrorDetected = true
|
||||
}
|
||||
if interruptFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA != 0 {
|
||||
uart.dataErrorDetected = true
|
||||
}
|
||||
if interruptFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA != 0 {
|
||||
uart.dataOverflowDetected = true
|
||||
}
|
||||
if interruptFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA != 0 {
|
||||
uart.dataErrorDetected = true
|
||||
}
|
||||
|
||||
// Clear the interrupt status bits.
|
||||
uart.Bus.INT_CLR.SetBits(interruptFlag)
|
||||
uart.Bus.INT_CLR.ClearBits(interruptFlag)
|
||||
// Re-enable UART interrupts.
|
||||
uart.Bus.INT_ENA.Set(uartInterrupts)
|
||||
}
|
||||
|
||||
func (uart *UART) enableReceiver() {
|
||||
// Reset the RX FIFO.
|
||||
uart.Bus.SetCONF0_RXFIFO_RST(1)
|
||||
uart.Bus.SetCONF0_RXFIFO_RST(0)
|
||||
// Trigger interrupt when 1 byte is available (low latency).
|
||||
uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1)
|
||||
// Enable the RX timeout so that a single byte still generates an interrupt
|
||||
// once the line has been idle for the given number of bit periods. Without
|
||||
// this, RXFIFO_FULL only fires once more than the threshold has arrived.
|
||||
uart.Bus.SetCONF1_RX_TOUT_THRHD(2)
|
||||
uart.Bus.SetCONF1_RX_TOUT_EN(1)
|
||||
// Enable RX-related interrupts.
|
||||
uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_RXFIFO_TOUT_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1)
|
||||
uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1)
|
||||
}
|
||||
|
||||
func (uart *UART) writeByte(b byte) error {
|
||||
|
||||
@@ -447,9 +447,17 @@ func (uart *UART) setupPins(config UARTConfig, regs registerSet) {
|
||||
config.TX.Configure(PinConfig{Mode: PinInputPullup})
|
||||
|
||||
// link TX with GPIO signal X (technical reference manual 5.10) (this is not interrupt signal!)
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
if config.InvertTX {
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal | esp.GPIO_FUNC_OUT_SEL_CFG_INV_SEL)
|
||||
} else {
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
}
|
||||
// link RX with GPIO signal X and route signals via GPIO matrix (GPIO_SIGn_IN_SEL 0x40)
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
|
||||
if config.InvertRX {
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX) | esp.GPIO_FUNC_IN_SEL_CFG_IN_INV_SEL)
|
||||
} else {
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
|
||||
}
|
||||
}
|
||||
|
||||
func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) { // Disable all UART interrupts
|
||||
|
||||
@@ -460,9 +460,17 @@ func (uart *UART) setupPins(config UARTConfig, regs registerSet) {
|
||||
config.TX.Configure(PinConfig{Mode: PinInputPullup})
|
||||
|
||||
// link TX with GPIO signal X (technical reference manual, GPIO matrix)
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
if config.InvertTX {
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal | esp.GPIO_FUNC_OUT_SEL_CFG_INV_SEL)
|
||||
} else {
|
||||
config.TX.outFunc().Set(regs.gpioMatrixSignal)
|
||||
}
|
||||
// link RX with GPIO signal X and route signals via GPIO matrix
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
|
||||
if config.InvertRX {
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX) | esp.GPIO_FUNC_IN_SEL_CFG_IN_INV_SEL)
|
||||
} else {
|
||||
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
|
||||
}
|
||||
}
|
||||
|
||||
func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) {
|
||||
|
||||
@@ -141,6 +141,16 @@ func (p Pin) setSchmitt(trigger bool) {
|
||||
p.padCtrl().ReplaceBits(boolToBit(trigger)<<rp.PADS_BANK0_GPIO0_SCHMITT_Pos, rp.PADS_BANK0_GPIO0_SCHMITT_Msk, 0)
|
||||
}
|
||||
|
||||
// setOutOver sets the output override (OUTOVER field) for the pin.
|
||||
func (p Pin) setOutOver(over uint32) {
|
||||
p.ioCtrl().ReplaceBits(over<<rp.IO_BANK0_GPIO0_CTRL_OUTOVER_Pos, rp.IO_BANK0_GPIO0_CTRL_OUTOVER_Msk, 0)
|
||||
}
|
||||
|
||||
// setInOver sets the input override (INOVER field) for the pin.
|
||||
func (p Pin) setInOver(over uint32) {
|
||||
p.ioCtrl().ReplaceBits(over<<rp.IO_BANK0_GPIO0_CTRL_INOVER_Pos, rp.IO_BANK0_GPIO0_CTRL_INOVER_Msk, 0)
|
||||
}
|
||||
|
||||
// setFunc will set pin function to fn.
|
||||
func (p Pin) setFunc(fn pinFunc) {
|
||||
// Set input enable, Clear output disable
|
||||
|
||||
@@ -52,9 +52,19 @@ func (uart *UART) Configure(config UARTConfig) error {
|
||||
// set GPIO mux to UART for the pins
|
||||
if config.TX != NoPin {
|
||||
config.TX.Configure(PinConfig{Mode: PinUART})
|
||||
if config.InvertTX {
|
||||
config.TX.setOutOver(rp.IO_BANK0_GPIO0_CTRL_OUTOVER_INVERT)
|
||||
} else {
|
||||
config.TX.setOutOver(rp.IO_BANK0_GPIO0_CTRL_OUTOVER_NORMAL)
|
||||
}
|
||||
}
|
||||
if config.RX != NoPin {
|
||||
config.RX.Configure(PinConfig{Mode: PinUART})
|
||||
if config.InvertRX {
|
||||
config.RX.setInOver(rp.IO_BANK0_GPIO0_CTRL_INOVER_INVERT)
|
||||
} else {
|
||||
config.RX.setInOver(rp.IO_BANK0_GPIO0_CTRL_INOVER_NORMAL)
|
||||
}
|
||||
}
|
||||
if config.RTS != 0 {
|
||||
config.RTS.Configure(PinConfig{Mode: PinOutput})
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//go:build sam && (atsamd51 || atsame5x)
|
||||
|
||||
package machine
|
||||
|
||||
// Configure UART TX/RX line inversion using SVD-generated APIs.
|
||||
func (uart *UART) setInversion(config UARTConfig) {
|
||||
if config.InvertTX {
|
||||
uart.Bus.SetCTRLA_TXINV(1)
|
||||
} else {
|
||||
uart.Bus.SetCTRLA_TXINV(0)
|
||||
}
|
||||
if config.InvertRX {
|
||||
uart.Bus.SetCTRLA_RXINV(1)
|
||||
} else {
|
||||
uart.Bus.SetCTRLA_RXINV(0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build sam && !atsamd51 && !atsame5x
|
||||
|
||||
package machine
|
||||
|
||||
// Hardware inversion is not supported on SAMD21.
|
||||
func (uart *UART) setInversion(config UARTConfig) {
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user