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
205 changed files with 1792 additions and 6948 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
+3 -23
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,7 +186,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 Node.js
uses: actions/setup-node@v6
@@ -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-v4
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
+10 -30
View File
@@ -194,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
@@ -316,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)
@@ -385,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
@@ -425,7 +419,6 @@ TEST_PACKAGES_LINUX := \
os/user \
regexp/syntax \
strconv \
testing/fstest \
text/tabwriter \
text/template/parse
@@ -436,11 +429,7 @@ TEST_PACKAGES_WINDOWS := \
compress/flate \
crypto/des \
crypto/hmac \
image \
mime \
regexp/syntax \
strconv \
text/tabwriter \
text/template/parse \
$(nil)
@@ -789,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
@@ -960,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
@@ -985,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
@@ -1183,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
+1 -15
View File
@@ -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
View File
@@ -28,7 +28,6 @@ func TestClangAttributes(t *testing.T) {
"cortex-m4",
"cortex-m7",
"esp32c3",
"esp32c6",
"esp32s3",
"fe310",
"gameboy-advance",
+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.
+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,
+1 -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)
},
}
-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{
+4 -4
View File
@@ -42,9 +42,9 @@ func TestBinarySize(t *testing.T) {
// This is a small number of very diverse targets that we want to test.
tests := []sizeTest{
// microcontrollers
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
{"microbit", "examples/serial", 2736, 356, 8, 2248},
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
{"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
@@ -99,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
}
+2 -4
View File
@@ -121,11 +121,9 @@ func parseLLDErrors(text string) error {
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{
+22
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
@@ -650,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,
@@ -703,6 +708,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
X: &ast.Ident{
NamePos: pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -758,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,
@@ -804,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{
@@ -811,6 +823,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -868,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,
@@ -950,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{
@@ -957,6 +976,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
X: &ast.Ident{
NamePos: bitfield.pos,
Name: typeName,
Obj: nil,
},
},
},
@@ -977,6 +997,7 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
{
NamePos: bitfield.pos,
Name: "value",
Obj: nil,
},
},
Type: bitfield.field.Type,
@@ -994,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,
+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
}
}
+60 -2
View File
@@ -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,
@@ -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:
@@ -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)
+1 -1
View File
@@ -539,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:
-1
View File
@@ -47,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
-3
View File
@@ -67,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"`
@@ -485,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:
+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
+15 -27
View File
@@ -92,7 +92,6 @@ type compilerContext struct {
pkg *types.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
@@ -177,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 {
@@ -196,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
@@ -304,11 +310,6 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package,
// 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{
@@ -868,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 {
@@ -1354,15 +1356,6 @@ func (b *builder) createFunctionStart(intrinsic bool) {
// diagnostic.
func (b *builder) createFunction() {
b.createFunctionStart(false)
// createFunctionStart returns early (leaving blockInfo unset) when it hits
// an error such as a redeclared symbol. Don't plow into the block loop and
// panic on an empty blockInfo — the error was already recorded.
if b.blockInfo == nil {
// TINYGO-DEBUG: surface the redeclaration collision.
fmt.Printf("TINYGO-DEBUG skipping fn (redeclared/errored): %s pkg=%s\n",
b.fn.RelString(nil), b.fn.Pkg.Pkg.Path())
return
}
// Fill blocks with instructions.
for _, block := range b.fn.DomPreorder() {
@@ -1902,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?
@@ -2196,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)
@@ -2427,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
+5 -22
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)
}
}
@@ -243,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.
@@ -505,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)
-26
View File
@@ -10,32 +10,6 @@ import (
"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) {
+46 -328
View File
@@ -10,7 +10,6 @@ import (
"fmt"
"go/token"
"go/types"
"path/filepath"
"sort"
"strconv"
"strings"
@@ -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 {
@@ -581,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:
@@ -644,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
}
@@ -652,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
}
@@ -670,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
}
@@ -678,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())
@@ -688,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
}
@@ -703,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 i := 0; i < mset.Len(); i++ {
walk(c.program.MethodValue(mset.At(i)), false)
}
pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type()))
for i := 0; i < pmset.Len(); i++ {
walk(c.program.MethodValue(pmset.At(i)), 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 i := 0; i < targs.Len(); i++ {
visit(targs.At(i))
}
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 i := 0; i < t.NumFields(); i++ {
visit(t.Field(i).Type())
}
case *types.Signature:
if p := t.Params(); p != nil {
for i := 0; i < p.Len(); i++ {
visit(p.At(i).Type())
}
}
if r := t.Results(); r != nil {
for i := 0; i < r.Len(); i++ {
visit(r.At(i).Type())
}
}
case *types.Tuple:
for i := 0; i < t.Len(); i++ {
visit(t.At(i).Type())
}
case *types.Interface:
// A synthetic local type can be reachable only through a
// 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 i := 0; i < t.NumMethods(); i++ {
visit(t.Method(i).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 {
@@ -1026,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() {
@@ -1053,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 {
@@ -1139,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() {
@@ -1183,7 +901,7 @@ func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value {
// thunk is declared, not defined: it will be defined by the interface lowering
// pass.
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
s, _ := c.getTypeCodeName(instr.Value.Type().Underlying())
s, _ := getTypeCodeName(instr.Value.Type().Underlying())
fnName := s + "." + instr.Method.Name() + "$invoke"
llvmFn := c.mod.NamedFunction(fnName)
if llvmFn.IsNil() {
@@ -1208,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() {
+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 {
+5 -1
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)
}
+2 -2
View File
@@ -133,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")
@@ -143,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)
+1 -1
View File
@@ -162,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"} {
+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
}
+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
@@ -18,9 +18,7 @@ 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) {
+1 -1
View File
@@ -299,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.
+13 -34
View File
@@ -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 := uint32(0); i < valueLen; i++ {
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
}
-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 -2
View File
@@ -27,6 +27,8 @@ import (
"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
@@ -433,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
@@ -483,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
+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)
}
}
+3 -38
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")
@@ -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,
+9 -2
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",
+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
@@ -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
}
-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
)
+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"
-45
View File
@@ -1,45 +0,0 @@
//go:build wasip1
// Internal-test helpers exposed via go:linkname so user code can drive
// the deadline-aware Read/Write loop without becoming a stdlib package.
// Not part of the public API; the names are intentionally awkward to
// signal "for tests only".
package poll
import (
"syscall"
"time"
)
// pollTestReadWithDeadline opens a pollable FD wrapper for sysfd, sets
// a read deadline d into the future, calls Read once, and returns
// (n, err). Caller is responsible for closing sysfd.
//
//go:linkname pollTestReadWithDeadline
func pollTestReadWithDeadline(sysfd int, d time.Duration, p []byte) (int, error) {
fd := &FD{Sysfd: sysfd, IsStream: true}
// Best-effort init; ignore error so a caller using a not-fcntl-able FD
// (stdin under wazero, etc.) still gets to test the deadline path on
// whatever park behaviour the runtime gives.
_ = fd.Init("test", true)
if err := fd.SetReadDeadline(time.Now().Add(d)); err != nil {
return 0, err
}
return fd.Read(p)
}
// pollTestSetNonblock toggles O_NONBLOCK on a raw sysfd. Useful in
// tests when the caller wants to ensure the FD is in nonblocking mode
// before calling pollTestReadWithDeadline (Init is best-effort and may
// silently skip).
//
//go:linkname pollTestSetNonblock
func pollTestSetNonblock(sysfd int) error {
flags, err := syscall.Fcntl(sysfd, syscall.F_GETFL, 0)
if err != nil {
return err
}
_, err = syscall.Fcntl(sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK)
return err
}
-549
View File
@@ -1,549 +0,0 @@
//go:build wasip1
// Package poll is a minimal subset of upstream Go's internal/poll, scoped
// to what is needed to back a wasip1 net implementation on top of
// TinyGo's cooperative-scheduler netpoll integration.
//
// On wasip1 the cooperative scheduler integrates poll_oneoff with FD
// waiters (see runtime/netpoll_wasip1.go and syscall/syscall_libc_wasip1.go).
// This package wraps the syscall layer to:
//
// - own the O_NONBLOCK policy decision (set on Init for pollable FDs),
// unblocking the EAGAIN→park retry loop that syscall.Read already has;
// - provide a Go-shaped FD type that net.* can use without reaching
// into syscall directly;
// - thread per-FD read/write deadlines through a runtime helper that
// lets a time.AfterFunc callback wake the parked goroutine;
// - dispatch socket FDs through wasi sock_recv / sock_send / sock_accept
// / sock_shutdown so net.Conn/net.Listener (via upstream Go's
// net/file_wasip1.go) work end-to-end.
package poll
import (
"errors"
"internal/task"
"syscall"
"time"
"unsafe"
)
// ErrFileClosing is returned when a Read or Write is started on a closed FD.
var ErrFileClosing = errors.New("use of closed file")
// ErrNetClosing is returned for network operations on a closed FD.
var ErrNetClosing = errors.New("use of closed network connection")
// ErrDeadlineExceeded is returned by Read/Write when a deadline expired.
// Matches the error returned by os.IsTimeout-style helpers.
var ErrDeadlineExceeded = errors.New("i/o timeout")
// ErrNoDeadline is returned if SetDeadline is called on an FD whose
// underlying type does not support deadlines.
var ErrNoDeadline = errors.New("file type does not support deadline")
// pollMode constants must mirror runtime/netpoll_wasip1.go's pollRead/
// pollWrite values.
const (
pollModeRead uint8 = 1
pollModeWrite uint8 = 2
)
//go:linkname runtime_netpoll_addwait runtime.runtime_netpoll_addwait
func runtime_netpoll_addwait(fd uint32, mode uint8) uintptr
//go:linkname runtime_netpoll_done runtime.runtime_netpoll_done
func runtime_netpoll_done(pd uintptr)
//go:linkname runtime_netpoll_pdfired runtime.runtime_netpoll_pdfired
func runtime_netpoll_pdfired(pd uintptr) bool
//go:linkname runtime_netpoll_wake runtime.runtime_netpoll_wake
func runtime_netpoll_wake(pd uintptr)
//go:linkname fd_fdstat_get_type syscall.fd_fdstat_get_type
func fd_fdstat_get_type(fd int) (syscall.Filetype, error)
// wasiIovec / wasiCiovec mirror wasi-snapshot-preview1's iovec / ciovec
// records: a buffer pointer plus a 32-bit length. Marshalled inline with
// each fd_read / fd_write / sock_recv / sock_send call.
type wasiIovec struct {
buf *byte
bufLen uint32
}
// fd_read / fd_write are bound directly here rather than going through
// wasi-libc so the deadline-aware Read/Write loop can observe EAGAIN
// cleanly. Outside package runtime, TinyGo's wasmimport binding
// restricts us to unsafe.Pointer for struct args and uint32 for the
// errno result; the wasi spec uses *iovec and uint16 respectively but
// the wire layout is identical.
//
//go:wasmimport wasi_snapshot_preview1 fd_read
func wasi_fd_read(fd int32, iovs unsafe.Pointer, iovsLen uint32, nread unsafe.Pointer) uint32
//go:wasmimport wasi_snapshot_preview1 fd_write
func wasi_fd_write(fd int32, iovs unsafe.Pointer, iovsLen uint32, nwritten unsafe.Pointer) uint32
// sock_recv / sock_send are the socket-data wasi syscalls used when the
// FD's Filetype indicates a socket. We bind them here for the same
// reason as fd_read / fd_write: the deadline-aware loop wants direct
// access to the EAGAIN signal without libc's translation layer.
//
//go:wasmimport wasi_snapshot_preview1 sock_recv
func wasi_sock_recv(fd int32, riData unsafe.Pointer, riDataLen uint32, riFlags uint32, roDatalen unsafe.Pointer, roFlags unsafe.Pointer) uint32
//go:wasmimport wasi_snapshot_preview1 sock_send
func wasi_sock_send(fd int32, siData unsafe.Pointer, siDataLen uint32, siFlags uint32, soDatalen unsafe.Pointer) uint32
// SysFile carries per-FD bookkeeping that upstream Go's poll.FD uses to
// share an underlying syscall FD between an os.File and a net.Conn (see
// Copy below). RefCountPtr / RefCount handle the shared-ownership case;
// Filetype caches the wasi filetype so socket-vs-file dispatch in Read /
// Write is a single integer compare on the hot path.
//
// wasip1 is single-threaded, so the refcount is a plain int — no atomics
// needed. Match upstream's field naming for source-level compatibility
// with code that constructs FDs via struct literal (e.g. upstream's
// net/fd_fake.go).
type SysFile struct {
RefCountPtr *int32
RefCount int32
Filetype uint32
}
// init lazily allocates the refcount the first time it's needed (i.e.
// the first Init or Copy on this FD). A zero SysFile starts at refcount
// 1 — the FD's sole owner is the caller.
func (s *SysFile) init() {
if s.RefCountPtr == nil {
s.RefCount = 1
s.RefCountPtr = &s.RefCount
}
}
// ref increments the shared refcount and returns a SysFile that points
// at the same counter. Used by FD.Copy.
func (s *SysFile) ref() SysFile {
s.init()
*s.RefCountPtr++
return SysFile{RefCountPtr: s.RefCountPtr, Filetype: s.Filetype}
}
// destroy decrements the refcount and reports whether the underlying
// syscall FD should now be closed (i.e. this was the last owner).
func (s *SysFile) destroy() bool {
if s.RefCountPtr == nil {
return true
}
*s.RefCountPtr--
return *s.RefCountPtr <= 0
}
// FD is the wasip1 file/socket descriptor wrapped with the bookkeeping
// that net and os rely on. It owns the lifecycle of the underlying
// syscall FD (modulo the Copy / refcount handoff between os.File and
// net.Conn).
//
// The struct mirrors upstream Go's internal/poll.FD field naming
// (Sysfd, IsStream, ZeroReadIsEOF, SysFile) so that upstream's
// net/file_wasip1.go and net/fd_fake.go can construct one via struct
// literal without modification.
type FD struct {
Sysfd int
SysFile SysFile
IsStream bool
ZeroReadIsEOF bool
closed bool
// Per-FD deadlines, zero means "no deadline". Subsequent Read/Write
// calls observe whatever the current value is at call time; an
// in-flight call uses the deadline it captured at its start.
rDeadline time.Time
wDeadline time.Time
}
// Init readies the FD for use. When pollable is true (i.e. the FD might
// block — sockets, pipes, FIFOs), Init sets O_NONBLOCK so that
// Read/Write enter the EAGAIN→park retry loop instead of blocking the
// entire wasm module.
//
// Init also caches the wasi filetype so the Read/Write hot path can
// dispatch socket vs file with a single integer compare.
//
// The net argument is currently ignored but kept for parity with
// upstream Go.
func (fd *FD) Init(net string, pollable bool) error {
_ = net
fd.SysFile.init()
if ft, err := fd_fdstat_get_type(fd.Sysfd); err == nil {
fd.SysFile.Filetype = uint32(ft)
}
if !pollable {
return nil
}
flags, err := syscall.Fcntl(fd.Sysfd, syscall.F_GETFL, 0)
if err != nil {
return err
}
if flags&syscall.O_NONBLOCK != 0 {
return nil
}
if _, err := syscall.Fcntl(fd.Sysfd, syscall.F_SETFL, flags|syscall.O_NONBLOCK); err != nil {
return err
}
return nil
}
// Copy returns a duplicate FD that shares the underlying Sysfd through
// the SysFile refcount. The original and the copy can independently
// call Close — only the last one actually issues the syscall. Used by
// upstream net/file_wasip1.go to hand a socket FD off from an os.File
// to a net.Listener / net.Conn.
func (fd *FD) Copy() FD {
return FD{
Sysfd: fd.Sysfd,
SysFile: fd.SysFile.ref(),
IsStream: fd.IsStream,
ZeroReadIsEOF: fd.ZeroReadIsEOF,
}
}
// Close marks the FD closed. The underlying syscall FD is only released
// when the refcount drops to zero — earlier Close calls (e.g. on the
// os.File side after a successful net.FileConn handoff) just decrement.
func (fd *FD) Close() error {
if fd.closed {
return ErrFileClosing
}
fd.closed = true
if !fd.SysFile.destroy() {
return nil
}
return syscall.Close(fd.Sysfd)
}
// Exist reports whether fd points to an actual FD wrapper (non-nil).
// Callers in package os hold a *FD via a per-target alias and need to
// nil-check without depending on the concrete type being a pointer.
func (fd *FD) Exist() bool { return fd != nil }
// CloseFunc is the hook upstream net's poll package exposes so tests
// can intercept Close. We just point at syscall.Close.
var CloseFunc func(int) error = syscall.Close
// String is an internal string definition for methods/functions that
// shouldn't be used outside the stdlib. Upstream net references it
// from rawconn.go to mark methods as not-for-external-use.
type String string
// RawControl / RawRead / RawWrite back syscall.RawConn's three callback
// methods. They invoke f with the underlying FD; the bool return of
// RawRead / RawWrite controls retry-on-EAGAIN, which we implement by
// parking the goroutine on the netpoll registry and retrying until f
// returns true (the same loop upstream uses).
func (fd *FD) RawControl(f func(uintptr)) error {
if fd.closed {
return ErrFileClosing
}
f(uintptr(fd.Sysfd))
return nil
}
func (fd *FD) RawRead(f func(uintptr) bool) error {
if fd.closed {
return ErrFileClosing
}
for {
if f(uintptr(fd.Sysfd)) {
return nil
}
wait(fd.Sysfd, pollModeRead)
}
}
func (fd *FD) RawWrite(f func(uintptr) bool) error {
if fd.closed {
return ErrFileClosing
}
for {
if f(uintptr(fd.Sysfd)) {
return nil
}
wait(fd.Sysfd, pollModeWrite)
}
}
// Shutdown calls wasi sock_shutdown. how is one of syscall.SHUT_RD,
// SHUT_WR, SHUT_RDWR.
func (fd *FD) Shutdown(how int) error {
return syscall.Shutdown(fd.Sysfd, how)
}
// Accept loops over wasi sock_accept, parking the goroutine on EAGAIN
// (waiting for a new connection) through the netpoll registry. Returns
// (newfd, sockaddr=nil, errcall, error) — sockaddr is always nil because
// wasi sock_accept doesn't return one.
func (fd *FD) Accept() (int, syscall.Sockaddr, string, error) {
deadline := fd.rDeadline
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return -1, nil, "accept", ErrDeadlineExceeded
}
nfd, _, err := syscall.Accept(fd.Sysfd)
if err == nil {
return nfd, nil, "", nil
}
if err == syscall.EINTR {
continue
}
if err != syscall.EAGAIN {
return -1, nil, "accept", err
}
if deadline.IsZero() {
wait(fd.Sysfd, pollModeRead)
} else {
if perr := fd.parkUntil(pollModeRead, deadline); perr != nil {
return -1, nil, "accept", perr
}
}
}
}
// isSocket reports whether the cached Filetype is a stream or datagram
// socket. False if Init was never called (Filetype stays 0 = UNKNOWN).
func (fd *FD) isSocket() bool {
ft := syscall.Filetype(fd.SysFile.Filetype)
return ft == syscall.FILETYPE_SOCKET_STREAM || ft == syscall.FILETYPE_SOCKET_DGRAM
}
// Read reads from the FD into p. Sockets dispatch to sock_recv (which
// honours fd.rDeadline directly); regular files/pipes go through
// syscall.Read for the no-deadline fast path or readWithDeadline when
// a deadline is set.
func (fd *FD) Read(p []byte) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
if len(p) == 0 {
return 0, nil
}
if fd.isSocket() {
return fd.sockRecv(p)
}
if fd.rDeadline.IsZero() {
return syscall.Read(fd.Sysfd, p)
}
return fd.readWithDeadline(p)
}
// Write writes p to the FD. Sockets dispatch to sock_send. Regular
// files / pipes go through syscall.Write or writeWithDeadline.
func (fd *FD) Write(p []byte) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
if fd.isSocket() {
return fd.sockSend(p)
}
if fd.wDeadline.IsZero() {
return syscall.Write(fd.Sysfd, p)
}
return fd.writeWithDeadline(p)
}
// Pread reads from the FD at the given offset. Always file semantics —
// sockets aren't seekable so this never goes through sock_recv.
func (fd *FD) Pread(p []byte, off int64) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
return syscall.Pread(fd.Sysfd, p, off)
}
// Pwrite writes to the FD at the given offset.
func (fd *FD) Pwrite(p []byte, off int64) (int, error) {
if fd.closed {
return 0, ErrFileClosing
}
return syscall.Pwrite(fd.Sysfd, p, off)
}
// SetDeadline sets both the read and write deadlines.
func (fd *FD) SetDeadline(t time.Time) error {
fd.rDeadline = t
fd.wDeadline = t
return nil
}
// SetReadDeadline sets the deadline for future Read calls. A zero t
// clears the deadline.
func (fd *FD) SetReadDeadline(t time.Time) error {
fd.rDeadline = t
return nil
}
// SetWriteDeadline sets the deadline for future Write calls.
func (fd *FD) SetWriteDeadline(t time.Time) error {
fd.wDeadline = t
return nil
}
// readWithDeadline implements the EAGAIN→park retry loop with deadline
// cancellation for non-socket FDs. The deadline is captured at function
// entry; later SetReadDeadline calls don't affect this in-flight Read.
func (fd *FD) readWithDeadline(p []byte) (int, error) {
deadline := fd.rDeadline
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
for {
if !time.Now().Before(deadline) {
return 0, ErrDeadlineExceeded
}
var n uint32
errno := wasi_fd_read(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
switch errno {
case 0:
return int(n), nil
case wasiErrnoIntr:
continue
case wasiErrnoAgain:
if err := fd.parkUntil(pollModeRead, deadline); err != nil {
return 0, err
}
default:
return 0, syscall.Errno(errno)
}
}
}
func (fd *FD) writeWithDeadline(p []byte) (int, error) {
deadline := fd.wDeadline
var nn int
for {
if !time.Now().Before(deadline) {
return nn, ErrDeadlineExceeded
}
if nn == len(p) {
return nn, nil
}
buf := p[nn:]
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
var n uint32
errno := wasi_fd_write(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, unsafe.Pointer(&n))
switch errno {
case 0:
nn += int(n)
case wasiErrnoIntr:
// retry
case wasiErrnoAgain:
if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
return nn, err
}
default:
return nn, syscall.Errno(errno)
}
}
}
// sockRecv is the socket sibling of readWithDeadline / syscall.Read.
// Always issues sock_recv, regardless of deadline state, so callers
// that don't want fd_read on a socket get a guaranteed sock_recv path.
func (fd *FD) sockRecv(p []byte) (int, error) {
deadline := fd.rDeadline
iov := wasiIovec{buf: &p[0], bufLen: uint32(len(p))}
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return 0, ErrDeadlineExceeded
}
var n uint32
var roFlags uint32
errno := wasi_sock_recv(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n), unsafe.Pointer(&roFlags))
switch errno {
case 0:
return int(n), nil
case wasiErrnoIntr:
continue
case wasiErrnoAgain:
if deadline.IsZero() {
wait(fd.Sysfd, pollModeRead)
} else if err := fd.parkUntil(pollModeRead, deadline); err != nil {
return 0, err
}
default:
return 0, syscall.Errno(errno)
}
}
}
func (fd *FD) sockSend(p []byte) (int, error) {
deadline := fd.wDeadline
var nn int
for {
if !deadline.IsZero() && !time.Now().Before(deadline) {
return nn, ErrDeadlineExceeded
}
if nn == len(p) {
return nn, nil
}
buf := p[nn:]
iov := wasiIovec{buf: &buf[0], bufLen: uint32(len(buf))}
var n uint32
errno := wasi_sock_send(int32(fd.Sysfd), unsafe.Pointer(&iov), 1, 0, unsafe.Pointer(&n))
switch errno {
case 0:
nn += int(n)
case wasiErrnoIntr:
// retry
case wasiErrnoAgain:
if deadline.IsZero() {
wait(fd.Sysfd, pollModeWrite)
} else if err := fd.parkUntil(pollModeWrite, deadline); err != nil {
return nn, err
}
default:
return nn, syscall.Errno(errno)
}
}
}
// wasi snapshot preview1 errno values, kept here so the read/write loops
// can compare without dragging in the full syscall errno table for a
// switch on three constants. These match the wasi spec exactly; see also
// syscall_libc_wasi.go in package syscall.
const (
wasiErrnoAgain uint32 = 6
wasiErrnoIntr uint32 = 27
)
// wait parks the current goroutine until the FD becomes ready in the
// given direction. No deadline; mirrors the helper of the same name in
// package syscall (intentionally duplicated rather than linknamed
// across the package boundary — see project memory on shim avoidance).
func wait(fd int, mode uint8) {
pd := runtime_netpoll_addwait(uint32(fd), mode)
task.Pause()
runtime_netpoll_done(pd)
}
// parkUntil parks the current goroutine on (fd, mode) with a deadline.
// Returns nil if the FD became ready or the timer fired (caller's loop
// re-checks the deadline at top); ErrDeadlineExceeded if the deadline
// was already in the past.
//
// Race handling: the deadline timer's callback and pollIO's event walk
// can both target the same pollDesc. The pd.fired flag guards against
// double-pushing the task to the run queue; whichever arrives second
// is a no-op.
func (fd *FD) parkUntil(mode uint8, deadline time.Time) error {
d := time.Until(deadline)
if d <= 0 {
return ErrDeadlineExceeded
}
pd := runtime_netpoll_addwait(uint32(fd.Sysfd), mode)
timer := time.AfterFunc(d, func() {
runtime_netpoll_wake(pd)
})
task.Pause()
timer.Stop()
runtime_netpoll_done(pd)
return nil
}
-92
View File
@@ -754,98 +754,6 @@ func (t *RawType) FieldAlign() int {
return t.Align()
}
// ConvertibleTo returns whether a value of type t can be converted to a variable of type u
func (r *RawType) ConvertibleTo(u *RawType) bool {
// This logic is mostly copied from Value.CanConvert
switch r.Kind() {
case Int, Int8, Int16, Int32, Int64:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
case String:
return true
}
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
case String:
return true
}
case Float32, Float64:
switch u.Kind() {
case Int, Int8, Int16, Int32, Int64:
return true
case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:
return true
case Float32, Float64:
return true
}
case Complex64, Complex128:
switch u.Kind() {
case Complex64, Complex128:
return true
}
case Slice:
switch u.Kind() {
case Array:
// This may fail at runtime if there isn't room
if r.elem() == u.elem() {
return true
}
case Pointer:
// This may fail at runtime if there isn't room
if u.elem().Kind() == Array && r.elem() == u.elem().elem() {
return true
}
case String:
// bytes or runes
if !r.elem().isNamed() && (r.elem().Kind() == Uint8 || r.elem().Kind() == Int32) {
return true
}
}
case String:
// bytes or runes
if u.Kind() == Slice && !u.elem().isNamed() && (u.elem().Kind() == Uint8 || u.elem().Kind() == Int32) {
return true
}
case Pointer:
if !r.isNamed() && u.Kind() == Pointer && !u.isNamed() && r.elem().underlying() == u.elem().underlying() {
return true
}
}
if r.underlying() == u.underlying() {
return true
}
if u.Kind() == Interface && u.NumMethod() == 0 {
return true
}
// TODO(dgryski): Unimplemented
// struct types
// channels
return false
}
// AssignableTo returns whether a value of type t can be assigned to a variable
// of type u.
func (t *RawType) AssignableTo(u Type) bool {
+15 -103
View File
@@ -51,22 +51,6 @@ func (v Value) isRO() bool {
return v.flags&(valueFlagRO) != 0
}
// These are package methods, not methods on Value, since the reflect.Value embeds a reflectlite.Value, so any
// methods added to reflectlite.Value are visible to the user.
func IsRO(v Value) bool {
return v.isRO()
}
func MakeRO(v Value, ro bool) Value {
if ro {
v.flags |= valueFlagRO
} else {
v.flags &^= valueFlagRO
}
return v
}
func (v Value) checkRO() {
if v.isRO() {
panic("reflect: value is not settable")
@@ -1501,11 +1485,13 @@ func convertOp(src Value, typ Type) (Value, bool) {
return cvtFloat(src, rtype), true
}
case Complex64, Complex128:
switch rtype := typ.(*RawType); rtype.Kind() {
case Complex64, Complex128:
return cvtComplex(src, rtype), true
}
/*
case Complex64, Complex128:
switch src.Kind() {
case Complex64, Complex128:
return cvtComplex
}
*/
case Slice:
switch rtype := typ.(*RawType); rtype.Kind() {
@@ -1548,12 +1534,6 @@ func convertOp(src Value, typ Type) (Value, bool) {
return cvtStringRunes(src, rtype), true
}
}
case Pointer:
rtype := typ.(*RawType)
if rtype.Kind() == Pointer && !src.typecode.isNamed() && !rtype.isNamed() && src.typecode.elem().underlying() == rtype.elem().underlying() {
return cvtDirect(src, rtype), true
}
}
// TODO(dgryski): Unimplemented:
@@ -1598,18 +1578,6 @@ func cvtFloat(v Value, t *RawType) Value {
return makeFloat(v.flags, v.Float(), t)
}
func cvtDirect(v Value, t *RawType) Value {
return Value{
typecode: t,
value: v.value,
flags: v.flags,
}
}
func cvtComplex(v Value, t *RawType) Value {
return makeComplex(v.flags, v.Complex(), t)
}
//go:linkname stringToBytes runtime.stringToBytes
func stringToBytes(x string) []byte
@@ -1693,76 +1661,20 @@ func makeFloat32(flags valueFlags, f float32, t *RawType) Value {
return v
}
func makeComplex(flags valueFlags, f complex128, t *RawType) Value {
size := t.Size()
v := Value{
typecode: t,
flags: flags,
}
ptr := unsafe.Pointer(&v.value)
if size > unsafe.Sizeof(uintptr(0)) {
ptr = alloc(size, nil)
v.value = ptr
}
switch size {
case 8:
*(*complex64)(ptr) = complex64(f)
case 16:
*(*complex128)(ptr) = f
}
return v
func cvtIntString(src Value, t *RawType) Value {
panic("cvtUintString: unimplemented")
}
func cvtIntString(v Value, t *RawType) Value {
s := "\uFFFD"
if x := v.Int(); int64(rune(x)) == x {
s = string(rune(x))
}
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
func cvtUintString(src Value, t *RawType) Value {
panic("cvtUintString: unimplemented")
}
func cvtUintString(v Value, t *RawType) Value {
s := "\uFFFD"
if x := v.Uint(); uint64(rune(x)) == x {
s = string(rune(x))
}
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
func cvtStringRunes(src Value, t *RawType) Value {
panic("cvsStringRunes: unimplemented")
}
//go:linkname stringToRunes runtime.stringToRunes
func stringToRunes(s string) []rune
func cvtStringRunes(v Value, t *RawType) Value {
b := stringToRunes(*(*string)(v.value))
return Value{
typecode: t,
value: unsafe.Pointer(&b),
flags: v.flags,
}
}
//go:linkname stringFromRunes runtime.stringFromRunes
func stringFromRunes(r []rune) string
func cvtRunesString(v Value, t *RawType) Value {
s := stringFromRunes(*(*[]rune)(v.value))
return Value{
typecode: t,
value: unsafe.Pointer(&s),
flags: v.flags,
}
func cvtRunesString(src Value, t *RawType) Value {
panic("cvsRunesString: unimplemented")
}
//go:linkname slicePanic runtime.slicePanic
+1 -1
View File
@@ -15,7 +15,7 @@ func GetRandom(p []byte, flags GetRandomFlag) (n int, err error) {
if len(p) == 0 {
return 0, nil
}
libc_arc4random_buf(unsafe.Pointer(unsafe.SliceData(p)), uint(len(p)))
libc_arc4random_buf(unsafe.Pointer(&p[0]), uint(len(p)))
return len(p), nil
}
-5
View File
@@ -42,8 +42,3 @@ func SystemStack() uintptr {
runtimePanic("scheduler is disabled")
return 0 // unreachable
}
func GoroutineStack() uintptr {
// No separate goroutine stack without a scheduler.
return 0
}
-11
View File
@@ -11,14 +11,3 @@ uintptr_t SystemStack() {
);
return sp;
}
uintptr_t GoroutineStack() {
uintptr_t sp;
asm volatile(
"mrs %0, PSP"
: "=r"(sp)
:
: "memory"
);
return sp;
}
-7
View File
@@ -70,10 +70,3 @@ func (s *state) pause() {
//
//export SystemStack
func SystemStack() uintptr
// GoroutineStack returns the PSP (goroutine stack pointer). When a fault
// occurs in goroutine context, the hardware saves the exception frame to PSP;
// reading PSP+0x18 gives the actual faulting PC.
//
//export GoroutineStack
func GoroutineStack() uintptr
+99
View File
@@ -0,0 +1,99 @@
package task
import (
"runtime/interrupt"
"unsafe"
)
// A waiter waits for an interrupt to fire. Call Wait() from a goroutine to
// pause it, and call Resume() from an interrupt handler to resume the
// goroutine.
type Waiter struct {
waiting *Task
lock PMutex
}
var resumeTaskSentinel = (*Task)(unsafe.Pointer(uintptr(2)))
var workingTaskSentinel = (*Task)(unsafe.Pointer(uintptr(1)))
// Wait from a main goroutine until an interrupt calls Resume(). It will also
// return if the interrupt called Resume() before the Wait() call (to avoid a
// race condition).
func (w *Waiter) Wait() {
if interrupt.In() {
runtimePanic("Waiter: called Wait from interrupt")
}
mask := interrupt.Disable()
w.lock.Lock()
switch w.waiting {
case nil, workingTaskSentinel:
w.waiting = Current()
w.lock.Unlock()
interrupt.Restore(mask)
Pause()
case resumeTaskSentinel:
// Marked as 'resume now', can return immediately.
w.waiting = workingTaskSentinel
w.lock.Unlock()
interrupt.Restore(mask)
default:
w.lock.Unlock()
interrupt.Restore(mask)
runtimePanic("Waiter: task is waiting already")
}
}
// Resume a waiting goroutine from an interrupt handler. If there is a goroutine
// waiting, it will resume. If not, the next one that calls Wait() will
// immediately resume.
func (w *Waiter) Resume() {
if !interrupt.In() {
runtimePanic("Waiter: called Resume outside interrupt")
}
waiting := w.waiting
switch waiting {
case nil, workingTaskSentinel:
w.waiting = resumeTaskSentinel
case resumeTaskSentinel:
// Called Resume() twice in a row, this may indicate a bug at the caller
// site.
default:
// Schedule the given task to resume.
// If it's not yet paused, it will immediately resume on the next call
// to Pause().
w.waiting = workingTaskSentinel
scheduleTask(waiting)
}
}
// Return true if Done has not been called after a Resume call.
// This is typically used to indicate the task outside the interrupt is still
// being worked on.
func (w *Waiter) Working() bool {
switch w.waiting {
case workingTaskSentinel, resumeTaskSentinel:
return true
default: // nil, <*task.Task>
return false
}
}
// Done can be called outside interrupt context to indicate the task this waiter
// was waiting for is done, and Working() can return false. It is entirely
// optional, if not called Working will continue to return true until it is
// blocked in Wait() but the waiter will function normally otherwise.
func (w *Waiter) Done() {
if interrupt.In() {
runtimePanic("Waiter: called Done from interrupt")
}
mask := interrupt.Disable()
w.lock.Lock()
if w.waiting == workingTaskSentinel {
w.waiting = nil
}
w.lock.Unlock()
interrupt.Restore(mask)
}
-113
View File
@@ -1,113 +0,0 @@
//go:build badger2350
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
// Pin source: https://github.com/pimoroni/badger2350/blob/main/board/pins.csv
//
// NOTES (verify before first flash):
// - POWER_EN (GPIO27) likely switches peripheral/display power.
// Verify polarity against the schematic; pull HIGH early if needed,
// otherwise the display will remain blank.
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
// NOT the RP2350 -> intentionally not defined as a pin here.
package machine
// Crystal oscillator frequency.
const xoscFreq = 12 // MHz
// User buttons.
const (
BUTTON_A Pin = GPIO7
BUTTON_B Pin = GPIO9
BUTTON_C Pin = GPIO10
BUTTON_UP Pin = GPIO11
BUTTON_DOWN Pin = GPIO6
BUTTON_HOME Pin = GPIO22
BUTTON_BOOT Pin = BUTTON_HOME
// System / RTC-related buttons.
BUTTON_RESET Pin = GPIO14
BUTTON_INT Pin = GPIO15
)
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
const (
CL0 Pin = GPIO0
CL1 Pin = GPIO1
CL2 Pin = GPIO2
CL3 Pin = GPIO3
// Convention: first zone as default LED.
LED = CL0
)
// E-ink display (Inky), on SPI0.
const (
INKY_BUSY Pin = GPIO16
INKY_CS Pin = GPIO17
INKY_SCLK Pin = GPIO18
INKY_MOSI Pin = GPIO19
INKY_DC Pin = GPIO20
INKY_RES Pin = GPIO21
)
// SPI0 default pins == Inky bus. SDI is unused by the EPD,
// but GPIO16 is a valid SPI0 RX line on the RP2350.
const (
SPI0_SCK_PIN = GPIO18 // INKY_SCLK
SPI0_SDO_PIN = GPIO19 // INKY_MOSI
SPI0_SDI_PIN = GPIO16 // (unused by the EPD)
)
// SPI1 is not routed to any header; NoPin satisfies the machine package.
const (
SPI1_SCK_PIN Pin = NoPin
SPI1_SDO_PIN Pin = NoPin
SPI1_SDI_PIN Pin = NoPin
)
// UART pins - default RP2350 UART0 mapping.
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
// Do not use UART0 and LEDs simultaneously.
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
// I2C0 (Qwiic/STEMMA QT + RTC PCF85063A).
const (
I2C0_SDA_PIN = GPIO4
I2C0_SCL_PIN = GPIO5
)
// I2C1 is not routed to any header; NoPin satisfies the machine package.
const (
I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = NoPin
)
// Power / Sensing.
const (
VBUS_DETECT Pin = GPIO12
RTC_ALARM Pin = GPIO13
VBAT_SENSE Pin = GPIO26
POWER_EN Pin = GPIO27
SENSE_1V1 Pin = GPIO28
BATTERY = VBAT_SENSE
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Badger 2350"
usb_STRING_MANUFACTURER = "Pimoroni"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x0005
)
-112
View File
@@ -1,112 +0,0 @@
//go:build blinky2350
// Chip: RP2350A (QFN-60, 30 GPIO) -> target inherits from "rp2350".
// Pin source: https://github.com/pimoroni/blinky2350/blob/main/board/pins.csv
//
// NOTES (verify before first flash):
// - POWER_EN (GPIO27) likely switches peripheral power.
// Verify polarity against the schematic; pull HIGH early if needed,
// otherwise the display will remain blank.
// - CHARGE_STAT is connected to EXT_GPIO2 (I2C IO expander) according to pins.csv,
// NOT the RP2350 -> intentionally not defined as a pin here.
// - The dot matrix is NOT driven via SPI (Latch/Blank/
// Row-Clock are not SPI signals) -> use PIO or bit-banging.
// SPI0 and SPI1 are therefore set to NoPin (required by the machine package).
package machine
// Crystal oscillator frequency.
const xoscFreq = 12 // MHz
// User buttons.
const (
BUTTON_A Pin = GPIO7
BUTTON_B Pin = GPIO9
BUTTON_C Pin = GPIO10
BUTTON_UP Pin = GPIO11
BUTTON_DOWN Pin = GPIO6
BUTTON_HOME Pin = GPIO22
BUTTON_BOOT Pin = BUTTON_HOME
BUTTON_RESET Pin = GPIO14
BUTTON_INT Pin = GPIO15
)
// Case LEDs (4-zone mono illumination), named CL0..CL3 in the docs.
const (
CL0 Pin = GPIO0
CL1 Pin = GPIO1
CL2 Pin = GPIO2
CL3 Pin = GPIO3
LED = CL0
)
// LED dot matrix: column shift register + row driver.
// NOT SPI -> drive via PIO or bit-banging.
const (
DISPLAY_COL_SCLK Pin = GPIO16
DISPLAY_COL_DATA Pin = GPIO17
DISPLAY_COL_LATCH Pin = GPIO18
DISPLAY_COL_BLANK Pin = GPIO19
DISPLAY_ROW_DATA Pin = GPIO20
DISPLAY_ROW_CLK Pin = GPIO21
)
// I2C0 (Qwiic/STEMMA QT + RTC).
const (
I2C0_SDA_PIN = GPIO4
I2C0_SCL_PIN = GPIO5
)
// I2C1 is not routed to any header; NoPin satisfies the machine package.
const (
I2C1_SDA_PIN Pin = NoPin
I2C1_SCL_PIN Pin = NoPin
)
// SPI pins - the LED matrix is driven via PIO/bit-banging, not SPI.
// NoPin satisfies the machine package requirements.
const (
SPI0_SCK_PIN Pin = NoPin
SPI0_SDO_PIN Pin = NoPin
SPI0_SDI_PIN Pin = NoPin
SPI1_SCK_PIN Pin = NoPin
SPI1_SDO_PIN Pin = NoPin
SPI1_SDI_PIN Pin = NoPin
)
// UART pins - default RP2350 UART0 mapping.
// Note: GPIO0/GPIO1 are also CL0/CL1 (case LEDs)
// Do not use UART0 and LEDs simultaneously.
const (
UART0_TX_PIN = GPIO0
UART0_RX_PIN = GPIO1
UART_TX_PIN = UART0_TX_PIN
UART_RX_PIN = UART0_RX_PIN
)
// Power / sensing.
const (
VBUS_DETECT Pin = GPIO12
RTC_ALARM Pin = GPIO13
VBAT_SENSE Pin = GPIO26
POWER_EN Pin = GPIO27
SENSE_1V1 Pin = GPIO28
BATTERY = VBAT_SENSE
)
var DefaultUART = UART0
// USB identifiers
const (
usb_STRING_PRODUCT = "Blinky 2350"
usb_STRING_MANUFACTURER = "Pimoroni"
)
var (
usb_VID uint16 = 0x2E8A
usb_PID uint16 = 0x0005
)
-144
View File
@@ -1,144 +0,0 @@
//go:build esp32s3_box3
// This file contains the pin mappings for the ESP32-S3-BOX-3 board.
//
// ESP32-S3-BOX-3 is an AI voice development kit with 2.4" display.
// - https://github.com/espressif/esp-box
//
// Based on ESP-BOX-3 BSP from Espressif.
package machine
// CPUFrequency returns the current CPU frequency for ESP32-S3-BOX-3.
// Returns 240MHz (max speed for ESP32-S3 with PSRAM)
func CPUFrequency() uint32 {
return 240_000_000 // 240MHz
}
const (
// GPIO pins available on ESP32-S3-BOX-3
// Based on ESP-BOX-3 BSP pin definitions
IO0 = GPIO0 // Button Config
IO1 = GPIO1 // Button Mute / Mute Status
IO2 = GPIO2 // I2S MCLK
IO3 = GPIO3 // LCD Touch Interrupt
IO4 = GPIO4 // LCD DC
IO5 = GPIO5 // LCD CS
IO6 = GPIO6 // LCD DATA0 (MOSI)
IO7 = GPIO7 // LCD PCLK (SCK)
IO8 = GPIO8 // I2C SDA
IO9 = GPIO9 // SD Card D0
IO10 = GPIO10
IO11 = GPIO11 // SD Card CLK
IO12 = GPIO12 // SD Card D3
IO13 = GPIO13 // SD Card D1
IO14 = GPIO14 // SD Card CMD
IO15 = GPIO15 // I2S DOUT (Speaker)
IO16 = GPIO16 // I2S DSIN (Microphone)
IO17 = GPIO17 // I2S SCLK
IO18 = GPIO18 // I2C SCL
IO19 = GPIO19 // USB_NEG
IO20 = GPIO20 // USB_POS
IO21 = GPIO21
IO38 = GPIO38
IO39 = GPIO39
IO40 = GPIO40 // I2C Dock SCL
IO41 = GPIO41 // I2C Dock SDA
IO42 = GPIO42 // SD Card D2
IO43 = GPIO43 // SD Card Power
IO44 = GPIO44 // (Unused)
IO45 = GPIO45 // I2S LCLK (LRCK)
IO46 = GPIO46 // Power Amp Control
IO47 = GPIO47 // LCD Backlight
IO48 = GPIO48 // LCD Reset
)
// SPI pins
const (
// SPI1 - used for LCD
SPI1_SCK_PIN = IO7 // LCD_PCLK
SPI1_MOSI_PIN = IO6 // LCD_DATA0 (MOSI)
SPI1_MISO_PIN = NoPin // Not used for LCD
SPI1_CS_PIN = IO5 // LCD_CS
// SPI2 (not used on this board)
SPI2_SCK_PIN = NoPin
SPI2_MOSI_PIN = NoPin
SPI2_MISO_PIN = NoPin
SPI2_CS_PIN = NoPin
)
// LCD pins (ST7789/ILI9341)
const (
LCD_SCK_PIN = SPI1_SCK_PIN
LCD_SDO_PIN = SPI1_MOSI_PIN
LCD_SDI_PIN = NoPin
LCD_SS_PIN = SPI1_CS_PIN
LCD_DC_PIN = IO4
LCD_RST_PIN = IO48
LCD_BL_PIN = IO47
)
// I2C pins (Internal I2C for sensors)
const (
SDA0_PIN = IO8
SCL0_PIN = IO18
SDA_PIN = SDA0_PIN
SCL_PIN = SCL0_PIN
)
// Dock I2C pins (for expansion dock)
const (
SDA1_PIN = IO41
SCL1_PIN = IO40
)
// UART pins (USB CDC - native USB on ESP32-S3)
// Note: Native USB doesn't require GPIO pins, these are kept for compatibility
const (
UART_TX_PIN = NoPin
UART_RX_PIN = NoPin
)
// Built-in LED
// Using LCD backlight as LED indicator
const LED = GPIO47
// Buttons
const (
BUTTON_CONFIG = GPIO0
BUTTON_MUTE = GPIO1
)
// Audio pins (I2S)
const (
I2S_SCLK_PIN = IO17 // Bit Clock
I2S_MCLK_PIN = IO2 // Master Clock
I2S_LRCK_PIN = IO45 // Left/Right Clock (Frame Sync)
I2S_DOUT_PIN = IO15 // Data Out to Speaker (ES8311)
I2S_DSIN_PIN = IO16 // Data In from Microphone (ES7210)
)
// SD Card pins (SD/MMC mode)
const (
SDCARD_CMD_PIN = IO14
SDCARD_CLK_PIN = IO11
SDCARD_D0_PIN = IO9
SDCARD_D1_PIN = IO13
SDCARD_D2_PIN = IO42
SDCARD_D3_PIN = IO12
SDCARD_PWR_PIN = IO43 // SD Card Power
)
// USB pins (native USB)
const (
USB_DPPIN = IO20 // USB D+
USB_DMPIN = IO19 // USB D-
)
// Touch interrupt
const TOUCH_INT_PIN = IO3
// Power amplifier control
const POWER_AMP_PIN = IO46
@@ -41,40 +41,6 @@ const (
D29 Pin = GPIO29
)
// GPIO pin aliases
const (
GP0 Pin = GPIO0
GP1 Pin = GPIO1
GP2 Pin = GPIO2
GP3 Pin = GPIO3
GP4 Pin = GPIO4
GP5 Pin = GPIO5
GP6 Pin = GPIO6
GP7 Pin = GPIO7
GP8 Pin = GPIO8
GP9 Pin = GPIO9
GP10 Pin = GPIO10
GP11 Pin = GPIO11
GP12 Pin = GPIO12
GP13 Pin = GPIO13
GP14 Pin = GPIO14
GP15 Pin = GPIO15
GP16 Pin = GPIO16
GP17 Pin = GPIO17
GP18 Pin = GPIO18
GP19 Pin = GPIO19
GP20 Pin = GPIO20
GP21 Pin = GPIO21
GP22 Pin = GPIO22
GP23 Pin = GPIO23
GP24 Pin = GPIO24
GP25 Pin = GPIO25
GP26 Pin = GPIO26
GP27 Pin = GPIO27
GP28 Pin = GPIO28
GP29 Pin = GPIO29
)
// Analog pins
const (
A0 Pin = D26
-57
View File
@@ -1,57 +0,0 @@
//go:build xiao_esp32c6
// This file contains the pin mappings for the Seeed XIAO ESP32C6 board.
//
// Seeed Studio XIAO ESP32C6 is an IoT mini development board based on
// the Espressif ESP32-C6 WiFi 6 / Bluetooth 5 / 802.15.4 chip.
//
// - https://www.seeedstudio.com/Seeed-Studio-XIAO-ESP32C6-p-5884.html
// - https://wiki.seeedstudio.com/xiao_esp32c6_getting_started/
package machine
// Digital Pins
const (
D0 = GPIO0
D1 = GPIO1
D2 = GPIO2
D3 = GPIO21
D4 = GPIO22
D5 = GPIO23
D6 = GPIO16
D7 = GPIO17
D8 = GPIO19
D9 = GPIO20
D10 = GPIO18
)
// Analog pins
const (
A0 = GPIO0
A1 = GPIO1
A2 = GPIO2
)
// UART pins
const (
UART_TX_PIN = GPIO16
UART_RX_PIN = GPIO17
)
// I2C pins
const (
SDA_PIN = GPIO22
SCL_PIN = GPIO23
)
// SPI pins
const (
SPI_SCK_PIN = GPIO19
SPI_SDI_PIN = GPIO20
SPI_SDO_PIN = GPIO18
)
// Onboard LEDs
const (
LED = GPIO15
)
+1 -1
View File
@@ -1,4 +1,4 @@
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32c6 || esp32s3
//go:build !baremetal || atmega || nrf || sam || stm32 || fe310 || k210 || rp2040 || rp2350 || mimxrt1062 || (esp32c3 && !m5stamp_c3) || esp32 || esp32s3
package machine
+18 -23
View File
@@ -23,6 +23,19 @@ const (
NumberOfUSBEndpoints = 8
)
var (
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
func (dev *USBDevice) Configure(config UARTConfig) {
if dev.initcomplete {
@@ -175,7 +188,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
var i uint32
for i = 1; i < NumberOfUSBEndpoints; i++ {
for i = 1; i < uint32(len(endPoints)); i++ {
// Check if endpoint has a pending interrupt
epFlags := getEPINTFLAG(i)
setEPINTFLAG(i, epFlags)
@@ -193,8 +206,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
func initEndpoint(ep, config uint32) {
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
// set packet size
@@ -204,7 +215,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT1)
@@ -216,7 +227,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
@@ -235,7 +246,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_EPINTENSET_TRCPT0)
@@ -254,7 +265,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_EPCFG_EPTYPE1_Pos))
// NAK on endpoint IN, the bank is not yet filled in.
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_BK1RDY)
@@ -652,19 +663,3 @@ func setEPINTENSET(ep uint32, val uint8) {
return
}
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ1)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_EPSTATUSSET_STALLRQ0)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ1)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_EPSTATUSCLR_STALLRQ0)
}
+18 -23
View File
@@ -23,6 +23,19 @@ const (
NumberOfUSBEndpoints = 8
)
var (
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// Configure the USB peripheral. The config is here for compatibility with the UART interface.
func (dev *USBDevice) Configure(config UARTConfig) {
if dev.initcomplete {
@@ -178,7 +191,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
// Now the actual transfer handlers, ignore endpoint number 0 (setup)
var i uint32
for i = 1; i < NumberOfUSBEndpoints; i++ {
for i = 1; i < uint32(len(endPoints)); i++ {
// Check if endpoint has a pending interrupt
epFlags := getEPINTFLAG(i)
setEPINTFLAG(i, epFlags)
@@ -196,8 +209,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
func initEndpoint(ep, config uint32) {
// Note: Both IN (Bank 1) and OUT (Bank 0) configurations share the same EPCFG register.
// We must use getEPCFG(ep) | ... to avoid clearing/disabling the opposite direction.
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
// set packet size
@@ -207,7 +218,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT1)
@@ -219,7 +230,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
@@ -238,7 +249,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[0].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_out_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_INTERRUPT+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_INTERRUPT + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE0_Pos))
// receive interrupts when current transfer complete
setEPINTENSET(ep, sam.USB_DEVICE_ENDPOINT_EPINTENSET_TRCPT0)
@@ -257,7 +268,7 @@ func initEndpoint(ep, config uint32) {
usbEndpointDescriptors[ep].DeviceDescBank[1].ADDR.Set(uint32(uintptr(unsafe.Pointer(&udd_ep_in_cache_buffer[ep]))))
// set endpoint type
setEPCFG(ep, getEPCFG(ep)|((usb.ENDPOINT_TYPE_BULK+1)<<sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
setEPCFG(ep, ((usb.ENDPOINT_TYPE_BULK + 1) << sam.USB_DEVICE_ENDPOINT_EPCFG_EPTYPE1_Pos))
// NAK on endpoint IN, the bank is not yet filled in.
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_BK1RDY)
@@ -483,19 +494,3 @@ func setEPINTENCLR(ep uint32, val uint8) {
func setEPINTENSET(ep uint32, val uint8) {
sam.USB_DEVICE.DEVICE_ENDPOINT[ep].EPINTENSET.Set(val)
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ1)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
setEPSTATUSSET(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSSET_STALLRQ0)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ1)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
setEPSTATUSCLR(ep, sam.USB_DEVICE_ENDPOINT_EPSTATUSCLR_STALLRQ0)
}
+5 -39
View File
@@ -295,33 +295,16 @@ var DefaultUART = UART0
var (
UART0 = &_UART0
_UART0 = UART{
Bus: esp.UART0,
Buffer: NewRingBuffer(),
TXRXSignal: 14,
RTSCTSSignal: 15,
}
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{
Bus: esp.UART1,
Buffer: NewRingBuffer(),
TXRXSignal: 17,
RTSCTSSignal: 18,
}
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
UART2 = &_UART2
_UART2 = UART{
Bus: esp.UART2,
Buffer: NewRingBuffer(),
TXRXSignal: 198,
RTSCTSSignal: 199,
}
_UART2 = UART{Bus: esp.UART2, Buffer: NewRingBuffer()}
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
TXRXSignal uint32
RTSCTSSignal uint32
Bus *esp.UART_Type
Buffer *RingBuffer
}
func (uart *UART) Configure(config UARTConfig) {
@@ -329,22 +312,6 @@ func (uart *UART) Configure(config UARTConfig) {
config.BaudRate = 115200
}
uart.Bus.CLKDIV.Set(peripheralClock / config.BaudRate)
if config.RX != NoPin {
config.RX.configure(PinConfig{Mode: PinInputPullup}, uart.TXRXSignal)
}
if config.TX != NoPin {
config.TX.configure(PinConfig{Mode: PinOutput}, uart.TXRXSignal)
}
if config.RTS != NoPin {
config.RTS.configure(PinConfig{Mode: PinOutput}, uart.RTSCTSSignal)
}
if config.CTS != NoPin {
config.CTS.configure(PinConfig{Mode: PinInputPullup}, uart.RTSCTSSignal)
}
}
func (uart *UART) writeByte(b byte) error {
@@ -352,7 +319,6 @@ func (uart *UART) writeByte(b byte) error {
// Read UART_TXFIFO_CNT from the status register, which indicates how
// many bytes there are in the transmit buffer. Wait until there are
// less than 128 bytes in this buffer (the default buffer size).
gosched()
}
// Write to the TX_FIFO register.
(*volatile.Register8)(unsafe.Add(unsafe.Pointer(uart.Bus), 0x200C0000)).Set(b)
-7
View File
@@ -19,10 +19,3 @@ var (
useExt1: false,
}
)
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
func enableI2C0PeriphClock() {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
}
-546
View File
@@ -1,546 +0,0 @@
//go:build esp32c6
package machine
import (
"device/esp"
"device/riscv"
"errors"
"runtime/interrupt"
"runtime/volatile"
"sync"
"unsafe"
)
const deviceName = esp.Device
const maxPin = 31
const cpuInterruptFromPin = 6
// CPUFrequency returns the current CPU frequency of the chip.
// Currently it is a fixed frequency but it may allow changing in the future.
func CPUFrequency() uint32 {
return 160e6 // 160MHz
}
const (
PinOutput PinMode = iota
PinInput
PinInputPullup
PinInputPulldown
PinAnalog
)
const (
GPIO0 Pin = 0
GPIO1 Pin = 1
GPIO2 Pin = 2
GPIO3 Pin = 3
GPIO4 Pin = 4
GPIO5 Pin = 5
GPIO6 Pin = 6
GPIO7 Pin = 7
GPIO8 Pin = 8
GPIO9 Pin = 9
GPIO10 Pin = 10
GPIO11 Pin = 11
GPIO12 Pin = 12
GPIO13 Pin = 13
GPIO14 Pin = 14
GPIO15 Pin = 15
GPIO16 Pin = 16
GPIO17 Pin = 17
GPIO18 Pin = 18
GPIO19 Pin = 19
GPIO20 Pin = 20
GPIO21 Pin = 21
GPIO22 Pin = 22
GPIO23 Pin = 23
GPIO24 Pin = 24
GPIO25 Pin = 25
GPIO26 Pin = 26
GPIO27 Pin = 27
GPIO28 Pin = 28
GPIO29 Pin = 29
GPIO30 Pin = 30
)
const (
ADC0 Pin = GPIO0
ADC1 Pin = GPIO1
ADC2 Pin = GPIO2
ADC3 Pin = GPIO3
ADC4 Pin = GPIO4
ADC5 Pin = GPIO5
ADC6 Pin = GPIO6
)
type PinChange uint8
// Pin change interrupt constants for SetInterrupt.
const (
PinRising PinChange = iota + 1
PinFalling
PinToggle
)
// Configure this pin with the given configuration.
func (p Pin) Configure(config PinConfig) {
if p == NoPin {
// This simplifies pin configuration in peripherals such as SPI.
return
}
var muxConfig uint32
// Configure this pin as a GPIO pin.
const function = 1 // function 1 is GPIO for every pin
muxConfig |= function << esp.IO_MUX_GPIO_MCU_SEL_Pos
// FUN_IE: disable for PinAnalog (high-Z for ADC)
if config.Mode != PinAnalog {
muxConfig |= esp.IO_MUX_GPIO_FUN_IE
}
// Set drive strength: 0 is lowest, 3 is highest.
muxConfig |= 2 << esp.IO_MUX_GPIO_FUN_DRV_Pos
// Select pull mode (no pulls for PinAnalog).
if config.Mode == PinInputPullup {
muxConfig |= esp.IO_MUX_GPIO_FUN_WPU
} else if config.Mode == PinInputPulldown {
muxConfig |= esp.IO_MUX_GPIO_FUN_WPD
}
// Configure the pad with the given IO mux configuration.
p.mux().Set(muxConfig)
// Set the output signal to the simple GPIO output.
p.outFunc().Set(0x80)
switch config.Mode {
case PinOutput:
// Set the 'output enable' bit.
esp.GPIO.ENABLE_W1TS.Set(1 << p)
case PinInput, PinInputPullup, PinInputPulldown, PinAnalog:
// Clear the 'output enable' bit.
esp.GPIO.ENABLE_W1TC.Set(1 << p)
}
}
// configure is the same as Configure, but allows setting a specific GPIO matrix signal.
func (p Pin) configure(config PinConfig, signal uint32) {
p.Configure(config)
if signal == 256 {
return
}
if config.Mode == PinOutput {
p.outFunc().Set(signal)
} else {
inFunc(signal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(p))
}
}
func initI2CExt1Clock() {}
// outFunc returns the FUNCx_OUT_SEL_CFG register used for configuring the
// output function selection.
func (p Pin) outFunc() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_OUT_SEL_CFG), uintptr(p)*4))
}
func (p Pin) pinReg() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Pointer((uintptr(unsafe.Pointer(&esp.GPIO.PIN0)) + uintptr(p)*4)))
}
// inFunc returns the FUNCy_IN_SEL_CFG register used for configuring the input
// function selection.
func inFunc(signal uint32) *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.FUNC0_IN_SEL_CFG), uintptr(signal)*4))
}
// mux returns the I/O mux configuration register corresponding to the given
// GPIO pin.
func (p Pin) mux() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.IO_MUX.GPIO0), uintptr(p)*4))
}
// pin returns the PIN register corresponding to the given GPIO pin.
func (p Pin) pin() *volatile.Register32 {
return (*volatile.Register32)(unsafe.Add(unsafe.Pointer(&esp.GPIO.PIN0), uintptr(p)*4))
}
// Set the pin to high or low.
// Warning: only use this on an output pin!
func (p Pin) Set(value bool) {
if value {
reg, mask := p.portMaskSet()
reg.Set(mask)
} else {
reg, mask := p.portMaskClear()
reg.Set(mask)
}
}
// Get returns the current value of a GPIO pin when configured as an input or as
// an output.
func (p Pin) Get() bool {
reg := &esp.GPIO.IN
return (reg.Get()>>p)&1 > 0
}
// Return the register and mask to enable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskSet() (*uint32, uint32) {
reg, mask := p.portMaskSet()
return &reg.Reg, mask
}
// Return the register and mask to disable a given GPIO pin. This can be used to
// implement bit-banged drivers.
//
// Warning: only use this on an output pin!
func (p Pin) PortMaskClear() (*uint32, uint32) {
reg, mask := p.portMaskClear()
return &reg.Reg, mask
}
func (p Pin) portMaskSet() (*volatile.Register32, uint32) {
return &esp.GPIO.OUT_W1TS, 1 << p
}
func (p Pin) portMaskClear() (*volatile.Register32, uint32) {
return &esp.GPIO.OUT_W1TC, 1 << p
}
// SetInterrupt sets an interrupt to be executed when a particular pin changes
// state. The pin should already be configured as an input, including a pull up
// or down if no external pull is provided.
//
// You can pass a nil func to unset the pin change interrupt. If you do so,
// the change parameter is ignored and can be set to any value (such as 0).
// If the pin is already configured with a callback, you must first unset
// this pins interrupt before you can set a new callback.
func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) (err error) {
if p >= maxPin {
return ErrInvalidInputPin
}
if callback == nil {
// Disable this pin interrupt
p.pin().ClearBits(esp.GPIO_PIN_INT_TYPE_Msk | esp.GPIO_PIN_INT_ENA_Msk)
if pinCallbacks[p] != nil {
pinCallbacks[p] = nil
}
return nil
}
if pinCallbacks[p] != nil {
// The pin was already configured.
// To properly re-configure a pin, unset it first and set a new
// configuration.
return ErrNoPinChangeChannel
}
pinCallbacks[p] = callback
onceSetupPinInterrupt.Do(func() {
err = setupPinInterrupt()
})
if err != nil {
return err
}
p.pin().Set(
(p.pin().Get() & ^uint32(esp.GPIO_PIN_INT_TYPE_Msk|esp.GPIO_PIN_INT_ENA_Msk)) |
uint32(change)<<esp.GPIO_PIN_INT_TYPE_Pos | uint32(1)<<esp.GPIO_PIN_INT_ENA_Pos)
return nil
}
var (
pinCallbacks [maxPin]func(Pin)
onceSetupPinInterrupt sync.Once
)
func setupPinInterrupt() error {
esp.INTERRUPT_CORE0.GPIO_INTERRUPT_PRO_MAP.Set(cpuInterruptFromPin)
return interrupt.New(cpuInterruptFromPin, func(interrupt.Interrupt) {
status := esp.GPIO.STATUS.Get()
// Clear before processing so new edges during callbacks are not lost.
esp.GPIO.STATUS_W1TC.Set(status)
for i, mask := 0, uint32(1); i < maxPin; i, mask = i+1, mask<<1 {
if (status&mask) != 0 && pinCallbacks[i] != nil {
pinCallbacks[i](Pin(i))
}
}
}).Enable()
}
var (
DefaultUART = UART0
UART0 = &_UART0
_UART0 = UART{Bus: esp.UART0, Buffer: NewRingBuffer()}
UART1 = &_UART1
_UART1 = UART{Bus: esp.UART1, Buffer: NewRingBuffer()}
onceUart = sync.Once{}
errSamePins = errors.New("UART: invalid pin combination")
errWrongUART = errors.New("UART: unsupported UARTn")
errWrongBitSize = errors.New("UART: invalid data size")
errWrongStopBitSize = errors.New("UART: invalid bit size")
)
type UART struct {
Bus *esp.UART_Type
Buffer *RingBuffer
ParityErrorDetected bool // set when parity error detected
DataErrorDetected bool // set when data corruption detected
DataOverflowDetected bool // set when data overflow detected in UART FIFO buffer or RingBuffer
}
const (
defaultDataBits = 8
defaultStopBit = 1
defaultParity = ParityNone
uartInterrupts = esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA |
esp.UART_INT_ENA_PARITY_ERR_INT_ENA |
esp.UART_INT_ENA_FRM_ERR_INT_ENA |
esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA |
esp.UART_INT_ENA_GLITCH_DET_INT_ENA
pplClockFreq = 80e6
)
type registerSet struct {
interruptMapReg *volatile.Register32
gpioMatrixSignal uint32
}
func (uart *UART) Configure(config UARTConfig) error {
if config.BaudRate == 0 {
config.BaudRate = 115200
}
if config.TX == config.RX {
return errSamePins
}
switch {
case uart.Bus == esp.UART0:
return uart.configure(config, registerSet{
interruptMapReg: &esp.INTERRUPT_CORE0.UART0_INTR_MAP,
gpioMatrixSignal: 6,
})
case uart.Bus == esp.UART1:
return uart.configure(config, registerSet{
interruptMapReg: &esp.INTERRUPT_CORE0.UART1_INTR_MAP,
gpioMatrixSignal: 9,
})
}
return errWrongUART
}
func (uart *UART) configure(config UARTConfig, regs registerSet) error {
initUARTClock(uart.Bus)
// - disable TX/RX clock to make sure the UART transmitter or receiver is not at work during configuration
uart.Bus.SetCLK_CONF_TX_SCLK_EN(0)
uart.Bus.SetCLK_CONF_RX_SCLK_EN(0)
// - default clock source: 1=XTAL_CLK, 2=RC_FAST_CLK, 3=FOSC_CLK
// On C6, SCLK_SEL=1 selects XTAL (40 MHz). Use FOSC (80 MHz) for
// better baud-rate accuracy.
uart.Bus.SetCLK_CONF_SCLK_SEL(3) // FOSC_CLK (80 MHz)
// reset divisor of the divider via UART_SCLK_DIV_NUM, UART_SCLK_DIV_A, and UART_SCLK_DIV_B
uart.Bus.SetCLK_CONF_SCLK_DIV_NUM(0)
uart.Bus.SetCLK_CONF_SCLK_DIV_A(0)
uart.Bus.SetCLK_CONF_SCLK_DIV_B(0)
uart.SetBaudRate(config.BaudRate)
uart.SetFormat(defaultDataBits, defaultStopBit, defaultParity)
// - set UART mode
uart.Bus.SetRS485_CONF_RS485_EN(0)
uart.Bus.SetRS485_CONF_RS485TX_RX_EN(0)
uart.Bus.SetRS485_CONF_RS485RXBY_TX_EN(0)
uart.Bus.SetCONF0_IRDA_EN(0)
// - disable hw-flow control
uart.Bus.SetCONF0_TX_FLOW_EN(0)
// synchronize values into Core Clock
uart.Bus.SetREG_UPDATE(1)
uart.setupPins(config, regs)
uart.configureInterrupt(regs.interruptMapReg)
uart.enableTransmitter()
uart.enableReceiver()
// Start TX/RX
uart.Bus.SetCLK_CONF_TX_SCLK_EN(1)
uart.Bus.SetCLK_CONF_RX_SCLK_EN(1)
return nil
}
func (uart *UART) SetFormat(dataBits, stopBits int, parity UARTParity) error {
if dataBits < 5 {
return errWrongBitSize
}
if stopBits > 1 {
return errWrongStopBitSize
}
// - data length
uart.Bus.SetCONF0_BIT_NUM(uint32(dataBits - 5))
// - stop bit
uart.Bus.SetCONF0_STOP_BIT_NUM(uint32(stopBits))
// - parity check
switch parity {
case ParityNone:
uart.Bus.SetCONF0_PARITY_EN(0)
case ParityEven:
uart.Bus.SetCONF0_PARITY_EN(1)
uart.Bus.SetCONF0_PARITY(0)
case ParityOdd:
uart.Bus.SetCONF0_PARITY_EN(1)
uart.Bus.SetCONF0_PARITY(1)
}
return nil
}
func initUARTClock(bus *esp.UART_Type) {
// On ESP32-C6, UART clock is controlled via PCR (Peripheral Clock Reset).
switch bus {
case esp.UART0:
esp.PCR.SetUART0_CONF_UART0_CLK_EN(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(0) // ensure not in reset
bus.SetCLK_CONF_RST_CORE(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(1)
esp.PCR.SetUART0_CONF_UART0_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(0)
// Configure SCLK divisor in PCR
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(0)
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_A(0)
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_B(0)
case esp.UART1:
esp.PCR.SetUART1_CONF_UART1_CLK_EN(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(1)
esp.PCR.SetUART1_CONF_UART1_RST_EN(0)
bus.SetCLK_CONF_RST_CORE(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_A(0)
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_B(0)
}
// wait for Core Clock to ready for configuration
for bus.GetREG_UPDATE() > 0 {
riscv.Asm("nop")
}
}
func (uart *UART) SetBaudRate(baudRate uint32) {
// based on esp-idf
max_div := uint32((1 << 12) - 1)
sclk_div := (pplClockFreq + (max_div * baudRate) - 1) / (max_div * baudRate)
clk_div := (pplClockFreq << 4) / (baudRate * sclk_div)
uart.Bus.SetCLKDIV(clk_div >> 4)
uart.Bus.SetCLKDIV_FRAG(clk_div & 0xf)
// On C6, the SCLK divisor is in the PCR register.
switch uart.Bus {
case esp.UART0:
esp.PCR.SetUART0_SCLK_CONF_UART0_SCLK_DIV_NUM(sclk_div - 1)
case esp.UART1:
esp.PCR.SetUART1_SCLK_CONF_UART1_SCLK_DIV_NUM(sclk_div - 1)
}
}
func (uart *UART) setupPins(config UARTConfig, regs registerSet) {
config.RX.Configure(PinConfig{Mode: PinInputPullup})
config.TX.Configure(PinConfig{Mode: PinInputPullup})
// link TX with GPIO signal X (technical reference manual, GPIO matrix)
config.TX.outFunc().Set(regs.gpioMatrixSignal)
// link RX with GPIO signal X and route signals via GPIO matrix
inFunc(regs.gpioMatrixSignal).Set(esp.GPIO_FUNC_IN_SEL_CFG_SEL | uint32(config.RX))
}
func (uart *UART) configureInterrupt(intrMapReg *volatile.Register32) {
// Disable all UART interrupts
uart.Bus.INT_ENA.ClearBits(0x0ffff)
intrMapReg.Set(7)
onceUart.Do(func() {
_ = interrupt.New(7, func(i interrupt.Interrupt) {
UART0.serveInterrupt(0)
UART1.serveInterrupt(1)
}).Enable()
})
}
func (uart *UART) serveInterrupt(num int) {
// get interrupt status
interrutFlag := uart.Bus.INT_ST.Get()
if (interrutFlag & uartInterrupts) == 0 {
return
}
// block UART interrupts while processing
uart.Bus.INT_ENA.ClearBits(uartInterrupts)
if interrutFlag&esp.UART_INT_ENA_RXFIFO_FULL_INT_ENA > 0 {
for uart.Bus.GetSTATUS_RXFIFO_CNT() > 0 {
b := uart.Bus.GetFIFO_RXFIFO_RD_BYTE()
if !uart.Buffer.Put(byte(b & 0xff)) {
uart.DataOverflowDetected = true
}
}
}
if interrutFlag&esp.UART_INT_ENA_PARITY_ERR_INT_ENA > 0 {
uart.ParityErrorDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_FRM_ERR_INT_ENA {
uart.DataErrorDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_RXFIFO_OVF_INT_ENA {
uart.DataOverflowDetected = true
}
if 0 != interrutFlag&esp.UART_INT_ENA_GLITCH_DET_INT_ENA {
uart.DataErrorDetected = true
}
// Clear the UART interrupt status
uart.Bus.INT_CLR.SetBits(interrutFlag)
uart.Bus.INT_CLR.ClearBits(interrutFlag)
// Enable interrupts
uart.Bus.INT_ENA.Set(uartInterrupts)
}
const uart_empty_thresh_default = 10
func (uart *UART) enableTransmitter() {
uart.Bus.SetCONF0_TXFIFO_RST(1)
uart.Bus.SetCONF0_TXFIFO_RST(0)
uart.Bus.SetCONF1_TXFIFO_EMPTY_THRHD(uart_empty_thresh_default)
}
func (uart *UART) enableReceiver() {
uart.Bus.SetCONF0_RXFIFO_RST(1)
uart.Bus.SetCONF0_RXFIFO_RST(0)
uart.Bus.SetCONF1_RXFIFO_FULL_THRHD(1)
uart.Bus.SetINT_ENA_RXFIFO_FULL_INT_ENA(1)
uart.Bus.SetINT_ENA_FRM_ERR_INT_ENA(1)
uart.Bus.SetINT_ENA_PARITY_ERR_INT_ENA(1)
uart.Bus.SetINT_ENA_GLITCH_DET_INT_ENA(1)
uart.Bus.SetINT_ENA_RXFIFO_OVF_INT_ENA(1)
}
func (uart *UART) writeByte(b byte) error {
for (uart.Bus.STATUS.Get()&esp.UART_STATUS_TXFIFO_CNT_Msk)>>esp.UART_STATUS_TXFIFO_CNT_Pos >= 128 {
// Wait until there is space in the transmit buffer.
}
uart.Bus.FIFO.Set(uint32(b))
return nil
}
func (uart *UART) flush() {}
-37
View File
@@ -1,37 +0,0 @@
//go:build esp32c6
package machine
import (
"device/esp"
)
// GPIO matrix signal indices for I2C0 on ESP32-C6.
const (
I2CEXT0_SCL_OUT_IDX = 45
I2CEXT0_SDA_OUT_IDX = 46
)
var (
I2C0 = &I2C{
Bus: esp.I2C0,
funcSCL: I2CEXT0_SCL_OUT_IDX,
funcSDA: I2CEXT0_SDA_OUT_IDX,
useExt1: false,
}
)
// enableI2C0PeriphClock enables the I2C0 peripheral clock via PCR.
func enableI2C0PeriphClock() {
// Enable the APB/bus clock for the I2C0 registers and pulse the reset.
esp.PCR.SetI2C0_CONF_I2C0_CLK_EN(1)
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(1)
esp.PCR.SetI2C0_CONF_I2C0_RST_EN(0)
// On the ESP32-C6 the I2C functional (source) clock is gated and selected
// in PCR.
// Select the XTAL (40 MHz) source and enable the clock gate, otherwise SCL
// is never driven and no bytes are clocked onto the bus.
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_SEL(i2cClkSource)
esp.PCR.SetI2C_SCLK_CONF_I2C_SCLK_EN(1)
}
-250
View File
@@ -1,250 +0,0 @@
//go:build esp32c6
package machine
import (
"device/esp"
"errors"
"machine/usb"
"machine/usb/descriptor"
"runtime/interrupt"
)
// USB Serial/JTAG Controller
// The ESP32-C6 has the same built-in USB Serial/JTAG hardware IP as the
// ESP32-C3. The only difference at the machine level is how the peripheral
// clock is enabled: C3 uses SYSTEM.PERIP_CLK_EN0, while C6 uses the PCR
// (Peripheral Clock Reset) block.
const cpuInterruptFromUSB = 10
type USB_DEVICE struct {
Bus *esp.USB_DEVICE_Type
Buffer *RingBuffer
txPending bool // unflushed data in the EP1 TX FIFO
txStalled bool // set when flushAndWait fails (no host reading); cleared when FIFO becomes writable
}
var (
_USBCDC = &USB_DEVICE{
Bus: esp.USB_DEVICE,
Buffer: NewRingBuffer(),
}
USBCDC Serialer = _USBCDC
)
var (
errUSBWrongSize = errors.New("USB: invalid write size")
errUSBCouldNotWriteAllData = errors.New("USB: could not write all data")
)
type Serialer interface {
WriteByte(c byte) error
Write(data []byte) (n int, err error)
Configure(config UARTConfig) error
Buffered() int
ReadByte() (byte, error)
DTR() bool
RTS() bool
}
var usbConfigured bool
// USBDevice provides a stub USB device for the ESP32-C6. The hardware
// only supports a fixed-function CDC-ACM serial port, so the programmable
// USB device features are no-ops.
type USBDevice struct {
initcomplete bool
InitEndpointComplete bool
}
var USBDev = &USBDevice{}
func (dev *USBDevice) SetStallEPIn(ep uint32) {}
func (dev *USBDevice) SetStallEPOut(ep uint32) {}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {}
// initUSB is intentionally empty — the interp phase evaluates init()
// functions at compile time and cannot access hardware registers.
// Actual hardware setup is deferred to the first Configure() call.
func initUSB() {}
// Configure initialises the USB Serial/JTAG controller clock, pads, and
// interrupt so that received data is buffered automatically.
func (usbdev *USB_DEVICE) Configure(config UARTConfig) error {
if usbConfigured {
return nil
}
usbConfigured = true
// Enable the USB_DEVICE peripheral clock via PCR (C6 uses PCR instead
// of SYSTEM.PERIP_CLK_EN0 that C3 used).
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_CLK_EN(1)
esp.PCR.SetUSB_DEVICE_CONF_USB_DEVICE_RST_EN(0)
// Ensure internal PHY is selected and USB pads are enabled.
usbdev.Bus.SetCONF0_PHY_SEL(0)
usbdev.Bus.SetCONF0_USB_PAD_ENABLE(1)
usbdev.Bus.SetCONF0_DP_PULLUP(1)
// Clear any pending interrupts.
usbdev.Bus.INT_CLR.Set(0xFFFFFFFF)
// Enable the RX-packet-received interrupt.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
// Map the USB peripheral interrupt to CPU interrupt cpuInterruptFromUSB.
esp.INTERRUPT_CORE0.SetUSB_INTR_MAP(cpuInterruptFromUSB)
_ = interrupt.New(cpuInterruptFromUSB, func(interrupt.Interrupt) {
_USBCDC.handleInterrupt()
}).Enable()
return nil
}
// ensureConfigured triggers lazy initialization on first use.
func (usbdev *USB_DEVICE) ensureConfigured() {
if !usbConfigured {
usbdev.Configure(UARTConfig{})
}
}
// handleInterrupt drains the hardware RX FIFO into the software ring buffer.
func (usbdev *USB_DEVICE) handleInterrupt() {
intStatus := usbdev.Bus.INT_ST.Get()
// Disable the RX interrupt to prevent re-triggering while we drain.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(0)
if intStatus&esp.USB_DEVICE_INT_ST_SERIAL_OUT_RECV_PKT_INT_ST != 0 {
for usbdev.Bus.GetEP1_CONF_SERIAL_OUT_EP_DATA_AVAIL() != 0 {
b := byte(usbdev.Bus.EP1.Get())
usbdev.Buffer.Put(b)
}
usbdev.Bus.SetINT_CLR_SERIAL_OUT_RECV_PKT_INT_CLR(1)
}
// Re-enable the RX interrupt.
usbdev.Bus.SetINT_ENA_SERIAL_OUT_RECV_PKT_INT_ENA(1)
}
func (usbdev *USB_DEVICE) WriteByte(c byte) error {
usbdev.ensureConfigured()
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
if usbdev.txStalled {
return errUSBCouldNotWriteAllData
}
if !usbdev.flushAndWait() {
usbdev.txStalled = true
return errUSBCouldNotWriteAllData
}
}
usbdev.txStalled = false
usbdev.Bus.EP1.Set(uint32(c))
if c == '\n' {
usbdev.flush()
usbdev.txPending = false
} else {
usbdev.txPending = true
}
return nil
}
func (usbdev *USB_DEVICE) Write(data []byte) (n int, err error) {
usbdev.ensureConfigured()
if len(data) == 0 {
return 0, nil
}
for i, c := range data {
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() == 0 {
if usbdev.txStalled {
return i, errUSBCouldNotWriteAllData
}
if !usbdev.flushAndWait() {
usbdev.txStalled = true
return i, errUSBCouldNotWriteAllData
}
}
usbdev.txStalled = false
usbdev.Bus.EP1.Set(uint32(c))
}
usbdev.flush()
usbdev.txPending = false
return len(data), nil
}
// Buffered returns the number of bytes waiting in the receive ring buffer.
func (usbdev *USB_DEVICE) Buffered() int {
usbdev.ensureConfigured()
if usbdev.txPending {
usbdev.flush()
usbdev.txPending = false
}
return int(usbdev.Buffer.Used())
}
// ReadByte returns a byte from the receive ring buffer.
func (usbdev *USB_DEVICE) ReadByte() (byte, error) {
b, ok := usbdev.Buffer.Get()
if !ok {
return 0, nil
}
return b, nil
}
func (usbdev *USB_DEVICE) DTR() bool {
return false
}
func (usbdev *USB_DEVICE) RTS() bool {
return false
}
// flush signals WR_DONE to tell the hardware to send the data that has
// been written to the EP1 FIFO.
func (usbdev *USB_DEVICE) flush() {
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
}
// flushAndWait signals WR_DONE and waits for the EP1 FIFO to become
// writable again. Returns false if the FIFO is still locked after the
// timeout (no host reading).
func (usbdev *USB_DEVICE) flushAndWait() bool {
usbdev.Bus.SetEP1_CONF_WR_DONE(1)
for i := 0; i < 50000; i++ {
if usbdev.Bus.GetEP1_CONF_SERIAL_IN_EP_DATA_FREE() != 0 {
return true
}
}
return false
}
// FlushSerial flushes any pending USB serial TX data.
func FlushSerial() {
if _USBCDC.txPending {
_USBCDC.flush()
_USBCDC.txPending = false
}
}
// ConfigureUSBEndpoint is a no-op on ESP32-C6 — the hardware does not
// support programmable USB endpoints.
func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointConfig, setup []usb.SetupConfig) {
}
// SendZlp is a no-op on ESP32-C6.
func SendZlp() {
}
// SendUSBInPacket is a no-op on ESP32-C6.
func SendUSBInPacket(ep uint32, data []byte) bool {
return false
}
-7
View File
@@ -33,10 +33,3 @@ func initI2CExt1Clock() {
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT1_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT1_RST(0)
}
// enableI2C0PeriphClock enables the I2C0 peripheral clock via SYSTEM.
func enableI2C0PeriphClock() {
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
}
+4 -2
View File
@@ -1,4 +1,4 @@
//go:build (esp32c3 || esp32c6 || esp32s3) && !m5stamp_c3
//go:build (esp32c3 || esp32s3) && !m5stamp_c3
package machine
@@ -53,7 +53,9 @@ func (i2c *I2C) Configure(config I2CConfig) error {
//go:inline
func (i2c *I2C) initClock(config I2CConfig) {
if !i2c.useExt1 {
enableI2C0PeriphClock()
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(1)
esp.SYSTEM.SetPERIP_CLK_EN0_I2C_EXT0_CLK_EN(1)
esp.SYSTEM.SetPERIP_RST_EN0_I2C_EXT0_RST(0)
} else {
initI2CExt1Clock()
}
+2 -4
View File
@@ -145,7 +145,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
// Set and enable the GPIOTE interrupt. It's not a problem if this happens
// more than once.
intr := interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
interrupt.New(nrf.IRQ_GPIOTE, func(interrupt.Interrupt) {
for i := range nrf.GPIOTE.EVENTS_IN {
if nrf.GPIOTE.EVENTS_IN[i].Get() != 0 {
nrf.GPIOTE.EVENTS_IN[i].Set(0)
@@ -153,9 +153,7 @@ func (p Pin) SetInterrupt(change PinChange, callback func(Pin)) error {
pinCallbacks[i](pin)
}
}
})
intr.SetPriority(0x40) // interrupt priority 2 (highest priority not reserved by the SoftDevice)
intr.Enable()
}).Enable()
// Everything was configured correctly.
return nil
+13 -18
View File
@@ -22,6 +22,17 @@ var (
epinen uint32
epouten uint32
easyDMABusy volatile.Register8
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
// enterCriticalSection is used to protect access to easyDMA - only one thing
@@ -172,7 +183,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
epDataStatus := nrf.USBD.EPDATASTATUS.Get()
nrf.USBD.EPDATASTATUS.Set(epDataStatus)
var i uint32
for i = 1; i < NumberOfUSBEndpoints; i++ {
for i = 1; i < uint32(len(endPoints)); i++ {
// Check if endpoint has a pending interrupt
inDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPIN1<<(i-1)) > 0
outDataDone := epDataStatus&(nrf.USBD_EPDATASTATUS_EPOUT1<<(i-1)) > 0
@@ -191,7 +202,7 @@ func handleUSBIRQ(interrupt.Interrupt) {
}
// ENDEPOUT[n] events
for i := 0; i < NumberOfUSBEndpoints; i++ {
for i := 0; i < len(endPoints); i++ {
if nrf.USBD.EVENTS_ENDEPOUT[i].Get() > 0 {
nrf.USBD.EVENTS_ENDEPOUT[i].Set(0)
buf := handleEndpointRx(uint32(i))
@@ -367,19 +378,3 @@ func ReceiveUSBControlPacket() ([cdcLineInfoSize]byte, error) {
return b, nil
}
func (dev *USBDevice) SetStallEPIn(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO | nrf.USBD_EPSTALL_STALL)
}
func (dev *USBDevice) SetStallEPOut(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_STALL)
}
func (dev *USBDevice) ClearStallEPIn(ep uint32) {
nrf.USBD.EPSTALL.Set(ep | nrf.USBD_EPSTALL_IO)
}
func (dev *USBDevice) ClearStallEPOut(ep uint32) {
nrf.USBD.EPSTALL.Set(ep)
}
+2 -2
View File
@@ -90,7 +90,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
s2 := rp.USBCTRL_REGS.BUFF_STATUS.Get()
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
// OUT (PC -> rp2040)
for i := 0; i < 16; i++ {
@@ -111,6 +110,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
}
rp.USBCTRL_REGS.BUFF_STATUS.Set(s2)
}
// Bus is reset
@@ -128,7 +128,7 @@ func handleUSBSetAddress(setup usb.Setup) bool {
const ackTimeout = 570
rp.USBCTRL_REGS.SIE_STATUS.Set(rp.USBCTRL_REGS_SIE_STATUS_ACK_REC)
sendUSBPacket(0, nil)
sendUSBPacket(0, []byte{})
// Wait for transfer to complete with a timeout.
t := timer.timeElapsed()
+15 -12
View File
@@ -155,7 +155,9 @@ static bool pico_processor_state_is_nonsecure(void) {
#define BOOTROM_FUNC_TABLE_OFFSET 0x14
#define BOOTROM_WELL_KNOWN_PTR_SIZE 2
// todo remove this (or #ifdef it for A1/A2)
#define BOOTROM_IS_A2() ((*(volatile uint8_t *)0x13) == 2)
#define BOOTROM_WELL_KNOWN_PTR_SIZE (BOOTROM_IS_A2() ? 2 : 4)
#define BOOTROM_VTABLE_OFFSET 0x00
#define BOOTROM_TABLE_LOOKUP_OFFSET (BOOTROM_FUNC_TABLE_OFFSET + BOOTROM_WELL_KNOWN_PTR_SIZE)
@@ -227,12 +229,11 @@ void reset_usb_boot(uint32_t usb_activity_gpio_pin_mask, uint32_t disable_interf
// https://github.com/raspberrypi/pico-sdk
// src/rp2350/hardware_regs/include/hardware/regs/qmi.h
#define QMI_DIRECT_CSR_EN_BITS 0x00000001
#define QMI_DIRECT_CSR_ASSERT_CS0N_BITS 0x00000004
#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000
#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400
#define QMI_M1_WFMT_RESET 0x00001000
#define QMI_M1_WCMD_RESET 0x0000a002
#define QMI_DIRECT_CSR_EN_BITS 0x00000001
#define QMI_DIRECT_CSR_RXEMPTY_BITS 0x00010000
#define QMI_DIRECT_CSR_TXFULL_BITS 0x00000400
#define QMI_M1_WFMT_RESET 0x00001000
#define QMI_M1_WCMD_RESET 0x0000a002
// https://github.com/raspberrypi/pico-sdk
@@ -405,11 +406,13 @@ static ram_func void flash_rp2350_restore_qmi_cs1(const flash_rp2350_qmi_save_st
}
void ram_func flash_cs_force(bool high) {
if (high) {
hw_clear_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
} else {
hw_set_bits(&qmi_hw->direct_csr, QMI_DIRECT_CSR_ASSERT_CS0N_BITS);
}
uint32_t field_val = high ?
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_HIGH :
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_VALUE_LOW;
hw_write_masked(&io_qspi_hw->io[1].ctrl,
field_val << IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_LSB,
IO_QSPI_GPIO_QSPI_SS_CTRL_OUTOVER_BITS
);
}
// Adapted from flash_range_program()
+1 -1
View File
@@ -93,7 +93,6 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
s2 := rp.USB.BUFF_STATUS.Get()
rp.USB.BUFF_STATUS.Set(s2)
// OUT (PC -> rp2350)
for i := 0; i < 16; i++ {
@@ -114,6 +113,7 @@ func handleUSBIRQ(intr interrupt.Interrupt) {
}
}
rp.USB.BUFF_STATUS.Set(s2)
}
// Bus is reset
+2 -2
View File
@@ -130,11 +130,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
+2 -2
View File
@@ -21,11 +21,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22
case rp.SPI1:
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26
}
+2 -2
View File
@@ -55,11 +55,11 @@ func (spi *SPI) validPins(config SPIConfig) error {
var okSDI, okSDO, okSCK bool
switch spi.Bus {
case rp.SPI0:
okSDI = config.SDI == NoPin || config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
okSDI = config.SDI == 0 || config.SDI == 4 || config.SDI == 16 || config.SDI == 20 || config.SDI == 32 || config.SDI == 36
okSDO = config.SDO == 3 || config.SDO == 7 || config.SDO == 19 || config.SDO == 23 || config.SDO == 35 || config.SDO == 39
okSCK = config.SCK == 2 || config.SCK == 6 || config.SCK == 18 || config.SCK == 22 || config.SCK == 34 || config.SCK == 38
case rp.SPI1:
okSDI = config.SDI == NoPin || config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
okSDI = config.SDI == 8 || config.SDI == 12 || config.SDI == 24 || config.SDI == 28 || config.SDI == 40 || config.SDI == 44
okSDO = config.SDO == 11 || config.SDO == 15 || config.SDO == 27 || config.SDO == 31 || config.SDO == 43 || config.SDO == 47
okSCK = config.SCK == 10 || config.SCK == 14 || config.SCK == 26 || config.SCK == 30 || config.SCK == 42 || config.SCK == 46
}
+1 -3
View File
@@ -174,9 +174,7 @@ func (spi *SPI) Configure(config SPIConfig) error {
// SPI pin configuration
config.SCK.setFunc(fnSPI)
config.SDO.setFunc(fnSPI)
if config.SDI != NoPin {
config.SDI.setFunc(fnSPI)
}
config.SDI.setFunc(fnSPI)
return spi.initSPI(config)
}
+35 -58
View File
@@ -16,60 +16,53 @@ var (
data []byte
pid uint32
}
endPoints = []uint32{
usb.CONTROL_ENDPOINT: usb.ENDPOINT_TYPE_CONTROL,
usb.CDC_ENDPOINT_ACM: (usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn),
usb.CDC_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_BULK | usb.EndpointOut),
usb.CDC_ENDPOINT_IN: (usb.ENDPOINT_TYPE_BULK | usb.EndpointIn),
usb.HID_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt In
usb.HID_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Interrupt Out
usb.MIDI_ENDPOINT_IN: (usb.ENDPOINT_TYPE_DISABLE), // Bulk In
usb.MIDI_ENDPOINT_OUT: (usb.ENDPOINT_TYPE_DISABLE), // Bulk Out
}
)
func initEndpoint(ep, config uint32) {
val := uint32(usbEpControlEnable) | uint32(usbEpControlInterruptPerBuff)
// Each endpoint has 128 bytes of DPRAM buffer space allocated (2 * usbBufferLen).
// To support bidirectional configurations using the same endpoint number,
// we allocate the first 64 bytes (Buffer0) to OUT transfers, and the remaining
// 64 bytes (Buffer1) to IN transfers by shifting the IN offset by usbBufferLen.
offset := ep*2*usbBufferLen + 0x100
val |= offset
// Bulk and interrupt endpoints must have their Packet ID reset to DATA0 when un-stalled.
// Since both directions share the same ep, we reset their PID flags independently.
if (config & usb.EndpointIn) != 0 {
epXPIDResetIn[ep] = false
} else {
epXPIDResetOut[ep] = false
}
epXPIDReset[ep] = false // Default to false in case an endpoint is re-initialized.
switch config {
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointIn:
epXPIDResetIn[ep] = true
epXdata0In[ep] = false
val |= offset + usbBufferLen
val |= usbEpControlEndpointTypeInterrupt
_usbDPSRAM.EPxControl[ep].In.Set(val)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_BULK | usb.EndpointOut:
epXPIDResetOut[ep] = true
epXdata0Out[ep] = false
val |= offset
val |= usbEpControlEndpointTypeBulk
_usbDPSRAM.EPxControl[ep].Out.Set(val)
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_INTERRUPT | usb.EndpointOut:
epXPIDResetOut[ep] = true
epXdata0Out[ep] = false
val |= offset
val |= usbEpControlEndpointTypeInterrupt
_usbDPSRAM.EPxControl[ep].Out.Set(val)
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBufferLen & usbBuf0CtrlLenMask)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_BULK | usb.EndpointIn:
epXPIDResetIn[ep] = true
epXdata0In[ep] = false
val |= offset + usbBufferLen
val |= usbEpControlEndpointTypeBulk
_usbDPSRAM.EPxControl[ep].In.Set(val)
epXPIDReset[ep] = true
case usb.ENDPOINT_TYPE_CONTROL:
val |= offset
val |= usbEpControlEndpointTypeControl
_usbDPSRAM.EPxBufferControl[ep].Out.Set(usbBuf0CtrlData1Pid)
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
@@ -97,7 +90,7 @@ func sendUSBPacket(ep uint32, data []byte) {
} else {
sendOnEP0DATADONE.offset = 0
}
epXdata0In[ep] = true
epXdata0[ep] = true
}
sendViaEPIn(ep, data, count)
@@ -134,31 +127,21 @@ func handleEndpointRx(ep uint32) []byte {
// AckUsbOutTransfer is called to acknowledge the completion of a USB OUT transfer.
func AckUsbOutTransfer(ep uint32) {
ep = ep & 0x7F
setEPDataPIDOut(ep, !epXdata0Out[ep])
setEPDataPID(ep, !epXdata0[ep])
}
// Set the USB endpoint Packet ID to DATA0 or DATA1 for OUT direction.
func setEPDataPIDOut(ep uint32, dataOne bool) {
epXdata0Out[ep] = dataOne
if epXdata0Out[ep] || ep == 0 {
// Set the USB endpoint Packet ID to DATA0 or DATA1.
func setEPDataPID(ep uint32, dataOne bool) {
epXdata0[ep] = dataOne
if epXdata0[ep] || ep == 0 {
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlData1Pid)
}
_usbDPSRAM.EPxBufferControl[ep].Out.SetBits(usbBuf0CtrlAvail)
}
// Set the USB endpoint Packet ID to DATA0 or DATA1 for IN direction.
func setEPDataPIDIn(ep uint32, dataOne bool) {
epXdata0In[ep] = dataOne
if epXdata0In[ep] || ep == 0 {
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlData1Pid)
}
_usbDPSRAM.EPxBufferControl[ep].In.SetBits(usbBuf0CtrlAvail)
}
func SendZlp() {
sendUSBPacket(0, nil)
sendUSBPacket(0, []byte{})
}
func sendViaEPIn(ep uint32, data []byte, count int) {
@@ -166,19 +149,15 @@ func sendViaEPIn(ep uint32, data []byte, count int) {
val := uint32(count) | usbBuf0CtrlAvail
// DATA0 or DATA1
epXdata0In[ep&0x7F] = !epXdata0In[ep&0x7F]
if !epXdata0In[ep&0x7F] {
epXdata0[ep&0x7F] = !epXdata0[ep&0x7F]
if !epXdata0[ep&0x7F] {
val |= usbBuf0CtrlData1Pid
}
// Mark as full
val |= usbBuf0CtrlFull
if (ep & 0x7F) == 0 {
copy(_usbDPSRAM.EPxBuffer[0].Buffer0[:], data[:count])
} else {
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer1[:], data[:count])
}
copy(_usbDPSRAM.EPxBuffer[ep&0x7F].Buffer0[:], data[:count])
_usbDPSRAM.EPxBufferControl[ep&0x7F].In.Set(val)
}
@@ -210,9 +189,9 @@ func (dev *USBDevice) ClearStallEPIn(ep uint32) {
ep = ep & 0x7F
val := uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep].In.ClearBits(val)
if epXPIDResetIn[ep] {
if epXPIDReset[ep] {
// Reset the PID to DATA0
setEPDataPIDIn(ep, false)
setEPDataPID(ep, false)
}
}
@@ -221,9 +200,9 @@ func (dev *USBDevice) ClearStallEPOut(ep uint32) {
ep = ep & 0x7F
val := uint32(usbBuf0CtrlStall)
_usbDPSRAM.EPxBufferControl[ep].Out.ClearBits(val)
if epXPIDResetOut[ep] {
if epXPIDReset[ep] {
// Reset the PID to DATA0
setEPDataPIDOut(ep, false)
setEPDataPID(ep, false)
}
}
@@ -251,12 +230,10 @@ type usbBuffer struct {
}
var (
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
epXdata0In [16]bool
epXdata0Out [16]bool
epXPIDResetIn [16]bool
epXPIDResetOut [16]bool
setupBytes [8]byte
_usbDPSRAM = (*usbDPSRAM)(unsafe.Pointer(uintptr(0x50100000)))
epXdata0 [16]bool
epXPIDReset [16]bool
setupBytes [8]byte
)
func (d *usbDPSRAM) setupBytes() []byte {
+15 -17
View File
@@ -76,15 +76,13 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
uart.Receive(byte((uart.rxReg.Get() & 0xFF)))
}
// Clear error flags (ORE=bit3, NE=bit2, FE=bit1, PE=bit0) to prevent
// an interrupt storm and ensure the USART can continue receiving.
if s&0xF != 0 {
// Clear overrun error (ORE, bit 3) to prevent an interrupt storm.
if s&0x8 != 0 {
if uart.errClearReg != nil {
// Newer USART peripherals (L0, L4, L5, G0, F7, U5, WL, etc.):
// clear all error flags via ICR (ORECF|NECF|FECF|PECF = bits 3:0).
uart.errClearReg.Set(s & 0xF)
// Newer USART peripherals: clear ORE via the ICR register.
uart.errClearReg.Set(0x8) // ORECF
} else if s&0x20 == 0 {
// Older USART (F1/F4): errors are cleared by reading SR then DR.
// Older USART (F1/F4): ORE is cleared by reading SR then DR.
// SR was already read above. If RXNE was set, DR was read in
// the Receive path. Otherwise do a dummy DR read to complete
// the clearing sequence.
@@ -93,20 +91,20 @@ func (uart *UART) handleInterrupt(interrupt.Interrupt) {
}
}
// SetBaudRate sets the communication speed for the UART. Defer to chip-specific
// routines for calculation
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
// WriteByte writes a byte of data to the UART.
func (uart *UART) writeByte(c byte) error {
// Wait for the transmit data register to be empty before writing, so we
// don't overwrite a byte that hasn't moved to the shift register yet.
uart.txReg.Set(uint32(c))
for !uart.statusReg.HasBits(uart.txEmptyFlag) {
}
uart.txReg.Set(uint32(c))
return nil
}
// flush waits until the USART shift register has finished transmitting the
// last byte (TC = Transmission Complete, bit 6). Without this, Write() returns
// while the final byte is still clocking out on the wire.
func (uart *UART) flush() {
for !uart.statusReg.HasBits(1 << 6) { // TC bit
}
}
func (uart *UART) flush() {}
@@ -1,13 +0,0 @@
//go:build stm32 && !stm32u585
package machine
// SetBaudRate sets the communication speed for the UART. Defers to
// chip-specific getBaudRateDivisor for the divisor calculation.
//
// On STM32U585 this function is overridden in machine_stm32u585.go because
// the U5 family requires UE=0 to write BRR.
func (uart *UART) SetBaudRate(br uint32) {
divider := uart.getBaudRateDivisor(br)
uart.Bus.BRR.Set(divider)
}
-1
View File
@@ -256,7 +256,6 @@ func enableAltFuncClock(bus unsafe.Pointer) {
stm32.RCC.APB1ENR2.SetBits(stm32.RCC_APB1ENR2_I2C4EN)
case unsafe.Pointer(stm32.USART1): // USART1 clock enable
stm32.RCC.APB2ENR.SetBits(stm32.RCC_APB2ENR_USART1EN)
_ = stm32.RCC.APB2ENR.Get() // readback: ensure clock is active before accessing USART1 registers
case unsafe.Pointer(stm32.USART2): // USART2 clock enable
stm32.RCC.APB1ENR1.SetBits(stm32.RCC_APB1ENR1_USART2EN)
case unsafe.Pointer(stm32.USART3): // USART3 clock enable
+6 -35
View File
@@ -8,14 +8,14 @@ import (
)
func CPUFrequency() uint32 {
return 160_000_000
return 4_000_000
}
// Internal use: configured speed of the APB1 and APB2 timers, this should be kept
// in sync with any changes to runtime package which configures the oscillators
// and clock frequencies
const APB1_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
const APB2_TIM_FREQ = 160e6 // 160MHz (PLL1: MSIS 4MHz × 80 / 1 / 2)
const APB1_TIM_FREQ = 4e6 // 4MHz (MSI default)
const APB2_TIM_FREQ = 4e6 // 4MHz (MSI default)
//---------- UART related code
@@ -55,21 +55,10 @@ func (uart *UART) isLPUART1() bool {
// NOTE: keep this in sync with the runtime/runtime_stm32u5.go clock init code
func (uart *UART) getBaudRateDivisor(baudRate uint32) uint32 {
if uart.isLPUART1() {
// LPUART uses BRR = 256 * fclk / baud.
// Use 64-bit arithmetic to avoid overflow: at 160 MHz,
// 256 * 160_000_000 = 40_960_000_000 which exceeds uint32 max.
return uint32(uint64(256) * uint64(CPUFrequency()) / uint64(baudRate))
// LPUART uses BRR = 256 * fclk / baud
return (256 * CPUFrequency()) / baudRate
}
// USART requires BRR >= 16 for 16x oversampling (OVER8=0).
// A divisor below 16 is invalid per the STM32 reference manual and causes
// undefined hardware behaviour — in practice the receiver fires ORE/RXNE
// interrupts at an impossible rate, completely starving the CPU.
const minBRR = 16
divisor := CPUFrequency() / baudRate
if divisor < minBRR {
divisor = minBRR
}
return divisor
return CPUFrequency() / baudRate
}
// Register names vary by ST processor, these are for STM U5
@@ -81,24 +70,6 @@ func (uart *UART) setRegisters() {
uart.errClearReg = &uart.Bus.ICR
}
// SetBaudRate overrides the shared implementation for STM32U5. On this
// family the BRR register is read-only while UE=1 (USART enabled), so the
// USART must be briefly disabled to change the baud rate. This matters when
// the servo library (or any code) calls SetBaudRate after Configure has
// already enabled the USART.
func (uart *UART) SetBaudRate(br uint32) {
cr1 := uart.Bus.CR1.Get()
if cr1&stm32.USART_CR1_UE != 0 {
// Disable the USART so BRR becomes writable.
uart.Bus.CR1.Set(cr1 &^ stm32.USART_CR1_UE)
}
uart.Bus.BRR.Set(uart.getBaudRateDivisor(br))
if cr1&stm32.USART_CR1_UE != 0 {
// Restore CR1 exactly as it was (re-enables USART, TE, RE, etc.).
uart.Bus.CR1.Set(cr1)
}
}
//---------- SPI related types and code
// SPI on the STM32U5 using the new SPIv2 peripheral
+2 -4
View File
@@ -71,18 +71,16 @@ type rttSerial struct {
rttControlBlock
}
var terminalByteString = []byte("Terminal\x00")
func (s *rttSerial) Configure(config UARTConfig) error {
s.maxNumUpBuffers = rttMaxNumUpBuffers
s.maxNumDownBuffers = rttMaxNumDownBuffers
s.buffersUp[0].name = &terminalByteString[0]
s.buffersUp[0].name = &[]byte("Terminal\x00")[0]
s.buffersUp[0].buffer = &rttBufferUpData[0]
s.buffersUp[0].bufferSize = rttBufferSizeUp
s.buffersUp[0].flags = rttModeNoBlockSkip
s.buffersDown[0].name = &terminalByteString[0]
s.buffersDown[0].name = &[]byte("Terminal\x00")[0]
s.buffersDown[0].buffer = &rttBufferDownData[0]
s.buffersDown[0].bufferSize = rttBufferSizeDown
s.buffersDown[0].flags = rttModeNoBlockSkip
+10 -32
View File
@@ -14,21 +14,9 @@ type USBDevice struct {
InitEndpointComplete bool
}
type usbEndpointEntry struct {
Endpoint uint32
Config uint32
}
var (
USBDev = &USBDevice{}
USBCDC Serialer
endPoints = []usbEndpointEntry{
{
Endpoint: usb.CONTROL_ENDPOINT,
Config: usb.ENDPOINT_TYPE_CONTROL,
},
}
)
func initUSB() {
@@ -208,18 +196,17 @@ func sendDescriptorString(data string, maxLen uint16) {
// Clamp the length.
maxEncBytes := min(len(usb_trans_buffer), len(udd_ep_control_cache_buffer), int(maxLen))
// Write the header.
buf := usb_trans_buffer[:min(2*len(data)+2, maxEncBytes)]
hdr, body := buf[:2], buf[2:]
data = data[:min(len(data), (maxEncBytes-2)/2)]
// hdr[0] (bLength) should convey the "original total string length" before being limited by the host's maxLen.
hdr[0] = byte(2*len(data) + 2)
// Write the header.
buf := usb_trans_buffer[:2*len(data)+2]
hdr, body := buf[:2], buf[2:]
hdr[0] = byte(len(buf))
hdr[1] = descriptor.TypeString
// Convert the string to UTF16.
// NOTE: Using range here would cause the length to disagree when multibyte codepoints are present.
limit := min(len(data), len(body)/2)
for i := 0; i < limit; i++ {
for i := 0; i < len(data); i++ {
body[2*i] = byte(data[i])
body[2*i+1] = 0
}
@@ -289,11 +276,8 @@ func handleStandardSetup(setup usb.Setup) bool {
case usb.SET_CONFIGURATION:
if setup.BmRequestType&usb.REQUEST_RECIPIENT == usb.REQUEST_DEVICE {
for _, entry := range endPoints {
if entry.Endpoint == usb.CONTROL_ENDPOINT {
continue
}
initEndpoint(uint32(entry.Endpoint), entry.Config)
for i := 1; i < len(endPoints); i++ {
initEndpoint(uint32(i), endPoints[i])
}
usbConfiguration = setup.WValueL
@@ -374,18 +358,12 @@ func ConfigureUSBEndpoint(desc descriptor.Descriptor, epSettings []usb.EndpointC
for _, ep := range epSettings {
if ep.IsIn {
endPoints = append(endPoints, usbEndpointEntry{
Endpoint: uint32(ep.Index),
Config: uint32(ep.Type | usb.EndpointIn),
})
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointIn)
if ep.TxHandler != nil {
usbTxHandler[ep.Index] = ep.TxHandler
}
} else {
endPoints = append(endPoints, usbEndpointEntry{
Endpoint: uint32(ep.Index),
Config: uint32(ep.Type | usb.EndpointOut),
})
endPoints[ep.Index] = uint32(ep.Type | usb.EndpointOut)
if ep.RxHandler != nil {
usbRxHandler[ep.Index] = func(b []byte) bool {
ep.RxHandler(b)
+3 -7
View File
@@ -2,14 +2,10 @@
package cdc
import (
"machine/usb"
)
const (
cdcEndpointACM = usb.CDC_ENDPOINT_ACM
cdcEndpointOut = usb.CDC_ENDPOINT_OUT
cdcEndpointIn = usb.CDC_ENDPOINT_IN
cdcEndpointACM = 1
cdcEndpointOut = 2
cdcEndpointIn = 3
)
// New returns USBCDC struct.
+19 -57
View File
@@ -26,23 +26,11 @@ type cdcLineInfo struct {
// USBCDC is the USB CDC aka serial over USB interface.
type USBCDC struct {
tx ring512
rx ring512
// inflight is the number of bytes currently submitted to the USB IN endpoint.
tx ring512
rx ring512
inflight atomic.Uint32
// txActive is the TX-pump ownership flag: 0 = idle, 1 = a pump owns the TX
// path. Claimed once (kickTx, CAS 0->1), held across every in-flight packet
// and the TX-complete IRQ, and released only when the ring drains. While it
// is set, kickTx's CAS fails and no second pump starts, which serializes the
// pump against Write across cores. Same model as Linux NAPI_STATE_SCHED:
// held across completion, dropped only with a recheck
// (Documentation/networking/napi.rst).
txActive atomic.Uint32
rbuf [1]byte
wbuf [1]byte
rbuf [1]byte
wbuf [1]byte
}
var (
@@ -93,7 +81,7 @@ func (usbcdc *USBCDC) Configure(config machine.UARTConfig) error {
// Flush flushes buffered data.
func (usbcdc *USBCDC) Flush() {
for usbcdc.tx.Used() > 0 || usbcdc.txActive.Load() != 0 {
for usbcdc.tx.Used() > 0 {
gosched()
}
}
@@ -117,59 +105,33 @@ func (usbcdc *USBCDC) Write(data []byte) (n int, err error) {
return n, nil
}
// kickTx claims the TX pump for a producer. This CAS is the only start-from-idle
// edge; if it fails, a pump already owns the path and will drain what we just
// enqueued -- see the recheck in sendFromRing.
// kickTx starts a transfer if none is in flight. Called from main context only.
func (usbcdc *USBCDC) kickTx() {
if !usbcdc.txActive.CompareAndSwap(0, 1) {
return
if usbcdc.inflight.Load() > 0 {
return // txhandler will chain the next packet.
}
usbcdc.sendFromRing()
}
func (usbcdc *USBCDC) txhandler() {
// TX-complete IRQ. The pump is still owned here (txActive stayed 1 across the
// in-flight packet), so continue WITHOUT re-claiming -- pairs with the CAS in
// kickTx. A CAS here would see the flag already set, bail, and stall the chain.
inflight := usbcdc.inflight.Load()
if inflight == 0 {
return
}
usbcdc.tx.Discard(inflight)
usbcdc.inflight.Store(0)
usbcdc.tx.Discard(inflight)
usbcdc.sendFromRing()
}
// sendFromRing runs one step of the TX pump: submit one IN packet, or release the
// pump if the ring is empty. Precondition: txActive == 1 (from kickTx's CAS, or
// still held from the previous packet when entered via txhandler).
// sendFromRing sends one USB packet from the ring and sets inflight.
// Called from kickTx (main) or txhandler (ISR), but never concurrently
// because kickTx only runs when inflight==0 and txhandler only runs
// when inflight>0.
func (usbcdc *USBCDC) sendFromRing() {
for {
d1, _ := usbcdc.tx.Peek()
if len(d1) == 0 {
// Release the pump, then re-scan the ring: closes the missed-wakeup
// race where Write Put()s data and kickTx's CAS then fails (txActive
// still set), leaving the data for this pump to drain. The Store(0)
// is ordered before the Used() load -- and, in the producer, Put()
// before its CAS -- by the sequential consistency of Go's atomics, so
// neither side misses the other (assumes the ring's accesses are
// atomic too). cf. napi_complete_done() clearing NAPI_STATE_SCHED
// then rechecking.
usbcdc.txActive.Store(0)
if usbcdc.tx.Used() == 0 {
return // ring empty and pump released; done
}
if !usbcdc.txActive.CompareAndSwap(0, 1) {
return // another producer re-claimed the pump; let it run
}
continue // re-claimed; re-peek and keep pumping
}
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
usbcdc.inflight.Store(uint32(len(chunk)))
machine.SendUSBInPacket(cdcEndpointIn, chunk)
return // in flight; txActive stays set, txhandler continues
d1, _ := usbcdc.tx.Peek()
if len(d1) == 0 {
return
}
chunk := d1[:min(usb.EndpointPacketSize, len(d1))]
usbcdc.inflight.Store(uint32(len(chunk)))
machine.SendUSBInPacket(cdcEndpointIn, chunk)
}
// WriteByte writes a byte of data to the USB CDC interface.
+3 -6
View File
@@ -150,9 +150,6 @@ var InterfaceCDCData = InterfaceType{
data: interfaceCDCData[:],
}
// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
var CDC = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
@@ -163,9 +160,9 @@ var CDC = Descriptor{
ClassSpecificCDCCallManagement.Bytes(),
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
EndpointEP1IN.Bytes(),
InterfaceCDCData.Bytes(),
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
}),
}
+96 -36
View File
@@ -15,44 +15,104 @@ const (
TransferTypeInterrupt
)
type EndpointNumber uint8
const (
EndpointEP1 EndpointNumber = iota
EndpointEP2
EndpointEP3
EndpointEP4
)
const (
maxEndpoints = 4
)
var (
endpointEPIn = [maxEndpoints][endpointTypeLen]byte{}
endpointEPOut = [maxEndpoints][endpointTypeLen]byte{}
)
func EndpointIN(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
e := EndpointType{data: endpointEPIn[ep][:]}
e.Length(endpointTypeLen)
e.Type(TypeEndpoint)
e.EndpointAddress(uint8(ep+1) | 0x80) // EndpointNumber is 0-based, addresses are 1-based
e.Attributes(transferType)
e.MaxPacketSize(maxPacketSize)
e.Interval(interval)
return e
var endpointEP1IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x81, // EndpointAddress
0x03, // Attributes
0x10, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x10, // Interval
}
func EndpointOUT(ep EndpointNumber, transferType uint8, maxPacketSize uint16, interval uint8) EndpointType {
e := EndpointType{data: endpointEPOut[ep][:]}
e.Length(endpointTypeLen)
e.Type(TypeEndpoint)
e.EndpointAddress(uint8(ep + 1)) // EndpointNumber is 0-based, addresses are 1-based
e.Attributes(transferType)
e.MaxPacketSize(maxPacketSize)
e.Interval(interval)
return e
var EndpointEP1IN = EndpointType{
data: endpointEP1IN[:],
}
var endpointEP2OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x02, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP2OUT = EndpointType{
data: endpointEP2OUT[:],
}
var endpointEP3IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x83, // EndpointAddress
0x02, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointEP3IN = EndpointType{
data: endpointEP3IN[:],
}
var endpointEP4IN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x84, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP4IN = EndpointType{
data: endpointEP4IN[:],
}
var endpointEP5OUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x05, // EndpointAddress
0x03, // Attributes
0x40, // MaxPacketSizeL
0x00, // MaxPacketSizeH
0x01, // Interval
}
var EndpointEP5OUT = EndpointType{
data: endpointEP5OUT[:],
}
// Mass Storage Class bulk in endpoint
var endpointMSCIN = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x86, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCIN = EndpointType{
data: endpointMSCIN[:],
}
// Mass Storage Class bulk out endpoint
var endpointMSCOUT = [endpointTypeLen]byte{
endpointTypeLen,
TypeEndpoint,
0x07, // EndpointAddress
TransferTypeBulk, // Attributes
0x40, // MaxPacketSizeL (64 bytes)
0x00, // MaxPacketSizeH
0x00, // Interval
}
var EndpointMSCOUT = EndpointType{
data: endpointMSCOUT[:],
}
const (
+5 -10
View File
@@ -111,11 +111,6 @@ var ClassHID = ClassHIDType{
data: classHID[:],
}
// EP1 IN : CDC Call Management
// EP2 OUT: CDC OUT
// EP2 IN : CDC IN
// EP3 OUT: HID OUT
// EP3 IN : HID IN
var CDCHID = Descriptor{
Device: DeviceCDC.Bytes(),
Configuration: Append([][]byte{
@@ -126,14 +121,14 @@ var CDCHID = Descriptor{
ClassSpecificCDCACM.Bytes(),
ClassSpecificCDCUnion.Bytes(),
ClassSpecificCDCCallManagement.Bytes(),
EndpointIN(EndpointEP1, TransferTypeInterrupt, 0x10, 0x10).Bytes(),
EndpointEP1IN.Bytes(),
InterfaceCDCData.Bytes(),
EndpointOUT(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointIN(EndpointEP2, TransferTypeBulk, 0x40, 0x00).Bytes(),
EndpointEP2OUT.Bytes(),
EndpointEP3IN.Bytes(),
InterfaceHID.Bytes(),
ClassHID.Bytes(),
EndpointIN(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
EndpointOUT(EndpointEP3, TransferTypeInterrupt, 0x40, 0x01).Bytes(),
EndpointEP4IN.Bytes(),
EndpointEP5OUT.Bytes(),
}),
HID: map[uint16][]byte{
2: Append([][]byte{ // Update ClassLength in classHID whenever the array length is modified!

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