Compare commits

..

2 Commits

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

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

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