Compare commits

..

21 Commits

Author SHA1 Message Date
deadprogram 59b50926c7 esp32s3: fix register-window corruption under interrupt load
Remove the C3 bluetooth hook addresses from esp32s3.ld (on the S3 they
point into the ROM md5/crc thunk table, and being bare assignments they
also shadowed the blob's own definitions), keep the interrupt frame clear
of the 16-byte windowed-ABI save area below SP, and make tinygo_swapTask
hold INTLEVEL across the stack switch while keeping the running frame's
WINDOWSTART bit set.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-20 17:42:42 +02:00
sago35 93bc65479f machine: add USBDevice.Attach and USBDevice.Detach (#5563)
* machine: add USBDevice.Attach and USBDevice.Detach

The USB device is attached to the bus automatically during startup,
before user code has a chance to finish its USB configuration (device
identifiers, extra HID interfaces, ...). Composite devices such as
keyboards may therefore be enumerated by the host with an incomplete
configuration.

Attach and Detach expose the soft-connect control (DP pull-up) so that
an application or library can detach in an init function, complete its
configuration, and attach again to let the host enumerate the finished
device. They can also be used to force re-enumeration without
replugging the cable.

Implemented for atsamd21, atsamd51, nrf52840, rp2040 and rp2350.

* machine: make USB Detach sticky on nrf52840

The USB IRQ handler re-enables the DP pull-up on every power-ready
event, silently undoing an earlier Detach. Guard the pull-up write with
a detached flag so the device stays off the bus until Attach is called.

* machine: add USB Attach and Detach to stm32 and esp32 targets

- stm32f4, stm32f7, stm32h7: implement Attach and Detach using the
  DCTL soft-disconnect bit that Configure already toggles.
- esp32c3, esp32c6, esp32s3: add no-op stubs to keep user code
  portable; the fixed-function USB Serial/JTAG controller has no
  software-controlled soft-connect.

* machine: use Attach and Detach in USB Configure

Replace the direct soft-connect register writes in Configure with the
equivalent Attach and Detach calls on atsamd21, atsamd51, rp2040,
rp2350, stm32f4, stm32f7 and stm32h7.
2026-08-20 17:17:33 +02:00
Jake Bailey 1958d6ff9d test: run ESP32 programs in QEMU 2026-08-20 16:07:13 +02:00
Jake Bailey 1991b00395 builder: fix ESP32 QEMU XIP image offsets 2026-08-20 16:07:13 +02:00
deadprogram ada22bc691 all: build/test using Go 1.27.0
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-20 14:27:13 +02:00
deadprogram e3bf9306b3 Revert "chore: copy fix to esp32xx"
This reverts commit 6cd0c00060.
2026-08-20 12:53:31 +02:00
Navendu Pottekkat 6cd0c00060 chore: copy fix to esp32xx
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-20 10:20:11 +02:00
Navendu Pottekkat dc97300c2e fix: remove incorrect !readLast check
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-20 10:20:11 +02:00
deadprogram 6136fb7b95 test: skip asn1 nesting-limit tests, and BoundarySlices crypto tests
Skip TestUnmarshalNestingLimitSlice/Struct, which fails due to the nesting limit
added in golang/go@6a6d115f9a.

Since Go 1.27 the crypto hash tests go through cryptotest.TestHash, which
calls cryptotest.BoundarySlices. Targets reporting GOOS=linux build
boundary.go (//go:build linux || darwin) instead of boundary_compat.go, and
that needs a working syscall.Mmap/syscall.Mprotect:

* baremetal fails to compile: undefined: syscall.Mprotect
* wasip2 gets ENOSYS from syscall.Mmap, and since t.Fatalf cannot Goexit on
  wasm the test falls through and panics with "slice out of range"

Exclude crypto/md5, crypto/sha1, crypto/sha256 and crypto/sha512 from both.
wasip1 reports GOOS=wasip1, gets the boundary_compat.go fallback, and keeps
testing all four.

Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-19 08:39:13 -07:00
deadprogram f21e027b5c all: build/test using Go 1.27-rc3
Signed-off-by: deadprogram <ron@hybridgroup.com>
2026-08-19 08:39:13 -07:00
Moses Narrow ac45c35924 os: add File.Chown on the unix path
file_unix.go (darwin/linux/wasip) was missing the exported (*File).Chown method
that file_other.go (baremetal/wasm) already has, so callers requiring the full
os.File surface — e.g. github.com/pkg/sftp — failed to compile for the
linux/amd64 TinyGo target. Delegates to the package-level Chown, mirroring
(*File).Truncate.
2026-08-18 14:18:42 -07:00
Moses Narrow 338af91ae6 runtime: implement MemStats.NumGC
Reading runtime.MemStats.NumGC is common enough in dependency code that its
absence is a compile error for programs that never look at the value. Rather
than add the field as a constant zero, track it:

- gc_blocks: count completed cycles in runGC, so collections triggered by an
  allocation are counted as well as explicit runtime.GC() calls. The counter is
  read and written under gcLock, like the other counters beside it.
- gc_boehm: report bdwgc's own gc_no from the prof_stats struct.
- gc_leaking: always 0, since that collector never completes a cycle.
2026-08-18 14:18:29 -07:00
v1rtl d74f70bdd0 os, syscall: add Statfs/Fstatfs stubs and Getpagesize for non-hosted targets 2026-08-18 14:18:19 -07:00
Navendu Pottekkat 80506a9cea esp32: fix pullup/pulldown on RTC GPIO pins
Signed-off-by: Navendu Pottekkat <navendu@apache.org>
2026-08-16 06:45:06 -07:00
Jake Bailey c33682cf00 builder: build SSA before compiling packages 2026-08-14 11:56:40 -07:00
Jake Bailey 570a3deac2 tests: normalize LLVM IR when updating goldens 2026-08-14 11:56:25 -07:00
Damian Gryski c4219439eb runtime: make arrays and struct field hashes order dependent 2026-08-12 12:08:52 -07:00
Konstantin Sharlaimov 7a9c649268 fix(gc): pause all cores before scanning stack and globals.
In gcMarkReachable, busy-wait for other cores to enter the interrupt handler and pause before scanning the GC core's stack or globals. Prevents data race where a running core relocates heap references to globals during mark phase.
2026-08-09 12:11:59 -07:00
Konstantin Sharlaimov 213d10838f fix(gc): correct leaking allocator bounds and overflow checks.
Change heap-end comparison to > to prevent spurious OOM when heapptr exactly matches heapEnd. Add overflow check to prevent heap wrapping.
2026-08-09 12:11:59 -07:00
Konstantin Sharlaimov b573ef3813 fix(gc): correct old size calculation in block realloc.
Using blocksPerStateByte instead of bytesPerBlock caused the old size to be underestimated for multi-block allocations, leading to data truncation on growing reallocations.
2026-08-09 12:11:59 -07:00
Mohammed-Asad-Khan 854f91ecf2 Fix to allow -o flag to point to a directory
Previously, running 'tinygo build -o dist/ file.go' where dist/ already
exists as a directory failed with 'open dist/: is a directory', because
Build() only auto-derived a binary name when outpath was empty, not when
it pointed to an existing directory. This adds that check, matching the
behavior of 'go build -o dir/' with a package.

Note: only handles the case where the directory already exists; if the
directory doesn't exist yet, behavior is unchanged.
2026-08-05 07:17:00 -07:00
50 changed files with 1640 additions and 152 deletions
+2 -2
View File
@@ -40,7 +40,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -131,7 +131,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Build TinyGo (LLVM ${{ matrix.version }}) - name: Build TinyGo (LLVM ${{ matrix.version }})
run: go install -tags=llvm${{ matrix.version }} run: go install -tags=llvm${{ matrix.version }}
+17 -5
View File
@@ -23,7 +23,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Run go mod tidy - name: Run go mod tidy
run: go mod tidy run: go mod tidy
@@ -36,7 +36,7 @@ jobs:
# statically linked binary. # statically linked binary.
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: golang:1.27rc2-alpine image: golang:1.27-alpine
outputs: outputs:
version: ${{ steps.version.outputs.version }} version: ${{ steps.version.outputs.version }}
steps: steps:
@@ -160,7 +160,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Install wasmtime - name: Install wasmtime
uses: bytecodealliance/actions/wasmtime/setup@v1 uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -196,6 +196,10 @@ jobs:
cat /proc/cpuinfo cat /proc/cpuinfo
sudo apt-get update sudo apt-get update
sudo apt-get install --no-install-recommends \ sudo apt-get install --no-install-recommends \
libgcrypt20 \
libpixman-1-0 \
libsdl2-2.0-0 \
libslirp0 \
qemu-system-arm \ qemu-system-arm \
qemu-system-riscv32 \ qemu-system-riscv32 \
qemu-user \ qemu-user \
@@ -204,7 +208,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
@@ -216,6 +220,14 @@ jobs:
version: "29.0.1" version: "29.0.1"
- name: Setup `wasm-tools` - name: Setup `wasm-tools`
uses: bytecodealliance/actions/wasm-tools/setup@v1 uses: bytecodealliance/actions/wasm-tools/setup@v1
- name: Install Espressif QEMU
run: |
release=esp-develop-9.2.2-20260417
archive=qemu-xtensa-softmmu-${release//-/_}-x86_64-linux-gnu.tar.xz
curl -fL --retry 3 -o "$RUNNER_TEMP/$archive" \
"https://github.com/espressif/qemu/releases/download/$release/$archive"
tar -xJf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP"
echo "$RUNNER_TEMP/qemu/bin" >> "$GITHUB_PATH"
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
id: cache-llvm-source id: cache-llvm-source
@@ -323,7 +335,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Restore LLVM source cache - name: Restore LLVM source cache
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
+4 -4
View File
@@ -34,7 +34,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Restore cached LLVM source - name: Restore cached LLVM source
uses: actions/cache/restore@v5 uses: actions/cache/restore@v5
@@ -129,7 +129,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -150,7 +150,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
@@ -176,7 +176,7 @@ jobs:
- name: Install Go - name: Install Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.27.0-rc.2' go-version: '1.27.0'
cache: true cache: true
- name: Download TinyGo build - name: Download TinyGo build
uses: actions/download-artifact@v8 uses: actions/download-artifact@v8
+2 -2
View File
@@ -1,5 +1,5 @@
# tinygo-llvm stage obtains the llvm source for TinyGo # tinygo-llvm stage obtains the llvm source for TinyGo
FROM golang:1.27rc2 AS tinygo-llvm FROM golang:1.27 AS tinygo-llvm
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y apt-utils make cmake clang-17 ninja-build && \ 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 # tinygo-compiler copies the compiler build over to a base Go container (without
# all the build tools etc). # all the build tools etc).
FROM golang:1.27rc2 AS tinygo-compiler FROM golang:1.27 AS tinygo-compiler
# Copy tinygo build. # Copy tinygo build.
COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo COPY --from=tinygo-compiler-build /tinygo/build/release/tinygo /tinygo
+23 -2
View File
@@ -473,9 +473,15 @@ TEST_PACKAGES_NONWASM = \
# (just like wasm). # (just like wasm).
# * picolibc math functions apparently are less precise, the math package # * picolibc math functions apparently are less precise, the math package
# fails on baremetal. # fails on baremetal.
# * Since Go 1.27 the crypto tests below go through cryptotest.TestHash, which
# calls cryptotest.BoundarySlices. These targets report GOOS=linux, so they
# build boundary.go (//go:build linux || darwin) rather than
# boundary_compat.go, and that needs a working syscall.Mmap/syscall.Mprotect
# which we don't have. See #5593.
TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST)) TEST_PACKAGES_BAREMETAL = $(filter-out $(TEST_PACKAGES_NONBAREMETAL), $(TEST_PACKAGES_FAST))
TEST_PACKAGES_NONBAREMETAL = \ TEST_PACKAGES_NONBAREMETAL = \
$(TEST_PACKAGES_NONWASM) \ $(TEST_PACKAGES_NONWASM) \
$(TEST_PACKAGES_NOBOUNDARYSLICES) \
math \ math \
$(nil) $(nil)
@@ -484,6 +490,19 @@ TEST_PACKAGES_NOWASI = \
crypto/ecdsa \ crypto/ecdsa \
$(nil) $(nil)
# wasip1 reports GOOS=wasip1 and so gets the boundary_compat.go fallback, but
# wasip2 reports GOOS=linux and hits the same BoundarySlices problem as
# baremetal. On wasip2 syscall.Mmap returns ENOSYS and t.Fatalf cannot Goexit,
# so the test falls through and panics with "slice out of range".
TEST_PACKAGES_FAST_WASIP2 = $(filter-out $(TEST_PACKAGES_NOBOUNDARYSLICES), $(TEST_PACKAGES_FAST_WASI))
TEST_PACKAGES_NOBOUNDARYSLICES = \
crypto/md5 \
crypto/sha1 \
crypto/sha256 \
crypto/sha512 \
$(nil)
# Report platforms on which each standard library package is known to pass tests # Report platforms on which each standard library package is known to pass tests
report-stdlib-tests-pass: report-stdlib-tests-pass:
$(eval jointmp := $(shell echo /tmp/join.$$$$)) $(eval jointmp := $(shell echo /tmp/join.$$$$))
@@ -510,7 +529,7 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS)
TEST_IOFS := false TEST_IOFS := false
endif endif
TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestAsValidation' TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestAsValidation|TestUnmarshalNestingLimitSlice|TestUnmarshalNestingLimitStruct'
TEST_ADDITIONAL_FLAGS ?= TEST_ADDITIONAL_FLAGS ?=
# Test known-working standard library packages. # Test known-working standard library packages.
@@ -519,6 +538,8 @@ TEST_ADDITIONAL_FLAGS ?=
tinygo-test: tinygo-test:
@# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented. @# TestExtraMethods: used by many crypto packages and uses reflect.Type.Method which is not implemented.
@# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm. @# TestParseAndBytesRoundTrip/P256/Generic: needs Goexit to run defers on wasm.
@# TestUnmarshalNestingLimit{Slice,Struct}: encoding/asn1 nesting limit added in
@# https://github.com/golang/go/commit/6a6d115f9a7422b2fa081ba6f567eefb4a099462
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW) $(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) $(filter-out encoding/xml,$(TEST_PACKAGES_HOST)) $(TEST_PACKAGES_SLOW)
ifeq ($(TEST_ENCODING_XML),true) ifeq ($(TEST_ENCODING_XML),true)
$(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml $(TINYGO) test $(TEST_ADDITIONAL_FLAGS) $(TEST_SKIP_FLAG) -stack-size=16MB encoding/xml
@@ -549,7 +570,7 @@ tinygo-test-wasip1-fast:
tinygo-test-wasip2-slow: tinygo-test-wasip2-slow:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW) $(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_SLOW)
tinygo-test-wasip2-fast: tinygo-test-wasip2-fast:
$(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASI) ./tests/runtime_wasi $(TINYGO) test -target=wasip2 $(TEST_SKIP_FLAG) $(TEST_PACKAGES_FAST_WASIP2) ./tests/runtime_wasi
tinygo-test-wasip2-sum-slow: tinygo-test-wasip2-sum-slow:
TINYGO=$(TINYGO) \ TINYGO=$(TINYGO) \
-1
View File
@@ -1 +0,0 @@
Use ASD-STE100 Simplified Technical English for content outside of the code like comments, PR description, PR title, github comments.
+9 -2
View File
@@ -24,6 +24,7 @@ import (
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/gofrs/flock" "github.com/gofrs/flock"
"github.com/tinygo-org/tinygo/compileopts" "github.com/tinygo-org/tinygo/compileopts"
@@ -269,6 +270,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
// Create the *ssa.Program. This does not yet build the entire SSA of the // Create the *ssa.Program. This does not yet build the entire SSA of the
// program so it's pretty fast and doesn't need to be parallelized. // program so it's pretty fast and doesn't need to be parallelized.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
buildProgram := sync.OnceFunc(program.Build)
// Add jobs to compile each package. // Add jobs to compile each package.
// Packages that have a cache hit will not be compiled again. // Packages that have a cache hit will not be compiled again.
@@ -398,8 +400,13 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
return nil return nil
} }
// Compile AST to IR. The compiler.CompilePackage function will // SSA package builds may run concurrently, but the resulting
// build the SSA as needed. // functions cannot be inspected until all builds have finished:
// generic instances and wrappers can be shared across packages.
// Build the whole program once before compiling any package.
buildProgram()
// Compile AST to IR.
mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA()) mod, errs := compiler.CompilePackage(pkg.ImportPath, pkg, program.Package(pkg.Pkg), machine, compilerConfig, config.DumpSSA())
defer mod.Context().Dispose() defer mod.Context().Dispose()
defer mod.Dispose() defer mod.Dispose()
+6 -3
View File
@@ -111,9 +111,6 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
// Compute the size of the RAM portion of the image (everything the ROM // Compute the size of the RAM portion of the image (everything the ROM
// bootloader loads, up to and including the appended SHA256 hash). // bootloader loads, up to and including the appended SHA256 hash).
ramImageSize := 0 ramImageSize := 0
if makeImage {
ramImageSize += 4096
}
ramImageSize += 24 // image header (8) + trailer fields (16) ramImageSize += 24 // image header (8) + trailer fields (16)
for _, seg := range segments { for _, seg := range segments {
ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned) ramImageSize += 8 + len(seg.data) // segment header + data (4-aligned)
@@ -284,6 +281,9 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
dromSize := 0 dromSize := 0
if len(dromSegs) > 0 { if len(dromSegs) > 0 {
targetImageOffset := int(dromFlashAddr - flashBase) targetImageOffset := int(dromFlashAddr - flashBase)
if makeImage {
targetImageOffset = int(dromFlashAddr)
}
if outf.Len() > targetImageOffset { if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr) return fmt.Errorf("ESP32: RAM segments too large (%d bytes), overlap DROM at flash 0x%x", outf.Len(), dromFlashAddr)
} }
@@ -304,6 +304,9 @@ func makeESPFirmwareImage(infile, outfile, format string) error {
} }
iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize iromFlashAddr := dromFlashAddr + uint32(dromPages)*pageSize
targetImageOffset := int(iromFlashAddr - flashBase) targetImageOffset := int(iromFlashAddr - flashBase)
if makeImage {
targetImageOffset = int(iromFlashAddr)
}
if outf.Len() > targetImageOffset { if outf.Len() > targetImageOffset {
return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr) return fmt.Errorf("ESP32: DROM too large, overlaps IROM at flash 0x%x", iromFlashAddr)
} }
+3 -3
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test. // This is a small number of very diverse targets that we want to test.
tests := []sizeTest{ tests := []sizeTest{
// microcontrollers // microcontrollers
{"hifive1b", "examples/echo", 4313, 323, 0, 2260}, {"hifive1b", "examples/echo", 4321, 323, 0, 2268},
{"microbit", "examples/serial", 2838, 382, 8, 2256}, {"microbit", "examples/serial", 2842, 382, 8, 2264},
{"wioterminal", "examples/pininterrupt", 8027, 1665, 132, 7488}, {"wioterminal", "examples/pininterrupt", 8039, 1665, 132, 7496},
// TODO: also check wasm. Right now this is difficult, because // TODO: also check wasm. Right now this is difficult, because
// wasm binaries are run through wasm-opt and therefore the // wasm binaries are run through wasm-opt and therefore the
+3 -3
View File
@@ -296,6 +296,9 @@ func Sizes(machine llvm.TargetMachine) types.Sizes {
} }
// CompilePackage compiles a single package to a LLVM module. // CompilePackage compiles a single package to a LLVM module.
//
// The SSA package must already be built. When packages are compiled
// concurrently, the entire SSA program must be built before compilation starts.
func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) { func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, machine llvm.TargetMachine, config *Config, dumpSSA bool) (llvm.Module, []error) {
c := newCompilerContext(moduleName, machine, config, dumpSSA) c := newCompilerContext(moduleName, machine, config, dumpSSA)
defer c.dispose() defer c.dispose()
@@ -306,9 +309,6 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg c.runtimePkg = ssaPkg.Prog.ImportedPackage("runtime").Pkg
c.program = ssaPkg.Prog c.program = ssaPkg.Prog
// Convert AST to SSA.
ssaPkg.Build()
// Assign names to function-local named types before compiling the // Assign names to function-local named types before compiling the
// package, so that types declared in different functions (or in // package, so that types declared in different functions (or in
// different instantiations of a generic function) do not collide. // different instantiations of a generic function) do not collide.
+6 -5
View File
@@ -115,7 +115,7 @@ func TestCompiler(t *testing.T) {
// Update test if needed. Do not check the result. // Update test if needed. Do not check the result.
if *flagUpdate { if *flagUpdate {
err := os.WriteFile(outPath, []byte(mod.String()), 0666) err := os.WriteFile(outPath, []byte(normalizeIR(mod.String())), 0666)
if err != nil { if err != nil {
t.Error("failed to write updated output file:", err) t.Error("failed to write updated output file:", err)
} }
@@ -174,9 +174,8 @@ func TestOptimizedLargeAggregateABI(t *testing.T) {
} }
} }
// normalizeIR canonicalizes LLVM IR so a single golden file keeps matching // normalizeIR canonicalizes LLVM-version-specific IR spellings for comparison
// across LLVM versions. Golden files are written against LLVM <21; newer LLVM // and when regenerating golden files.
// prints some attributes differently.
func normalizeIR(s string) string { func normalizeIR(s string) string {
// Golden files are written using the pre-LLVM21 'nocapture' spelling, // Golden files are written using the pre-LLVM21 'nocapture' spelling,
// which LLVM printed before any co-occurring attribute such as // which LLVM printed before any co-occurring attribute such as
@@ -414,5 +413,7 @@ func testCompilePackage(t *testing.T, options *compileopts.Options, file string)
// Compile AST to IR. // Compile AST to IR.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
pkg := lprogram.MainPkg() pkg := lprogram.MainPkg()
return CompilePackage(file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) ssaPkg := program.Package(pkg.Pkg)
ssaPkg.Build()
return CompilePackage(file, pkg, ssaPkg, machine, compilerConfig, false)
} }
+16 -16
View File
@@ -19,33 +19,33 @@ define hidden void @main.chanIntSend(ptr dereferenceable_or_null(36) %ch, ptr %c
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
store i32 3, ptr %chan.value, align 4 store i32 3, ptr %chan.value, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 call void @runtime.chanSend(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
ret void ret void
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #2 declare void @llvm.lifetime.start.p0(ptr nocapture) #2
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 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) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #2 declare void @llvm.lifetime.end.p0(ptr nocapture) #2
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanIntRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
%chan.value = alloca i32, align 4 %chan.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.start.p0(ptr nonnull %chan.value)
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3 %0 = call i1 @runtime.chanRecv(ptr %ch, ptr nonnull %chan.value, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %chan.value) call void @llvm.lifetime.end.p0(ptr nonnull %chan.value)
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -55,9 +55,9 @@ declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferen
define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanZeroSend(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 call void @runtime.chanSend(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -65,9 +65,9 @@ entry:
define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 { define hidden void @main.chanZeroRecv(ptr dereferenceable_or_null(36) %ch, ptr %context) unnamed_addr #1 {
entry: entry:
%chan.op = alloca %runtime.channelOp, align 8 %chan.op = alloca %runtime.channelOp, align 8
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.start.p0(ptr nonnull %chan.op)
%0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3 %0 = call i1 @runtime.chanRecv(ptr %ch, ptr null, ptr nonnull %chan.op, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %chan.op) call void @llvm.lifetime.end.p0(ptr nonnull %chan.op)
ret void ret void
} }
@@ -77,7 +77,7 @@ entry:
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8 %select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
%select.send.value = alloca i32, align 4 %select.send.value = alloca i32, align 4
store i32 1, ptr %select.send.value, align 4 store i32 1, ptr %select.send.value, align 4
call void @llvm.lifetime.start.p0(i64 16, ptr nonnull %select.states.alloca) call void @llvm.lifetime.start.p0(ptr nonnull %select.states.alloca)
store ptr %ch1, ptr %select.states.alloca, align 4 store ptr %ch1, ptr %select.states.alloca, align 4
%select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4 %select.states.alloca.repack1 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 4
store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4 store ptr %select.send.value, ptr %select.states.alloca.repack1, align 4
@@ -86,7 +86,7 @@ entry:
%.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12 %.repack3 = getelementptr inbounds nuw i8, ptr %select.states.alloca, i32 12
store ptr null, ptr %.repack3, align 4 store ptr null, ptr %.repack3, align 4
%select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3 %select.result = call { i32, i1 } @runtime.chanSelect(ptr undef, ptr nonnull %select.states.alloca, i32 2, i32 2, ptr null, i32 0, i32 0, ptr undef) #3
call void @llvm.lifetime.end.p0(i64 16, ptr nonnull %select.states.alloca) call void @llvm.lifetime.end.p0(ptr nonnull %select.states.alloca)
%1 = extractvalue { i32, i1 } %select.result, 0 %1 = extractvalue { i32, i1 } %select.result, 0
%2 = icmp eq i32 %1, 0 %2 = icmp eq i32 %1, 0
br i1 %2, label %select.done, label %select.next br i1 %2, label %select.done, label %select.next
+5 -5
View File
@@ -15,7 +15,7 @@ target triple = "wasm32-unknown-wasi"
@"reflect/types.type:basic:string" = linkonce_odr constant { i8, ptr } { i8 81, ptr @"reflect/types.type:pointer:basic:string" }, align 4 @"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 @"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 declare void @runtime.trackPointer(ptr nocapture readonly, ptr, ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define hidden void @main.init(ptr %context) unnamed_addr #1 { define hidden void @main.init(ptr %context) unnamed_addr #1 {
@@ -31,7 +31,7 @@ entry:
} }
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: readwrite) ; 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 declare void @llvm.memcpy.p0.p0.i32(ptr noalias nocapture writeonly, ptr noalias nocapture readonly, i32, i1 immarg) #2
; Function Attrs: nounwind ; 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 { 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 {
@@ -344,7 +344,7 @@ if.then: ; preds = %typeassert.next
declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0 declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0
; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write) ; Function Attrs: nocallback nofree nounwind willreturn memory(argmem: write)
declare void @llvm.memset.p0.i32(ptr writeonly captures(none), i8, i32, i1 immarg) #7 declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #7
; Function Attrs: nounwind ; 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 { define hidden i8 @main.useLargeMap(ptr readonly dereferenceable_or_null(1025) %key, ptr readonly dereferenceable_or_null(1025) %value, ptr %context) unnamed_addr #1 {
@@ -423,12 +423,12 @@ if.then: ; preds = %entry
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(ptr captures(none)) #8 declare void @llvm.lifetime.start.p0(ptr nocapture) #8
declare void @runtime.chanSend(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 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) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(ptr captures(none)) #8 declare void @llvm.lifetime.end.p0(ptr nocapture) #8
declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0 declare i1 @runtime.chanRecv(ptr dereferenceable_or_null(36), ptr, ptr dereferenceable_or_null(16), ptr) #0
+18 -18
View File
@@ -21,23 +21,23 @@ entry:
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 4 store %main.hasPadding %2, ptr %hashmap.key, align 4
%3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 %3 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
%4 = load i32, ptr %hashmap.value, align 4 %4 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret i32 %4 ret i32 %4
} }
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.start.p0(ptr nocapture) #3
declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0 declare i1 @runtime.hashmapGenericGet(ptr dereferenceable_or_null(48), ptr nocapture, ptr nocapture, i32, ptr) #0
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) ; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #3 declare void @llvm.lifetime.end.p0(ptr nocapture) #3
; Function Attrs: noinline nounwind ; Function Attrs: noinline nounwind
define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 { define hidden void @main.testZeroSet(ptr dereferenceable_or_null(48) %m, i1 %s.b1, i32 %s.i, i1 %s.b2, ptr %context) unnamed_addr #2 {
@@ -47,13 +47,13 @@ entry:
%0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0 %0 = insertvalue %main.hasPadding zeroinitializer, i1 %s.b1, 0
%1 = insertvalue %main.hasPadding %0, i32 %s.i, 1 %1 = insertvalue %main.hasPadding %0, i32 %s.i, 1
%2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2 %2 = insertvalue %main.hasPadding %1, i1 %s.b2, 2
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4 store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
store %main.hasPadding %2, ptr %hashmap.key, align 4 store %main.hasPadding %2, ptr %hashmap.key, align 4
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 12, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret void ret void
} }
@@ -64,17 +64,17 @@ define hidden i32 @main.testZeroArrayGet(ptr dereferenceable_or_null(48) %m, [2
entry: entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4 %hashmap.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0 %s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
%0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4 %0 = call i1 @runtime.hashmapGenericGet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, i32 4, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
%1 = load i32, ptr %hashmap.value, align 4 %1 = load i32, ptr %hashmap.value, align 4
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret i32 %1 ret i32 %1
} }
@@ -83,17 +83,17 @@ define hidden void @main.testZeroArraySet(ptr dereferenceable_or_null(48) %m, [2
entry: entry:
%hashmap.key = alloca [2 x %main.hasPadding], align 8 %hashmap.key = alloca [2 x %main.hasPadding], align 8
%hashmap.value = alloca i32, align 4 %hashmap.value = alloca i32, align 4
call void @llvm.lifetime.start.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.value)
store i32 5, ptr %hashmap.value, align 4 store i32 5, ptr %hashmap.value, align 4
call void @llvm.lifetime.start.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.start.p0(ptr nonnull %hashmap.key)
%s.elt = extractvalue [2 x %main.hasPadding] %s, 0 %s.elt = extractvalue [2 x %main.hasPadding] %s, 0
store %main.hasPadding %s.elt, ptr %hashmap.key, align 4 store %main.hasPadding %s.elt, ptr %hashmap.key, align 4
%hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12 %hashmap.key.repack1 = getelementptr inbounds nuw i8, ptr %hashmap.key, i32 12
%s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1 %s.elt2 = extractvalue [2 x %main.hasPadding] %s, 1
store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4 store %main.hasPadding %s.elt2, ptr %hashmap.key.repack1, align 4
call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4 call void @runtime.hashmapGenericSet(ptr %m, ptr nonnull %hashmap.key, ptr nonnull %hashmap.value, ptr undef) #4
call void @llvm.lifetime.end.p0(i64 24, ptr nonnull %hashmap.key) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.key)
call void @llvm.lifetime.end.p0(i64 4, ptr nonnull %hashmap.value) call void @llvm.lifetime.end.p0(ptr nonnull %hashmap.value)
ret void ret void
} }
+8 -1
View File
@@ -177,8 +177,15 @@ func Build(pkgName, outpath string, config *compileopts.Config) error {
// Pick a default output path based on the main directory. // Pick a default output path based on the main directory.
outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension() outpath = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
} }
} else if fi, statErr := os.Stat(outpath); statErr == nil && fi.IsDir() {
var name string
if strings.HasSuffix(pkgName, ".go") {
name = filepath.Base(pkgName[:len(pkgName)-3]) + config.DefaultBinaryExtension()
} else {
name = filepath.Base(result.MainDir) + config.DefaultBinaryExtension()
}
outpath = filepath.Join(outpath, name)
} }
if err := os.Rename(result.Binary, outpath); err != nil { if err := os.Rename(result.Binary, outpath); err != nil {
// Moving failed. Do a file copy. // Moving failed. Do a file copy.
inf, err := os.Open(result.Binary) inf, err := os.Open(result.Binary)
+30
View File
@@ -261,6 +261,21 @@ func TestTimerStopResetRace(t *testing.T) {
runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil) runTest("timer_stop_reset_race.go", optionsFromTarget("", sema), t, nil, nil)
} }
func TestESP32QEMU(t *testing.T) {
t.Parallel()
options := optionsFromTarget("esp32-qemu", sema)
emuCheck(t, options)
machines, err := exec.Command("qemu-system-xtensa", "-machine", "help").Output()
if err != nil {
t.Fatal("failed to list qemu-system-xtensa machines:", err)
}
if !regexp.MustCompile(`(?m)^esp32\s`).Match(machines) {
t.Skip("qemu-system-xtensa does not support the ESP32 machine")
}
runTest("print.go", options, t, nil, nil)
}
func runPlatTests(options compileopts.Options, tests []string, t *testing.T) { func runPlatTests(options compileopts.Options, tests []string, t *testing.T) {
emuCheck(t, options) emuCheck(t, options)
@@ -539,6 +554,9 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
if config.EmulatorName() == "simavr" { if config.EmulatorName() == "simavr" {
actual = cleanSimAVRTestOutput(actual) actual = cleanSimAVRTestOutput(actual)
} }
if config.EmulatorName() == "qemu-system-xtensa" {
actual = cleanESP32QEMUOutput(actual)
}
if name == "testing.go" { if name == "testing.go" {
// Strip actual time. // Strip actual time.
re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`) re := regexp.MustCompile(`\([0-9]\.[0-9][0-9]s\)`)
@@ -564,6 +582,18 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
} }
} }
func cleanESP32QEMUOutput(output []byte) []byte {
entryLine := bytes.Index(output, []byte("\nentry "))
if entryLine < 0 {
return output
}
entryLineEnd := bytes.IndexByte(output[entryLine+1:], '\n')
if entryLineEnd < 0 {
return output
}
return output[entryLine+1+entryLineEnd+1:]
}
func cleanSimAVRTestOutput(output []byte) []byte { func cleanSimAVRTestOutput(output []byte) []byte {
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil) output = bytes.ReplaceAll(output, []byte{0x1b, '[', '3', '2', 'm'}, nil)
output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil) output = bytes.ReplaceAll(output, []byte{0x1b, '[', '0', 'm'}, nil)
+27 -13
View File
@@ -68,14 +68,20 @@ tinygo_swapTask:
// After the recursive spill returns, the physical register file still // After the recursive spill returns, the physical register file still
// has WindowStart bits set for the spill helper frames. // has WindowStart bits set for the spill helper frames.
// We will clear WindowStart completely (to 0) right before the retw.n // We will drop every WindowStart bit except this window's own
// below, after the stack switch is done. This prevents stale overflow // (WINDOWSTART = 1 << WINDOWBASE) right before the retw.n below, after the
// when the new goroutine's calls rotate back into these panes. // stack switch is done. This prevents stale overflow when the new
// goroutine's calls rotate back into these panes.
// Restore interrupts. // Reload the saved PS, but do NOT restore it yet: everything from the stack
// switch below until WindowStart is made consistent with the new stack must
// stay masked. Once sp points at the new task's stack, the memory below sp
// is that task's save area -- the a0-a3 the retw.n is about to reload
// through a window underflow. An interrupt landing in that gap builds its
// frame there and overwrites them, and a garbage a0 makes the next retw an
// Illegal Instruction. The PS restore below is therefore deferred to just
// before the retw.n, once the window state matches the new stack.
l32i a4, sp, 4 // reload saved PS l32i a4, sp, 4 // reload saved PS
wsr.ps a4
rsync
// At this point, the following is true: // At this point, the following is true:
// WindowStart == 1 << WindowBase // WindowStart == 1 << WindowBase
@@ -97,16 +103,24 @@ tinygo_swapTask:
// register also stores the parent register window. // register also stores the parent register window.
l32i.n a0, sp, 0 l32i.n a0, sp, 0
// Clear ALL WindowStart bits. With all windows spilled to the stack, // Drop every WindowStart bit except this window's. Stale bits (from the
// we must ensure no stale WS bits remain: the retw.n below will trigger // spill helpers or the old goroutine) would overflow garbage registers into
// underflow4 to load the new goroutine's registers from the new stack // memory. This window's own bit has to stay set: zeroing WindowStart marks
// (which sets the appropriate WS bit via rfwu). Any stale WS bits // the running frame dead, so an interrupt arriving here returns through a
// (from spill helpers or the old goroutine) would cause spurious // retw that underflows into a save area nothing ever wrote. Keeping it
// overflows of garbage register values into memory. // costs nothing -- WindowStart[WindowBase - n] is still clear, so the
movi a5, 0 // retw.n below underflows as intended.
rsr a6, WINDOWBASE
movi a5, 1
ssl a6
sll a5, a5 // a5 = 1 << WindowBase
wsr a5, WINDOWSTART wsr a5, WINDOWSTART
rsync rsync
// Now that the new task's window state is consistent, take interrupts back.
wsr.ps a4
rsync
// Return into the new stack. This instruction will trigger a window // Return into the new stack. This instruction will trigger a window
// underflow, reloading the saved registers from the stack. // underflow, reloading the saved registers from the stack.
retw.n retw.n
+15 -1
View File
@@ -51,7 +51,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos)
// attach // attach
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) dev.Attach()
// enable interrupt for end of reset // enable interrupt for end of reset
sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST)
@@ -68,6 +68,20 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true dev.initcomplete = true
} }
// Attach connects the device to the USB bus, allowing the host to detect and
// enumerate it. It can be used together with Detach to delay enumeration
// until the USB configuration (device identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
}
// Detach disconnects the device from the USB bus. To the host this appears
// as if the device was unplugged. A subsequent Attach makes the host
// enumerate the device again.
func (dev *USBDevice) Detach() {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_DETACH)
}
func handlePadCalibration() { func handlePadCalibration() {
// Load Pad Calibration data from non-volatile memory // Load Pad Calibration data from non-volatile memory
// This requires registers that are not included in the SVD file. // This requires registers that are not included in the SVD file.
+15 -1
View File
@@ -51,7 +51,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos) sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_SPDCONF_FS << sam.USB_DEVICE_CTRLB_SPDCONF_Pos)
// attach // attach
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH) dev.Attach()
// enable interrupt for end of reset // enable interrupt for end of reset
sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST) sam.USB_DEVICE.INTENSET.SetBits(sam.USB_DEVICE_INTENSET_EORST)
@@ -71,6 +71,20 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true dev.initcomplete = true
} }
// Attach connects the device to the USB bus, allowing the host to detect and
// enumerate it. It can be used together with Detach to delay enumeration
// until the USB configuration (device identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
sam.USB_DEVICE.CTRLB.ClearBits(sam.USB_DEVICE_CTRLB_DETACH)
}
// Detach disconnects the device from the USB bus. To the host this appears
// as if the device was unplugged. A subsequent Attach makes the host
// enumerate the device again.
func (dev *USBDevice) Detach() {
sam.USB_DEVICE.CTRLB.SetBits(sam.USB_DEVICE_CTRLB_DETACH)
}
func handlePadCalibration() { func handlePadCalibration() {
// Load Pad Calibration data from non-volatile memory // Load Pad Calibration data from non-volatile memory
// This requires registers that are not included in the SVD file. // This requires registers that are not included in the SVD file.
+59
View File
@@ -112,6 +112,11 @@ func (p Pin) configure(config PinConfig, signal uint32) {
// Configure the pad with the given IO mux configuration. // Configure the pad with the given IO mux configuration.
p.mux().Set(muxConfig) p.mux().Set(muxConfig)
// Internal pull resistors for pins with RTC function ignore
// the IO_MUX_GPIO0_FUN_WPU and IO_MUX_GPIO0_FUN_WPD bits set
// above and are instead controlled by the RTC_IO registers.
p.configureRTCPull(config.Mode)
switch config.Mode { switch config.Mode {
case PinOutput: case PinOutput:
// Set the 'output enable' bit. // Set the 'output enable' bit.
@@ -293,6 +298,60 @@ func (p Pin) mux() *volatile.Register32 {
} }
} }
// configureRTCPull applies the pullup/pulldown setting to a pin.
// This mirrors ESP-IDF:
// https://github.com/espressif/esp-idf/blob/08e0d30/components/esp_driver_gpio/src/gpio.c#L283
// https://github.com/espressif/esp-idf/blob/08e0d30/components/esp_hal_gpio/esp32/rtc_io_periph.c#L57
// https://github.com/espressif/esp-idf/blob/08e0d30/components/esp_hal_gpio/esp32/include/hal/rtc_io_ll.h#L175
func (p Pin) configureRTCPull(mode PinMode) {
var rue, rde uint32
switch mode {
case PinInputPullup:
rue = 1
case PinInputPulldown:
rde = 1
}
switch p {
case 0:
esp.RTC_IO.SetTOUCH_PAD1_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD1_RDE(rde)
case 2:
esp.RTC_IO.SetTOUCH_PAD2_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD2_RDE(rde)
case 4:
esp.RTC_IO.SetTOUCH_PAD0_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD0_RDE(rde)
case 12:
esp.RTC_IO.SetTOUCH_PAD5_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD5_RDE(rde)
case 13:
esp.RTC_IO.SetTOUCH_PAD4_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD4_RDE(rde)
case 14:
esp.RTC_IO.SetTOUCH_PAD6_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD6_RDE(rde)
case 15:
esp.RTC_IO.SetTOUCH_PAD3_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD3_RDE(rde)
case 25:
esp.RTC_IO.SetPAD_DAC1_PDAC1_RUE(rue)
esp.RTC_IO.SetPAD_DAC1_PDAC1_RDE(rde)
case 26:
esp.RTC_IO.SetPAD_DAC2_PDAC2_RUE(rue)
esp.RTC_IO.SetPAD_DAC2_PDAC2_RDE(rde)
case 27:
esp.RTC_IO.SetTOUCH_PAD7_RUE(rue)
esp.RTC_IO.SetTOUCH_PAD7_RDE(rde)
case 32:
esp.RTC_IO.SetXTAL_32K_PAD_X32P_RUE(rue)
esp.RTC_IO.SetXTAL_32K_PAD_X32P_RDE(rde)
case 33:
esp.RTC_IO.SetXTAL_32K_PAD_X32N_RUE(rue)
esp.RTC_IO.SetXTAL_32K_PAD_X32N_RDE(rde)
}
}
const maxPin = 40 const maxPin = 40
// cpuInterruptFromPin selects an edge-triggered CPU interrupt line for GPIO. // cpuInterruptFromPin selects an edge-triggered CPU interrupt line for GPIO.
+1 -3
View File
@@ -242,7 +242,6 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error {
timeoutNS := int64(timeoutMS) * 1000000 timeoutNS := int64(timeoutMS) * 1000000
needAddress := true needAddress := true
needRestart := false needRestart := false
readLast := false
var readTo []byte var readTo []byte
for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); { for cmdIdx, reg := 0, &i2c.Bus.COMD0; cmdIdx < len(cmd); {
c := &cmd[cmdIdx] c := &cmd[cmdIdx]
@@ -310,7 +309,6 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error {
} }
if split { if split {
readLast = true
reg.Set(i2cCMD_READLAST | 1) reg.Set(i2cCMD_READLAST | 1)
reg = nextAddress(reg) reg = nextAddress(reg)
readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte readTo = c.data[c.head : c.head+bytes+1] // read bytes + 1 last byte
@@ -342,7 +340,7 @@ func (i2c *I2C) transmit(addr uint16, cmd []i2cCommand, timeoutMS int) error {
} }
} }
switch { switch {
case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0 && !readLast: case mask&esp.I2C_INT_STATUS_ACK_ERR_INT_ST_Msk != 0:
return errI2CAckExpected return errI2CAckExpected
case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0: case mask&esp.I2C_INT_STATUS_TIME_OUT_INT_ST_Msk != 0:
// timeout leaves the bus in an undefined state, reset // timeout leaves the bus in an undefined state, reset
+5
View File
@@ -67,6 +67,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {} func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {} func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init() // initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers. // functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call. // Actual hardware setup is deferred to the first Configure() call.
+5
View File
@@ -66,6 +66,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {} func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {} func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init() // initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers. // functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call. // Actual hardware setup is deferred to the first Configure() call.
+5
View File
@@ -67,6 +67,11 @@ func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {} func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {} func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// Attach and Detach are no-ops: the USB Serial/JTAG controller has no
// software-controlled soft-connect, it is always attached to the bus.
func (dev *USBDevice) Attach() {}
func (dev *USBDevice) Detach() {}
// initUSB is intentionally empty — the interp phase evaluates init() // initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers. // functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call. // Actual hardware setup is deferred to the first Configure() call.
+24
View File
@@ -22,6 +22,11 @@ var (
epinen uint32 epinen uint32
epouten uint32 epouten uint32
easyDMABusy volatile.Register8 easyDMABusy volatile.Register8
// usbDetached keeps the device detached from the bus after Detach: the
// USB IRQ handler re-enables the DP pull-up on every power-ready event,
// which would otherwise silently undo a Detach.
usbDetached bool
) )
// enterCriticalSection is used to protect access to easyDMA - only one thing // enterCriticalSection is used to protect access to easyDMA - only one thing
@@ -89,6 +94,23 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true dev.initcomplete = true
} }
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
usbDetached = false
nrf.USBD.USBPULLUP.Set(1)
}
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
usbDetached = true
nrf.USBD.USBPULLUP.Set(0)
}
func handleUSBIRQ(interrupt.Interrupt) { func handleUSBIRQ(interrupt.Interrupt) {
if nrf.USBD.EVENTS_SOF.Get() == 1 { if nrf.USBD.EVENTS_SOF.Get() == 1 {
nrf.USBD.EVENTS_SOF.Set(0) nrf.USBD.EVENTS_SOF.Set(0)
@@ -103,7 +125,9 @@ func handleUSBIRQ(interrupt.Interrupt) {
// Configure control endpoint // Configure control endpoint
initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL) initEndpoint(0, usb.ENDPOINT_TYPE_CONTROL)
if !usbDetached {
nrf.USBD.USBPULLUP.Set(1) nrf.USBD.USBPULLUP.Set(1)
}
usbConfiguration = 0 usbConfiguration = 0
} }
+15
View File
@@ -43,9 +43,24 @@ func (dev *USBDevice) Configure(config UARTConfig) {
rp.USBCTRL_REGS_INTE_SETUP_REQ) rp.USBCTRL_REGS_INTE_SETUP_REQ)
// Present full speed device by enabling pull up on DP // Present full speed device by enabling pull up on DP
dev.Attach()
}
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
rp.USBCTRL_REGS.SIE_CTRL.SetBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN) rp.USBCTRL_REGS.SIE_CTRL.SetBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN)
} }
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
rp.USBCTRL_REGS.SIE_CTRL.ClearBits(rp.USBCTRL_REGS_SIE_CTRL_PULLUP_EN)
}
func handleUSBIRQ(intr interrupt.Interrupt) { func handleUSBIRQ(intr interrupt.Interrupt) {
status := rp.USBCTRL_REGS.INTS.Get() status := rp.USBCTRL_REGS.INTS.Get()
+16 -1
View File
@@ -43,12 +43,27 @@ func (dev *USBDevice) Configure(config UARTConfig) {
rp.USB_INTE_SETUP_REQ) rp.USB_INTE_SETUP_REQ)
// Present full speed device by enabling pull up on DP // Present full speed device by enabling pull up on DP
rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN) dev.Attach()
// 12.7.2 Disable phy isolation // 12.7.2 Disable phy isolation
rp.USB.SetMAIN_CTRL_PHY_ISO(0x0) rp.USB.SetMAIN_CTRL_PHY_ISO(0x0)
} }
// Attach connects the device to the USB bus by enabling the DP pull-up,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
rp.USB.SIE_CTRL.SetBits(rp.USB_SIE_CTRL_PULLUP_EN)
}
// Detach disconnects the device from the USB bus by disabling the DP pull-up.
// To the host this appears as if the device was unplugged. A subsequent
// Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
rp.USB.SIE_CTRL.ClearBits(rp.USB_SIE_CTRL_PULLUP_EN)
}
func handleUSBIRQ(intr interrupt.Interrupt) { func handleUSBIRQ(intr interrupt.Interrupt) {
status := rp.USB.INTS.Get() status := rp.USB.INTS.Get()
+17 -2
View File
@@ -254,7 +254,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
otgPower.PCGCCTL.Set(0) otgPower.PCGCCTL.Set(0)
// Soft-disconnect now (after CSRST reset DCTL to its default connected state). // Soft-disconnect now (after CSRST reset DCTL to its default connected state).
otgDevice.DCTL.SetBits(dctlSDIS) dev.Detach()
// ---- 7. Configure data FIFOs -------------------------------------------- // ---- 7. Configure data FIFOs --------------------------------------------
@@ -311,11 +311,26 @@ func (dev *USBDevice) Configure(config UARTConfig) {
// ---- 12. Connect to host (clear soft-disconnect) ----------------------- // ---- 12. Connect to host (clear soft-disconnect) -----------------------
otgDevice.DCTL.ClearBits(dctlSDIS) dev.Attach()
dev.initcomplete = true dev.initcomplete = true
} }
// Attach connects the device to the USB bus by releasing soft disconnect,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
otgDevice.DCTL.ClearBits(dctlSDIS)
}
// Detach disconnects the device from the USB bus by asserting soft
// disconnect. To the host this appears as if the device was unplugged. A
// subsequent Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
otgDevice.DCTL.SetBits(dctlSDIS)
}
// handleUSBIRQ is the OTG FS interrupt handler, dispatching on GINTSTS bits. // handleUSBIRQ is the OTG FS interrupt handler, dispatching on GINTSTS bits.
func handleUSBIRQ(intr interrupt.Interrupt) { func handleUSBIRQ(intr interrupt.Interrupt) {
status := stm32.OTG_FS_GLOBAL.GINTSTS.Get() & status := stm32.OTG_FS_GLOBAL.GINTSTS.Get() &
+16 -1
View File
@@ -206,7 +206,7 @@ func (dev *USBDevice) Configure(config UARTConfig) {
// Stay soft-disconnected until configuration is complete; CSRST left // Stay soft-disconnected until configuration is complete; CSRST left
// DCTL at its default "connected" state. // DCTL at its default "connected" state.
usbOTG.DCTL.SetBits(DCTL_SDIS) dev.Detach()
// 5. Force device mode now that the core is out of reset. The mode // 5. Force device mode now that the core is out of reset. The mode
// change takes effect only after up to 25 ms (RM0433); poll GINTSTS.CMOD // change takes effect only after up to 25 ms (RM0433); poll GINTSTS.CMOD
@@ -265,9 +265,24 @@ func (dev *USBDevice) Configure(config UARTConfig) {
dev.initcomplete = true dev.initcomplete = true
// Release soft-disconnect: pulls D+ high, making device visible to host. // Release soft-disconnect: pulls D+ high, making device visible to host.
dev.Attach()
}
// Attach connects the device to the USB bus by releasing soft disconnect,
// allowing the host to detect and enumerate it. It can be used together with
// Detach to delay enumeration until the USB configuration (device
// identifiers, classes, ...) is complete.
func (dev *USBDevice) Attach() {
usbOTG.DCTL.ClearBits(DCTL_SDIS) usbOTG.DCTL.ClearBits(DCTL_SDIS)
} }
// Detach disconnects the device from the USB bus by asserting soft
// disconnect. To the host this appears as if the device was unplugged. A
// subsequent Attach makes the host enumerate the device again.
func (dev *USBDevice) Detach() {
usbOTG.DCTL.SetBits(DCTL_SDIS)
}
func initEndpoint(ep, config uint32) { func initEndpoint(ep, config uint32) {
if ep == 0 { if ep == 0 {
// Control endpoint // Control endpoint
+11
View File
@@ -163,6 +163,17 @@ func (f *File) Truncate(size int64) (err error) {
return Truncate(f.name, size) return Truncate(f.name, size)
} }
// Chown changes the numeric uid and gid of the named file.
// A uid or gid of -1 means to not change that value.
// If there is an error, it will be of type *PathError.
func (f *File) Chown(uid, gid int) error {
if f.handle == nil {
return ErrClosed
}
return Chown(f.name, uid, gid)
}
func (f *File) chmod(mode FileMode) error { func (f *File) chmod(mode FileMode) error {
if f.handle == nil { if f.handle == nil {
return ErrClosed return ErrClosed
+6
View File
@@ -0,0 +1,6 @@
package os
import "syscall"
// Getpagesize returns the underlying system's memory page size.
func Getpagesize() int { return syscall.Getpagesize() }
-5
View File
@@ -6,11 +6,6 @@
package os package os
import "syscall"
// Getpagesize returns the underlying system's memory page size.
func Getpagesize() int { return syscall.Getpagesize() }
func (fs *fileStat) Name() string { return fs.name } func (fs *fileStat) Name() string { return fs.name }
func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() } func (fs *fileStat) IsDir() bool { return fs.Mode().IsDir() }
+10 -1
View File
@@ -56,6 +56,7 @@ var (
endBlock gcBlock // the block just past the end of the available space endBlock gcBlock // the block just past the end of the available space
gcTotalAlloc uint64 // total number of bytes allocated gcTotalAlloc uint64 // total number of bytes allocated
gcMallocs uint64 // total number of allocations gcMallocs uint64 // total number of allocations
gcNumGC uint32 // total number of completed collection cycles
gcLock task.PMutex // lock to avoid race conditions on multicore systems gcLock task.PMutex // lock to avoid race conditions on multicore systems
) )
@@ -510,7 +511,7 @@ func realloc(ptr unsafe.Pointer, size uintptr) unsafe.Pointer {
lastBlock := firstBlock.findHead() lastBlock := firstBlock.findHead()
// Calculate the size of the original allocation body. // Calculate the size of the original allocation body.
oldSize := uintptr(lastBlock-firstBlock)*blocksPerStateByte + (bytesPerBlock - unsafe.Sizeof(objHeader{})) oldSize := uintptr(lastBlock-firstBlock)*bytesPerBlock + (bytesPerBlock - unsafe.Sizeof(objHeader{}))
if size <= oldSize { if size <= oldSize {
// The requested size is less than the old size. // The requested size is less than the old size.
@@ -610,6 +611,11 @@ func runGC() (freeBytes uintptr) {
dumpHeap() dumpHeap()
} }
// The cycle is complete. Counted here rather than in GC() so that
// collections triggered by an allocation are counted too. Every caller
// holds gcLock, the same lock ReadMemStats reads it under.
gcNumGC++
return return
} }
@@ -850,6 +856,9 @@ func ReadMemStats(m *MemStats) {
// Record the total allocated bytes. // Record the total allocated bytes.
m.TotalAlloc = gcTotalAlloc m.TotalAlloc = gcTotalAlloc
// Record the number of completed collection cycles.
m.NumGC = gcNumGC
gcLock.Unlock() gcLock.Unlock()
} }
+1
View File
@@ -129,6 +129,7 @@ func ReadMemStats(m *MemStats) {
m.Mallocs = 0 // not provided by bdwgc m.Mallocs = 0 // not provided by bdwgc
m.Frees = 0 // not provided by bdwgc m.Frees = 0 // not provided by bdwgc
m.Sys = uint64(gcMemStats.obtained_from_os_bytes) m.Sys = uint64(gcMemStats.obtained_from_os_bytes)
m.NumGC = uint32(gcMemStats.gc_no)
gcLock.Unlock() gcLock.Unlock()
} }
+8 -1
View File
@@ -47,8 +47,14 @@ func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
gcTotalAlloc += uint64(size) gcTotalAlloc += uint64(size)
gcMallocs++ gcMallocs++
heapptr += size heapptr += size
for heapptr >= heapEnd { if heapptr < addr {
// The allocation size overflowed the heap pointer.
runtimePanic("out of memory")
}
for heapptr > heapEnd {
// Try to increase the heap and check again. // Try to increase the heap and check again.
// Use > instead of >= because an allocation that exactly
// ends at heapEnd is still within heap boundaries.
if growHeap() { if growHeap() {
continue continue
} }
@@ -103,6 +109,7 @@ func ReadMemStats(m *MemStats) {
m.HeapAlloc = gcTotalAlloc m.HeapAlloc = gcTotalAlloc
m.HeapObjects = gcMallocs m.HeapObjects = gcMallocs
m.Alloc = m.HeapAlloc m.Alloc = m.HeapAlloc
m.NumGC = 0 // this GC never collects, so no cycle ever completes
gcLock.Unlock() gcLock.Unlock()
} }
+6 -7
View File
@@ -43,6 +43,12 @@ func gcMarkReachable() {
gcPauseCore(i) gcPauseCore(i)
} }
// Busy-wait until all the other cores are ready.
for gcScanState.Load() != numCPU {
spinLoopWait()
}
gcScanState.Store(0)
// Scan the stack(s) of the current core. // Scan the stack(s) of the current core.
scanCurrentStack() scanCurrentStack()
if !task.OnSystemStack() { if !task.OnSystemStack() {
@@ -53,13 +59,6 @@ func gcMarkReachable() {
// Scan globals. // Scan globals.
findGlobals(markRoots) findGlobals(markRoots)
// Busy-wait until all the other cores are ready. They certainly should be,
// after the scanning we did above.
for gcScanState.Load() != numCPU {
spinLoopWait()
}
gcScanState.Store(0)
// Signal each core in turn that they can scan the stack. // Signal each core in turn that they can scan the stack.
for i := uint32(0); i < numCPU; i++ { for i := uint32(0); i < numCPU; i++ {
if i == core { if i == core {
+2 -2
View File
@@ -807,13 +807,13 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 {
case reflectlite.Array: case reflectlite.Array:
var hash uint32 var hash uint32
for i := 0; i < x.Len(); i++ { for i := 0; i < x.Len(); i++ {
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) hash = (hash * 31) ^ hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed)
} }
return hash return hash
case reflectlite.Struct: case reflectlite.Struct:
var hash uint32 var hash uint32
for i := 0; i < x.NumField(); i++ { for i := 0; i < x.NumField(); i++ {
hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) hash = (hash * 31) ^ hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed)
} }
return hash return hash
default: default:
+5
View File
@@ -82,4 +82,9 @@ type MemStats struct {
// GCSys is bytes of memory in garbage collection metadata. // GCSys is bytes of memory in garbage collection metadata.
GCSys uint64 GCSys uint64
// NumGC is the number of completed GC cycles.
//
// The leaking collector never collects, so it always reports 0.
NumGC uint32
} }
-6
View File
@@ -78,12 +78,6 @@ var _ebss [0]byte
//go:extern _vector_table //go:extern _vector_table
var _vector_table [0]uintptr var _vector_table [0]uintptr
func abort() {
for {
device.Asm("waiti 0")
}
}
// interruptInit installs the Xtensa vector table by writing its address // interruptInit installs the Xtensa vector table by writing its address
// to the VECBASE special register and ensures all CPU interrupts are // to the VECBASE special register and ensures all CPU interrupts are
// initially disabled. // initially disabled.
+11
View File
@@ -0,0 +1,11 @@
//go:build esp32 && !qemu
package runtime
import "device"
func abort() {
for {
device.Asm("waiti 0")
}
}
+28
View File
@@ -0,0 +1,28 @@
//go:build esp32 && qemu
package runtime
import "device"
func exit(code int) {
qemuExit(code)
}
func abort() {
qemuExit(1)
}
func qemuExit(code int) {
// QEMU semihosting expects a2 = SYS_exit (1) and a3 = the exit code.
// AsmFull cannot declare these clobbers, so no Go code may run afterward.
device.AsmFull(
"mov a3, {code}\n"+
"movi a2, 1\n"+
"simcall",
map[string]interface{}{"code": code},
)
// This loop ensures the clobbered registers are never used again.
for {
device.Asm("waiti 0")
}
}
-4
View File
@@ -55,10 +55,6 @@ func ticksToNanoseconds(ticks timeUnit) int64 {
return int64(ticks) * 25 return int64(ticks) * 25
} }
func exit(code int) {
abort()
}
func putchar(c byte) { func putchar(c byte) {
machine.Serial.WriteByte(c) machine.Serial.WriteByte(c)
} }
+7
View File
@@ -0,0 +1,7 @@
//go:build (esp32 && !qemu) || esp32c3 || esp32c6
package runtime
func exit(code int) {
abort()
}
+39
View File
@@ -0,0 +1,39 @@
//go:build baremetal || js || wasip1 || wasip2 || wasm_unknown || nintendoswitch
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package syscall
// Statfs_t and Fsid have been copied from the Go source tree
// (syscall/ztypes_linux_amd64.go).
type Statfs_t struct {
Type int64
Bsize int64
Blocks uint64
Bfree uint64
Bavail uint64
Files uint64
Ffree uint64
Fsid Fsid
Namelen int64
Frsize int64
Flags int64
Spare [4]int64
}
type Fsid struct {
X__val [2]int32
}
// This is a stub, it is not functional.
func Statfs(path string, buf *Statfs_t) (err error) {
return ENOSYS
}
// This is a stub, it is not functional.
func Fstatfs(fd int, buf *Statfs_t) (err error) {
return ENOSYS
}
+5
View File
@@ -0,0 +1,5 @@
{
"inherits": ["esp32-generic"],
"build-tags": ["qemu"],
"emulator": "qemu-system-xtensa -machine esp32 -nographic -semihosting -drive file={img},if=mtd,format=raw"
}
-1
View File
@@ -25,6 +25,5 @@
], ],
"binary-format": "esp32", "binary-format": "esp32",
"flash-method": "esp32flash", "flash-method": "esp32flash",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"] "gdb": ["xtensa-esp32-elf-gdb"]
} }
+82 -3
View File
@@ -299,13 +299,37 @@ _handle_kernel_exc:
.global _handle_level1 .global _handle_level1
_handle_level1: _handle_level1:
// --- allocate 96-byte exception frame on the interrupted stack --- // --- EXCCAUSE 5 (alloca / MOVSP) is not a fault, it is a request ---
//
// MOVSP raises this whenever it moves the stack pointer while the caller's
// register window is still live in the register file: the hardware is
// asking us to spill that window, not reporting an error. LLVM emits MOVSP
// for any call that needs a 7th argument on the stack (the windowed ABI
// passes six in registers), so it shows up in ordinary C and Go code alike.
//
// Without this, such a call takes a fatal user exception. The check goes
// first, before any state is touched, so a0 is still in EXCSAVE1 where
// _xt_alloca_exc wants it and every other cause falls through to the
// handler below completely unchanged.
rsr a0, EXCCAUSE
bnei a0, 5, 1f
j _xt_alloca_exc
1:
rsr a0, EXCSAVE1 // restore a0 clobbered by the EXCCAUSE read
// --- allocate the exception frame on the interrupted stack ---
// Layout (offsets from a1 after adjustment): // Layout (offsets from a1 after adjustment):
// 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5 // 0: a0 4: a1(orig) 8: a2 12: a3 16: a4 20: a5
// 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11 // 24: a6 28: a7 32: a8 36: a9 40: a10 44: a11
// 48: a12 52: a13 56: a14 60: a15 // 48: a12 52: a13 56: a14 60: a15
// 64: SAR 68: EPC1 72: PS // 64: SAR 68: EPC1 72: PS 76: WINDOWBASE 80: WINDOWSTART
addi a0, a1, -96 // a0 = new frame pointer //
// 128, not the 84 bytes the layout above needs: the 16 bytes below the
// interrupted SP are the windowed-ABI caller save area, written by window
// overflow and read back by underflow. Clobbering them corrupts the
// interrupted code's a0, which surfaces later as an Illegal Instruction on
// its retw. ESP-IDF reserves the same gap (the 0x20 in XT_STK_FRMSZ).
addi a0, a1, -128 // a0 = new frame pointer
s32i a1, a0, 4 // save original a1 (SP) s32i a1, a0, 4 // save original a1 (SP)
mov a1, a0 // a1 = frame pointer mov a1, a0 // a1 = frame pointer
@@ -339,6 +363,15 @@ _handle_level1:
// level-1 interrupts. // level-1 interrupts.
rsr a2, PS rsr a2, PS
s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware) s32i a2, a1, 72 // save PS (with EXCM=1 set by hardware)
// Window state as it was at the fault. Reading it from the C handler
// instead describes the handler: getting there costs a callx4, and the
// printfs rotate and spill windows of their own.
rsr a3, WINDOWBASE
s32i a3, a1, 76
rsr a3, WINDOWSTART
s32i a3, a1, 80
movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4) movi a3, ~0x1F // mask: clear INTLEVEL (bits 0-3) + EXCM (bit 4)
and a2, a2, a3 and a2, a2, a3
movi a3, 1 // INTLEVEL = 1 movi a3, 1 // INTLEVEL = 1
@@ -412,6 +445,52 @@ _handle_level1:
rfe rfe
// -----------------------------------------------------------------------
// Alloca (MOVSP) exception handler.
//
// Rotates back to the window that executed MOVSP, fixes up PS.OWB to match
// the new WINDOWBASE, and falls into the window underflow handler that
// corresponds to the caller's call size. The underflow handler spills the
// window and returns to the interrupted MOVSP via rfwu, which then completes.
//
// All of the interruptee's registers are intact except a0, which is in
// EXCSAVE1. PS.EXCM is set by the hardware, so this cannot be interrupted.
// The registers of the base-save area are free scratch here, because taking
// this exception means they have already been spilled and the underflow
// handler will restore them.
// -----------------------------------------------------------------------
.balign 4
.global _xt_alloca_exc
_xt_alloca_exc:
rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it
rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch
rsr a2, PS
extui a3, a2, 8, 4 // a3 = PS.OWB (shift 8, 4 bits)
xor a3, a3, a4 // bits that changed from old to current WB
rsr a4, EXCSAVE1 // restore the interruptee's a0 (now in a4)
slli a3, a3, 8
xor a2, a2, a3 // flip those bits in PS.OWB
wsr a2, PS // PS.OWB now matches the new WINDOWBASE
rsync
// Dispatch on the call size encoded in the interruptee's return address:
// bits 31:30 of a0 are 00/01 for call4, 10 for call8, 11 for call12.
//
// The canonical sequence branches straight to the underflow vectors with
// _bbci.l, but that has only an 8-bit displacement and this handler lives
// outside the vector table, well out of reach. Inverting each test and
// putting the vector target on a full-range `j` is the same dispatch
// without the range limit.
_bbsi.l a4, 31, 1f
j _window_underflow4
1:
rotw -1 // interruptee's a0 moves to a8
_bbsi.l a8, 30, 2f
j _window_underflow8
2:
rotw -1
j _window_underflow12
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Default weak espradio_user_exception: infinite loop halt. // Default weak espradio_user_exception: infinite loop halt.
// Overridden by the strong definition in espradio's isr.c when linked. // Overridden by the strong definition in espradio's isr.c when linked.
-1
View File
@@ -25,6 +25,5 @@
], ],
"binary-format": "esp32s3", "binary-format": "esp32s3",
"flash-method": "esp32jtag", "flash-method": "esp32jtag",
"emulator": "qemu-system-xtensa -machine esp32 -nographic -drive file={img},if=mtd,format=raw",
"gdb": ["xtensa-esp32-elf-gdb"] "gdb": ["xtensa-esp32-elf-gdb"]
} }
+1017 -2
View File
File diff suppressed because it is too large Load Diff
+29 -24
View File
@@ -76,7 +76,7 @@ func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Modu
actual = actual[strings.Index(actual, "\ntarget datalayout = ")+1:] actual = actual[strings.Index(actual, "\ntarget datalayout = ")+1:]
if *update { if *update {
err := os.WriteFile(pathPrefix+".out.ll", []byte(actual), 0666) err := os.WriteFile(pathPrefix+".out.ll", []byte(normalizeIR(actual)), 0666)
if err != nil { if err != nil {
t.Error("failed to write out new output:", err) t.Error("failed to write out new output:", err)
} }
@@ -100,28 +100,8 @@ func testTransform(t *testing.T, pathPrefix string, transform func(mod llvm.Modu
// equal. That means, only relevant lines are compared (excluding comments // equal. That means, only relevant lines are compared (excluding comments
// etc.). // etc.).
func fuzzyEqualIR(s1, s2 string) bool { func fuzzyEqualIR(s1, s2 string) bool {
// Golden files are written using the pre-LLVM21 'nocapture' spelling, s1 = normalizeIR(s1)
// which LLVM printed before any co-occurring attribute such as s2 = normalizeIR(s2)
// '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)
// 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
// escape-analysis behavior under test, so ignore it for comparison.
s1 = strings.ReplaceAll(s1, "nocreateundeforpoison ", "")
s2 = strings.ReplaceAll(s2, "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.
s1 = lifetimeSizeArgRe.ReplaceAllString(s1, "$1")
s2 = lifetimeSizeArgRe.ReplaceAllString(s2, "$1")
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n")) lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n")) lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
@@ -138,6 +118,29 @@ func fuzzyEqualIR(s1, s2 string) bool {
return true return true
} }
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
// escape-analysis 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
}
// capturesNoneAttrRe matches a co-occurring attribute directly followed by // capturesNoneAttrRe matches a co-occurring attribute directly followed by
// 'captures(none)', which is how LLVM 21+ orders these two attributes when // 'captures(none)', which is how LLVM 21+ orders these two attributes when
// printing IR (the pre-LLVM21 'nocapture' attribute printed the other way // printing IR (the pre-LLVM21 'nocapture' attribute printed the other way
@@ -221,7 +224,9 @@ func compileGoFileForTesting(t *testing.T, filename string) llvm.Module {
// Compile AST to IR. // Compile AST to IR.
program := lprogram.LoadSSA() program := lprogram.LoadSSA()
pkg := lprogram.MainPkg() pkg := lprogram.MainPkg()
mod, errs := compiler.CompilePackage(filename, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false) ssaPkg := program.Package(pkg.Pkg)
ssaPkg.Build()
mod, errs := compiler.CompilePackage(filename, pkg, ssaPkg, machine, compilerConfig, false)
if errs != nil { if errs != nil {
for _, err := range errs { for _, err := range errs {
t.Error(err) t.Error(err)